From 5f6913653d3b805cff812ea42f8d9cc45be3dfc0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 17:18:43 +0800 Subject: [PATCH 001/466] Refactor resource binding to use ResourceSlot arrays Modernize and simplify the graphics API resource binding model: - Remove ResourceLayout and ResourceBinding in favor of a new ResourceSlot struct. - Update pipeline and table descriptions to use ResourceSlot[] arrays. - Add ResourceTable.Set() for per-slot resource assignment with validation. - Refactor ValidationLayer to validate ResourceSlot-based bindings. - Remove obsolete code, messages, and abstract methods related to the old system. - General cleanup of unused files and legacy logic. --- sources/Zenith.NET/GraphicsContext.cs | 9 - sources/Zenith.NET/ResourceLayout.cs | 8 - sources/Zenith.NET/ResourceTable.cs | 66 ++++++++ .../Zenith.NET/Structs/ComputePipelineDesc.cs | 2 +- .../Structs/GraphicsPipelineDesc.cs | 2 +- .../Structs/MeshShadingPipelineDesc.cs | 2 +- sources/Zenith.NET/Structs/ResourceBinding.cs | 12 -- .../Zenith.NET/Structs/ResourceLayoutDesc.cs | 6 - sources/Zenith.NET/Structs/ResourceSlot.cs | 8 + .../Zenith.NET/Structs/ResourceTableDesc.cs | 4 +- sources/Zenith.NET/ValidationLayer.cs | 154 +++++------------- 11 files changed, 122 insertions(+), 151 deletions(-) delete mode 100644 sources/Zenith.NET/ResourceLayout.cs delete mode 100644 sources/Zenith.NET/Structs/ResourceBinding.cs delete mode 100644 sources/Zenith.NET/Structs/ResourceLayoutDesc.cs create mode 100644 sources/Zenith.NET/Structs/ResourceSlot.cs diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 9facf070..86289626 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -100,13 +100,6 @@ public Sampler CreateSampler(SamplerDesc desc) return CreateSamplerImpl(desc); } - public ResourceLayout CreateResourceLayout(ResourceLayoutDesc desc) - { - ValidationLayer?.ValidateDesc(desc); - - return CreateResourceLayoutImpl(desc); - } - public ResourceTable CreateResourceTable(ResourceTableDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -175,8 +168,6 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract Sampler CreateSamplerImpl(SamplerDesc desc); - protected abstract ResourceLayout CreateResourceLayoutImpl(ResourceLayoutDesc desc); - protected abstract ResourceTable CreateResourceTableImpl(ResourceTableDesc desc); protected abstract GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc); diff --git a/sources/Zenith.NET/ResourceLayout.cs b/sources/Zenith.NET/ResourceLayout.cs deleted file mode 100644 index 741f25ec..00000000 --- a/sources/Zenith.NET/ResourceLayout.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public abstract class ResourceLayout(GraphicsContext context, ResourceLayoutDesc desc) : GraphicsResource(context) -{ - private ResourceLayoutDesc desc = desc; - - public ref readonly ResourceLayoutDesc Desc => ref desc; -} \ No newline at end of file diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs index 8e9082e6..73f34c06 100644 --- a/sources/Zenith.NET/ResourceTable.cs +++ b/sources/Zenith.NET/ResourceTable.cs @@ -6,10 +6,76 @@ public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc d public ref readonly ResourceTableDesc Desc => ref desc; + public void Set(uint slot, params IBindableResource[] resources) + { + if (slot >= desc.Slots.Length || resources.Length is 0 || resources.Length > desc.Slots[slot].Count) + { + return; + } + + foreach (IBindableResource resource in resources) + { + if (resource?.IsDisposed is not false) + { + return; + } + } + + switch (desc.Slots[slot].Type) + { + case ResourceType.ConstantBuffer: + case ResourceType.StructuredBuffer: + case ResourceType.StructuredBufferReadWrite: + foreach (IBindableResource resource in resources) + { + if (resource is not Buffer and not BufferView) + { + return; + } + } + break; + + case ResourceType.Texture: + case ResourceType.TextureReadWrite: + foreach (IBindableResource resource in resources) + { + if (resource is not Texture and not TextureView) + { + return; + } + } + break; + + case ResourceType.Sampler: + foreach (IBindableResource resource in resources) + { + if (resource is not Sampler) + { + return; + } + } + break; + + case ResourceType.AccelerationStructure: + foreach (IBindableResource resource in resources) + { + if (resource is not TopLevelAccelerationStructure) + { + return; + } + } + break; + } + + SetImpl(slot, resources); + } + internal void Preprocess(CommandBuffer commandBuffer) { PreprocessImpl(commandBuffer); } + protected abstract void SetImpl(uint slot, IBindableResource[] resources); + protected abstract void PreprocessImpl(CommandBuffer commandBuffer); } diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index e659023f..c496810f 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -4,7 +4,7 @@ public record struct ComputePipelineDesc { public Shader Compute; - public ResourceLayout? ResourceLayout; + public ResourceSlot[] ResourceSlots; public uint ThreadGroupSizeX; diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 093d74a2..60b4c1aa 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -8,7 +8,7 @@ public record struct GraphicsPipelineDesc public Shader Pixel; - public ResourceLayout? ResourceLayout; + public ResourceSlot[] ResourceSlots; public InputLayout[] InputLayouts; diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index d7af84d6..31aa1af6 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -10,7 +10,7 @@ public record struct MeshShadingPipelineDesc public Shader Pixel; - public ResourceLayout? ResourceLayout; + public ResourceSlot[] ResourceSlots; public PrimitiveTopology PrimitiveTopology; diff --git a/sources/Zenith.NET/Structs/ResourceBinding.cs b/sources/Zenith.NET/Structs/ResourceBinding.cs deleted file mode 100644 index 4b7b759c..00000000 --- a/sources/Zenith.NET/Structs/ResourceBinding.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Zenith.NET; - -public record struct ResourceBinding -{ - public ResourceType Type; - - public uint Index; - - public uint Count; - - public ShaderStageFlags StageFlags; -} diff --git a/sources/Zenith.NET/Structs/ResourceLayoutDesc.cs b/sources/Zenith.NET/Structs/ResourceLayoutDesc.cs deleted file mode 100644 index c325b92f..00000000 --- a/sources/Zenith.NET/Structs/ResourceLayoutDesc.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Zenith.NET; - -public record struct ResourceLayoutDesc -{ - public ResourceBinding[] Bindings; -} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/ResourceSlot.cs b/sources/Zenith.NET/Structs/ResourceSlot.cs new file mode 100644 index 00000000..71c79858 --- /dev/null +++ b/sources/Zenith.NET/Structs/ResourceSlot.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public record struct ResourceSlot +{ + public ResourceType Type; + + public uint Count; +} diff --git a/sources/Zenith.NET/Structs/ResourceTableDesc.cs b/sources/Zenith.NET/Structs/ResourceTableDesc.cs index 31c227ea..0c98c0a2 100644 --- a/sources/Zenith.NET/Structs/ResourceTableDesc.cs +++ b/sources/Zenith.NET/Structs/ResourceTableDesc.cs @@ -2,7 +2,5 @@ public record struct ResourceTableDesc { - public ResourceLayout Layout; - - public IBindableResource[] Resources; + public ResourceSlot[] Slots; } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 9a4e6aca..aadf0be0 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -347,114 +347,18 @@ internal void ValidateDesc(SamplerDesc desc) } } - internal void ValidateDesc(ResourceLayoutDesc desc) - { - if (desc.Bindings is null || desc.Bindings.Length is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ResourceLayoutDesc.Bindings")); - - return; - } - - foreach (ResourceBinding binding in desc.Bindings) - { - if (!Enum.IsDefined(binding.Type)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "ResourceLayoutBinding.Type", binding.Type)); - } - } - } - internal void ValidateDesc(ResourceTableDesc desc) { - if (desc.Layout is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ResourceTableDesc.Layout")); - - return; - } - - if (desc.Layout.IsDisposed) + if (desc.Slots is null || desc.Slots.Length is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ResourceTableDesc.Layout")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ResourceTableDesc.Slots")); return; } - if (desc.Resources is null) + for (int i = 0; i < desc.Slots.Length; i++) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ResourceTableDesc.Resources")); - - return; - } - - uint resourceStartIndex = 0; - - for (int i = 0; i < desc.Layout.Desc.Bindings.Length; i++) - { - ResourceBinding binding = desc.Layout.Desc.Bindings[i]; - - if (resourceStartIndex + binding.Count > (uint)desc.Resources.Length) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInsufficientResources, "ResourceTableDesc.Resources", resourceStartIndex + binding.Count, i, desc.Resources.Length)); - - break; - } - - for (uint j = 0; j < binding.Count; j++) - { - IBindableResource resource = desc.Resources[(int)(resourceStartIndex + j)]; - - if (resource is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ResourceTableDesc.Resources")); - - continue; - } - - if (resource.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ResourceTableDesc.Resources")); - - continue; - } - - switch (binding.Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - if (resource is not Buffer and not BufferView) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeOfType, "ResourceTableDesc.Resources", "Buffer or BufferView", binding.Type)); - } - break; - - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - if (resource is not Texture and not TextureView) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeOfType, "ResourceTableDesc.Resources", "Texture or TextureView", binding.Type)); - } - break; - - case ResourceType.Sampler: - if (resource is not Sampler) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeOfType, "ResourceTableDesc.Resources", "Sampler", binding.Type)); - } - break; - - case ResourceType.AccelerationStructure: - if (resource is not TopLevelAccelerationStructure) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeOfType, "ResourceTableDesc.Resources", "AccelerationStructure", binding.Type)); - } - break; - } - } - - resourceStartIndex += binding.Count; + CheckResourceSlot($"ResourceTableDesc.Slots[{i}]", desc.Slots[i]); } } @@ -480,9 +384,16 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "GraphicsPipelineDesc.Pixel")); } - if (desc.ResourceLayout?.IsDisposed is true) + if (desc.ResourceSlots is null) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.ResourceSlots")); + } + else { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "GraphicsPipelineDesc.ResourceLayout")); + for (int i = 0; i < desc.ResourceSlots.Length; i++) + { + CheckResourceSlot($"GraphicsPipelineDesc.ResourceSlots[{i}]", desc.ResourceSlots[i]); + } } if (desc.InputLayouts is null) @@ -539,9 +450,16 @@ internal void ValidateDesc(ComputePipelineDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ComputePipelineDesc.Compute")); } - if (desc.ResourceLayout?.IsDisposed is true) + if (desc.ResourceSlots is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ComputePipelineDesc.ResourceLayout")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ComputePipelineDesc.ResourceSlots")); + } + else + { + for (int i = 0; i < desc.ResourceSlots.Length; i++) + { + CheckResourceSlot($"ComputePipelineDesc.ResourceSlots[{i}]", desc.ResourceSlots[i]); + } } if (desc.ThreadGroupSizeX is 0 || desc.ThreadGroupSizeY is 0 || desc.ThreadGroupSizeZ is 0) @@ -577,9 +495,16 @@ internal void ValidateDesc(MeshShadingPipelineDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Pixel")); } - if (desc.ResourceLayout?.IsDisposed is true) + if (desc.ResourceSlots is null) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.ResourceSlots")); + } + else { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.ResourceLayout")); + for (int i = 0; i < desc.ResourceSlots.Length; i++) + { + CheckResourceSlot($"MeshShadingPipelineDesc.ResourceSlots[{i}]", desc.ResourceSlots[i]); + } } if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) @@ -799,6 +724,19 @@ internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAc } } + private void CheckResourceSlot(string name, ResourceSlot resourceSlot) + { + if (!Enum.IsDefined(resourceSlot.Type)) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", resourceSlot.Type)); + } + + if (resourceSlot.Count is 0) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.Count")); + } + } + private void CheckRenderStates(string name, RenderStates renderStates) { if (!Enum.IsDefined(renderStates.RasterizerState.CullMode)) @@ -971,10 +909,6 @@ file static class ValidationMessages public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; - public const string HasInsufficientResources = "{0} has insufficient resources: requires at least {1} to satisfy the layout up to binding index {2}, but only {3} provided."; - - public const string MustBeOfType = "{0} item must be a {1} for {2} binding."; - public const string MustBeOneOf = "{0} must be one of: {1}."; public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; From 895b984cbfddb4e3626c3e286560daf71f5b3a0c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 18:28:06 +0800 Subject: [PATCH 002/466] Refactor DX12 resource binding to use ResourceSlots Removed DXResourceLayout and DXResourceRange in favor of directly using ResourceSlots for descriptor range generation and resource binding. Pipelines now generate root signature parameters from ResourceSlots, and DXResourceTable manages descriptor tokens and resource views per slot. Resource binding and preprocessing logic have been updated accordingly. All references to the old layout classes have been removed, and related logic has been centralized and clarified. Minor cleanups and improvements to resource management included. --- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 4 +- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 5 - .../DXGraphicsPipeline.cs | 48 +-- .../DXMeshShadingPipeline.cs | 48 +-- .../Zenith.NET.DirectX12/DXResourceLayout.cs | 135 ------- .../Zenith.NET.DirectX12/DXResourceRange.cs | 3 - .../Zenith.NET.DirectX12/DXResourceTable.cs | 360 ++++++++---------- sources/Zenith.NET.DirectX12/Extensions.cs | 72 +++- 8 files changed, 255 insertions(+), 420 deletions(-) delete mode 100644 sources/Zenith.NET.DirectX12/DXResourceLayout.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXResourceRange.cs diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index 8d08337b..069c0929 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -18,10 +18,10 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : CS = desc.Compute.DirectX12().GetShaderBytecode(scope) }; - // ResourceLayout + // ResourceSlots { List parameters = []; - if (desc.ResourceLayout is not null && desc.ResourceLayout.DirectX12().DescriptorRanges(ShaderStageFlags.None, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + if (desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) { if (cbvSrvUavRanges.Length > 0) { diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 9a5ab3a3..07497505 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -155,11 +155,6 @@ protected override Sampler CreateSamplerImpl(SamplerDesc desc) return new DXSampler(this, desc); } - protected override ResourceLayout CreateResourceLayoutImpl(ResourceLayoutDesc desc) - { - return new DXResourceLayout(this, desc); - } - protected override ResourceTable CreateResourceTableImpl(ResourceTableDesc desc) { return new DXResourceTable(this, desc); diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 342ac305..b3911bca 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -106,45 +106,35 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); } - // ResourceLayout + // ResourceSlots { List parameters = []; - if (desc.ResourceLayout is not null) + if (desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) { - DXResourceLayout resourceLayout = desc.ResourceLayout.DirectX12(); - - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) + if (cbvSrvUavRanges.Length > 0) { - if (resourceLayout.DescriptorRanges(stage, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + parameters.Add(new() { - if (cbvSrvUavRanges.Length > 0) + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); + NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) } + }); + } - if (samplerRanges.Length > 0) + if (samplerRanges.Length > 0) + { + parameters.Add(new() + { + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); + NumDescriptorRanges = (uint)samplerRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) } - } + }); } } diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 2bf0201e..78c245e8 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -103,45 +103,35 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); } - // ResourceLayout + // ResourceSlots { List parameters = []; - if (desc.ResourceLayout is not null) + if (desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) { - DXResourceLayout resourceLayout = desc.ResourceLayout.DirectX12(); - - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) + if (cbvSrvUavRanges.Length > 0) { - if (resourceLayout.DescriptorRanges(stage, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + parameters.Add(new() { - if (cbvSrvUavRanges.Length > 0) + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); + NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) } + }); + } - if (samplerRanges.Length > 0) + if (samplerRanges.Length > 0) + { + parameters.Add(new() + { + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); + NumDescriptorRanges = (uint)samplerRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) } - } + }); } } diff --git a/sources/Zenith.NET.DirectX12/DXResourceLayout.cs b/sources/Zenith.NET.DirectX12/DXResourceLayout.cs deleted file mode 100644 index 92f47130..00000000 --- a/sources/Zenith.NET.DirectX12/DXResourceLayout.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal class DXResourceLayout : ResourceLayout -{ - private readonly DXResourceRange[] ranges; - - public DXResourceLayout(DXGraphicsContext context, ResourceLayoutDesc desc) : base(context, desc) - { - ranges = new DXResourceRange[desc.Bindings.Length]; - - uint index = 0; - for (int i = 0; i < desc.Bindings.Length; i++) - { - ResourceBinding binding = desc.Bindings[i]; - - ranges[i] = new(binding.Type, binding.StageFlags, index, binding.Count); - - index += binding.Count; - } - - if (ResourceRanges(ShaderStageFlags.None, out DXResourceRange[] cbvSrvUavRanges, out DXResourceRange[] samplerRanges)) - { - if (cbvSrvUavRanges.Length > 0) - { - RootParameterCount++; - } - - if (samplerRanges.Length > 0) - { - RootParameterCount++; - } - } - - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) - { - if (ResourceRanges(stage, out cbvSrvUavRanges, out samplerRanges)) - { - if (cbvSrvUavRanges.Length > 0) - { - GraphicsRootParameterCount++; - } - - if (samplerRanges.Length > 0) - { - GraphicsRootParameterCount++; - } - } - } - } - - public uint RootParameterCount { get; } - - public uint GraphicsRootParameterCount { get; } - - public bool ResourceRanges(ShaderStageFlags stage, out DXResourceRange[] cbvSrvUavRanges, out DXResourceRange[] samplerRanges) - { - List cbvSrvUavRangeList = []; - List samplerRangeList = []; - - foreach (DXResourceRange range in ranges) - { - if (stage is not ShaderStageFlags.None && !range.StageFlags.HasFlag(stage)) - { - continue; - } - - if (range.Type is ResourceType.Sampler) - { - samplerRangeList.Add(range); - } - else - { - cbvSrvUavRangeList.Add(range); - } - } - - cbvSrvUavRanges = [.. cbvSrvUavRangeList]; - samplerRanges = [.. samplerRangeList]; - - return cbvSrvUavRanges.Length > 0 || samplerRanges.Length > 0; - } - - public bool DescriptorRanges(ShaderStageFlags stage, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges) - { - List cbvSrvUavRangeList = []; - List samplerRangeList = []; - - uint cbvSrvUavRangeOffset = 0; - uint samplerRangeOffset = 0; - foreach (ResourceBinding binding in Desc.Bindings) - { - if (stage is not ShaderStageFlags.None && !binding.StageFlags.HasFlag(stage)) - { - continue; - } - - DescriptorRange range = new() - { - RangeType = DXFormats.DirectX12(binding.Type), - NumDescriptors = binding.Count, - BaseShaderRegister = binding.Index - }; - - if (binding.Type is ResourceType.Sampler) - { - range.OffsetInDescriptorsFromTableStart = samplerRangeOffset; - samplerRangeOffset += binding.Count; - - samplerRangeList.Add(range); - } - else - { - range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset; - cbvSrvUavRangeOffset += binding.Count; - - cbvSrvUavRangeList.Add(range); - } - } - - cbvSrvUavRanges = [.. cbvSrvUavRangeList]; - samplerRanges = [.. samplerRangeList]; - - return cbvSrvUavRanges.Length > 0 || samplerRanges.Length > 0; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - } -} diff --git a/sources/Zenith.NET.DirectX12/DXResourceRange.cs b/sources/Zenith.NET.DirectX12/DXResourceRange.cs deleted file mode 100644 index d127e433..00000000 --- a/sources/Zenith.NET.DirectX12/DXResourceRange.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Zenith.NET.DirectX12; - -internal readonly record struct DXResourceRange(ResourceType Type, ShaderStageFlags StageFlags, uint Index, uint Count); \ No newline at end of file diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index d09d3ae5..ef64a7ac 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -6,273 +6,215 @@ internal class DXResourceTable : ResourceTable { private readonly DXDescriptorToken cbvSrvUavToken; private readonly DXDescriptorToken samplerToken; - private readonly Dictionary graphicsTokens; + private readonly DXTextureView?[] srvTextureViews; + private readonly DXTextureView?[] uavTextureViews; public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - (cbvSrvUavToken, samplerToken) = GetTokens(ShaderStageFlags.None); - graphicsTokens = ZenithHelper.GraphicShaderStages().ToDictionary(static item => item, GetTokens); - - uint resourceStartIndex = 0; - List srvTextureViews = []; - List uavTextureViews = []; - - for (int i = 0; i < desc.Layout.Desc.Bindings.Length; i++) + if (desc.Slots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) { - ResourceBinding binding = desc.Layout.Desc.Bindings[i]; - - for (uint j = 0; j < binding.Count; j++) + if (cbvSrvUavRanges.Length > 0) { - IBindableResource resource = desc.Resources[(int)(resourceStartIndex + j)]; - - if (binding.Type is ResourceType.Texture or ResourceType.TextureReadWrite) - { - List views = binding.Type is ResourceType.Texture ? srvTextureViews : uavTextureViews; - - if (resource is Texture texture) - { - views.Add(texture.DirectX12().View); - } - else if (resource is TextureView textureView) - { - views.Add(textureView.DirectX12()); - } - } + cbvSrvUavToken = Context.CbvSrvUavAllocator.Allocate((uint)cbvSrvUavRanges.Sum(static item => item.NumDescriptors)); } - resourceStartIndex += binding.Count; + if (samplerRanges.Length > 0) + { + samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.NumDescriptors)); + } } - SrvTextureViews = [.. srvTextureViews]; - UavTextureViews = [.. uavTextureViews]; + srvTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; + uavTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - public DXTextureView[] SrvTextureViews { get; } - - public DXTextureView[] UavTextureViews { get; } - public void Bind(DXCommandBuffer commandBuffer, DXDescriptorTable cbvSrvUavTable, DXDescriptorTable samplerTable, bool isGraphics) { uint offset = 0; - if (isGraphics) + if (cbvSrvUavToken.Length > 0) { - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) + if (isGraphics) { - (DXDescriptorToken cbvSrvUavToken, DXDescriptorToken samplerToken) = graphicsTokens[stage]; - - if (cbvSrvUavToken.Length > 0) - { - commandBuffer.GraphicsCommandList4.SetGraphicsRootDescriptorTable(offset++, cbvSrvUavTable.GpuCurrentHandle); - - cbvSrvUavTable.Write(cbvSrvUavToken); - } - - if (samplerToken.Length > 0) - { - commandBuffer.GraphicsCommandList4.SetGraphicsRootDescriptorTable(offset++, samplerTable.GpuCurrentHandle); - - samplerTable.Write(samplerToken); - } + commandBuffer.GraphicsCommandList4.SetGraphicsRootDescriptorTable(offset++, cbvSrvUavTable.GpuCurrentHandle); } - } - else - { - if (cbvSrvUavToken.Length > 0) + else { commandBuffer.GraphicsCommandList4.SetComputeRootDescriptorTable(offset++, cbvSrvUavTable.GpuCurrentHandle); - - cbvSrvUavTable.Write(cbvSrvUavToken); } - if (samplerToken.Length > 0) + cbvSrvUavTable.Write(cbvSrvUavToken); + } + + if (samplerToken.Length > 0) + { + if (isGraphics) + { + commandBuffer.GraphicsCommandList4.SetGraphicsRootDescriptorTable(offset++, samplerTable.GpuCurrentHandle); + } + else { commandBuffer.GraphicsCommandList4.SetComputeRootDescriptorTable(offset++, samplerTable.GpuCurrentHandle); - - samplerTable.Write(samplerToken); } + + samplerTable.Write(samplerToken); } } - protected override void PreprocessImpl(CommandBuffer commandBuffer) + protected override void SetImpl(uint slot, IBindableResource[] resources) { - DXCommandBuffer dxCommandBuffer = commandBuffer.DirectX12(); + ResourceSlot resourceSlot = Desc.Slots[slot]; - foreach (DXTextureView textureView in SrvTextureViews) + uint index = 0; + if (resourceSlot.Type is ResourceType.Sampler) { - textureView.TransitionStates(dxCommandBuffer, ResourceStates.AllShaderResource); + index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); } - - foreach (DXTextureView textureView in UavTextureViews) + else { - textureView.TransitionStates(dxCommandBuffer, ResourceStates.UnorderedAccess); + index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); } - } - - protected override void SetResourceName(string name) - { - } - protected override void Destroy() - { - foreach ((DXDescriptorToken cbvSrvUavToken, DXDescriptorToken samplerToken) in graphicsTokens.Values) + switch (resourceSlot.Type) { - cbvSrvUavToken.Dispose(); - samplerToken.Dispose(); - } - graphicsTokens.Clear(); + case ResourceType.ConstantBuffer: + { + foreach (IBindableResource resource in resources) + { + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); + } - samplerToken.Dispose(); - cbvSrvUavToken.Dispose(); - } + index++; + } + } + break; - private (DXDescriptorToken CbvSrvUavToken, DXDescriptorToken SamplerToken) GetTokens(ShaderStageFlags stage) - { - DXDescriptorToken cbvSrvUavToken = default; - DXDescriptorToken samplerToken = default; + case ResourceType.StructuredBuffer: + { + foreach (IBindableResource resource in resources) + { + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); + } - if (Desc.Layout.DirectX12().ResourceRanges(stage, out DXResourceRange[] cbvSrvUavRanges, out DXResourceRange[] samplerRanges)) - { - if (cbvSrvUavRanges.Length > 0) - { - cbvSrvUavToken = Context.CbvSrvUavAllocator.Allocate((uint)cbvSrvUavRanges.Sum(static item => item.Count)); + index++; + } + } + break; - uint index = 0; - foreach (DXResourceRange range in cbvSrvUavRanges) + case ResourceType.StructuredBufferReadWrite: { - IBindableResource[] resources = [.. Desc.Resources.Skip((int)range.Index).Take((int)range.Count)]; + foreach (IBindableResource resource in resources) + { + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); + } + + index++; + } + } + break; - switch (range.Type) + case ResourceType.Texture: + { + foreach (IBindableResource resource in resources) { - case ResourceType.ConstantBuffer: - { - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.StructuredBuffer: - { - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.StructuredBufferReadWrite: - { - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.Texture: - { - foreach (IBindableResource resource in resources) - { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is TextureView textureView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.TextureReadWrite: - { - foreach (IBindableResource resource in resources) - { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is TextureView textureView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.AccelerationStructure: - { - foreach (IBindableResource resource in resources) - { - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; + if (resource is Texture texture) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); + + srvTextureViews[index] = texture.DirectX12().View; + } + else if (resource is TextureView textureView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); + + srvTextureViews[index] = textureView.DirectX12(); + } + + index++; } } - } + break; - if (samplerRanges.Length > 0) - { - samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.Count)); + case ResourceType.TextureReadWrite: + { + foreach (IBindableResource resource in resources) + { + if (resource is Texture texture) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + + uavTextureViews[index] = texture.DirectX12().View; + } + else if (resource is TextureView textureView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); + + uavTextureViews[index] = textureView.DirectX12(); + } + + index++; + } + } + break; - uint index = 0; - foreach (DXResourceRange range in samplerRanges) + case ResourceType.AccelerationStructure: { - foreach (IBindableResource resource in Desc.Resources.Skip((int)range.Index).Take((int)range.Count)) + foreach (IBindableResource resource in resources) { - if (resource is Sampler sampler) + if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) { - Context.Device.CopyDescriptorsSimple(1, samplerToken[index], sampler.DirectX12().Token.Handle, DescriptorHeapType.Sampler); + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); } index++; } } - } + break; } + } + + protected override void PreprocessImpl(CommandBuffer commandBuffer) + { + DXCommandBuffer dxCommandBuffer = commandBuffer.DirectX12(); - return (cbvSrvUavToken, samplerToken); + foreach (DXTextureView? textureView in srvTextureViews) + { + textureView?.TransitionStates(dxCommandBuffer, ResourceStates.AllShaderResource); + } + + foreach (DXTextureView? textureView in uavTextureViews) + { + textureView?.TransitionStates(dxCommandBuffer, ResourceStates.UnorderedAccess); + } + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + samplerToken.Dispose(); + cbvSrvUavToken.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index a9598823..0095e9c7 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; @@ -28,6 +29,69 @@ internal bool IsSuccess() } } + extension(ResourceSlot[] resourceSlots) + { + internal bool DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges) + { + List cbvSrvUavRangeList = []; + List samplerRangeList = []; + + uint cbvIndex = 0; + uint srvIndex = 0; + uint uavIndex = 0; + uint samplerIndex = 0; + uint cbvSrvUavRangeOffset = 0; + uint samplerRangeOffset = 0; + foreach (ResourceSlot resourceSlot in resourceSlots) + { + DescriptorRange range = new() + { + RangeType = DXFormats.DirectX12(resourceSlot.Type), + NumDescriptors = resourceSlot.Count + }; + + switch (resourceSlot.Type) + { + case ResourceType.ConstantBuffer: + range.BaseShaderRegister = cbvIndex++; + range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset++; + + cbvSrvUavRangeList.Add(range); + break; + + case ResourceType.StructuredBuffer: + case ResourceType.Texture: + case ResourceType.AccelerationStructure: + range.BaseShaderRegister = srvIndex++; + range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset++; + + cbvSrvUavRangeList.Add(range); + break; + + case ResourceType.StructuredBufferReadWrite: + case ResourceType.TextureReadWrite: + range.BaseShaderRegister = uavIndex++; + range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset++; + + cbvSrvUavRangeList.Add(range); + break; + + case ResourceType.Sampler: + range.BaseShaderRegister = samplerIndex++; + range.OffsetInDescriptorsFromTableStart = samplerRangeOffset++; + + samplerRangeList.Add(range); + break; + } + } + + cbvSrvUavRanges = [.. cbvSrvUavRangeList]; + samplerRanges = [.. samplerRangeList]; + + return cbvSrvUavRanges.Length > 0 || samplerRanges.Length > 0; + } + } + extension(CommandBuffer commandBuffer) { internal DXCommandBuffer DirectX12() @@ -116,14 +180,6 @@ internal DXTopLevelAccelerationStructure DirectX12() } } - extension(ResourceLayout resourceLayout) - { - internal DXResourceLayout DirectX12() - { - return (DXResourceLayout)resourceLayout; - } - } - extension(ResourceTable resourceTable) { internal DXResourceTable DirectX12() From 25a649e8e87722628d3b5994a4476ae4a38744bc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 19:01:49 +0800 Subject: [PATCH 003/466] Refactor Metal resource layout and table binding logic Removed MTLResourceLayout and now compute buffer/texture/sampler counts and indices directly from ResourceTableDesc.Slots. Resource binding in MTLResourceTable is handled via SetImpl, using arrays of nullable Binding structs. The Binding struct now supports both GPU address and resource ID for buffers. Updated MTLGraphicsPipeline to compute vertex buffer start index from resource slots. Removed unused Metal extension methods and layout creation logic. Refactored DXResourceTable binding logic for clarity. --- .../Zenith.NET.DirectX12/DXResourceTable.cs | 120 +++++------ sources/Zenith.NET.Metal/Extensions.cs | 8 - .../Zenith.NET.Metal/MTLGraphicsContext.cs | 5 - .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 2 +- sources/Zenith.NET.Metal/MTLResourceLayout.cs | 45 ---- sources/Zenith.NET.Metal/MTLResourceTable.cs | 192 ++++++++++-------- 6 files changed, 157 insertions(+), 215 deletions(-) delete mode 100644 sources/Zenith.NET.Metal/MTLResourceLayout.cs diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index ef64a7ac..7fb842db 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -80,114 +80,102 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) switch (resourceSlot.Type) { case ResourceType.ConstantBuffer: + foreach (IBindableResource resource in resources) { - foreach (IBindableResource resource in resources) + if (resource is Buffer buffer) { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); + } + + index++; } break; case ResourceType.StructuredBuffer: + foreach (IBindableResource resource in resources) { - foreach (IBindableResource resource in resources) + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); } + + index++; } break; case ResourceType.StructuredBufferReadWrite: + foreach (IBindableResource resource in resources) { - foreach (IBindableResource resource in resources) + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); } + + index++; } break; case ResourceType.Texture: + foreach (IBindableResource resource in resources) { - foreach (IBindableResource resource in resources) + if (resource is Texture texture) { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - - srvTextureViews[index] = texture.DirectX12().View; - } - else if (resource is TextureView textureView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - srvTextureViews[index] = textureView.DirectX12(); - } + srvTextureViews[index] = texture.DirectX12().View; + } + else if (resource is TextureView textureView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - index++; + srvTextureViews[index] = textureView.DirectX12(); } + + index++; } break; case ResourceType.TextureReadWrite: + foreach (IBindableResource resource in resources) { - foreach (IBindableResource resource in resources) + if (resource is Texture texture) { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - uavTextureViews[index] = texture.DirectX12().View; - } - else if (resource is TextureView textureView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - - uavTextureViews[index] = textureView.DirectX12(); - } + uavTextureViews[index] = texture.DirectX12().View; + } + else if (resource is TextureView textureView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - index++; + uavTextureViews[index] = textureView.DirectX12(); } + + index++; } break; case ResourceType.AccelerationStructure: + foreach (IBindableResource resource in resources) { - foreach (IBindableResource resource in resources) + if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) { - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); - } - - index++; + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); } + + index++; } break; } diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 33bbf9b5..e975c15b 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -114,14 +114,6 @@ internal MTLTopLevelAccelerationStructure Metal() } } - extension(ResourceLayout resourceLayout) - { - internal MTLResourceLayout Metal() - { - return (MTLResourceLayout)resourceLayout; - } - } - extension(ResourceTable resourceTable) { internal MTLResourceTable Metal() diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index bdd25114..1dc8f358 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -103,11 +103,6 @@ protected override Sampler CreateSamplerImpl(SamplerDesc desc) return new MTLSampler(this, desc); } - protected override ResourceLayout CreateResourceLayoutImpl(ResourceLayoutDesc desc) - { - return new MTLResourceLayout(this, desc); - } - protected override ResourceTable CreateResourceTableImpl(ResourceTableDesc desc) { return new MTLResourceTable(this, desc); diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs index 86fa35ca..c6a42103 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -10,7 +10,7 @@ internal class MTLGraphicsPipeline : GraphicsPipeline public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) { - VertexBufferStartIndex = desc.ResourceLayout is not null ? desc.ResourceLayout.Metal().BufferCount : 0; + VertexBufferStartIndex = (uint)desc.ResourceSlots.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); MTL4RenderPipelineDescriptor descriptor = new() { diff --git a/sources/Zenith.NET.Metal/MTLResourceLayout.cs b/sources/Zenith.NET.Metal/MTLResourceLayout.cs deleted file mode 100644 index 34b60f3a..00000000 --- a/sources/Zenith.NET.Metal/MTLResourceLayout.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Zenith.NET.Metal; - -internal class MTLResourceLayout : ResourceLayout -{ - public MTLResourceLayout(MTLGraphicsContext context, ResourceLayoutDesc desc) : base(context, desc) - { - for (int i = 0; i < desc.Bindings.Length; i++) - { - ResourceBinding binding = desc.Bindings[i]; - - switch (binding.Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - case ResourceType.AccelerationStructure: - BufferCount += binding.Count; - break; - - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - TextureCount += binding.Count; - break; - - case ResourceType.Sampler: - SamplerCount += binding.Count; - break; - } - } - } - - public uint BufferCount { get; } - - public uint TextureCount { get; } - - public uint SamplerCount { get; } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - } -} diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs index 50885767..319311d8 100644 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ b/sources/Zenith.NET.Metal/MTLResourceTable.cs @@ -4,117 +4,127 @@ namespace Zenith.NET.Metal; internal class MTLResourceTable : ResourceTable { - private readonly Binding[] bufferBindings; - private readonly Binding[] textureBindings; - private readonly Binding[] samplerBindings; - private readonly Binding[] accelerationStructureBindings; + private readonly Binding?[] bufferBindings; + private readonly Binding?[] textureBindings; + private readonly Binding?[] samplerBindings; public MTL4ArgumentTable ArgumentTable; public MTLResourceTable(MTLGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - MTLResourceLayout layout = desc.Layout.Metal(); + bufferBindings = new Binding?[desc.Slots.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count)]; + textureBindings = new Binding?[desc.Slots.Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count)]; + samplerBindings = new Binding?[desc.Slots.Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count)]; MTL4ArgumentTableDescriptor descriptor = new() { - MaxBufferBindCount = layout.BufferCount, - MaxTextureBindCount = layout.TextureCount, - MaxSamplerStateBindCount = layout.SamplerCount + MaxBufferBindCount = (uint)bufferBindings.Length, + MaxTextureBindCount = (uint)textureBindings.Length, + MaxSamplerStateBindCount = (uint)samplerBindings.Length }; ArgumentTable = context.Device.MakeArgumentTable(descriptor, out NSError error); error.Success(); - - List bufferBindingList = []; - List textureBindingList = []; - List samplerBindingList = []; - List accelerationStructureBindingList = []; - - uint resourceStartIndex = 0; - - for (int i = 0; i < layout.Desc.Bindings.Length; i++) - { - ResourceBinding binding = layout.Desc.Bindings[i]; - - for (uint j = 0; j < binding.Count; j++) - { - IBindableResource resource = desc.Resources[(int)(resourceStartIndex + j)]; - - switch (binding.Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - if (resource is Buffer buffer) - { - bufferBindingList.Add(new(buffer.Metal().GpuAddress, default, binding.Index + j)); - } - else if (resource is BufferView bufferView) - { - bufferBindingList.Add(new(bufferView.Metal().GpuAddress, default, binding.Index + j)); - } - break; - - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - if (resource is Texture texture) - { - textureBindingList.Add(new(default, texture.Metal().Texture.GpuResourceID, binding.Index + j)); - } - else if (resource is TextureView textureView) - { - textureBindingList.Add(new(default, textureView.Metal().Texture.GpuResourceID, binding.Index + j)); - } - break; - - case ResourceType.Sampler: - if (resource is Sampler sampler) - { - samplerBindingList.Add(new(default, sampler.Metal().SamplerState.GpuResourceID, binding.Index + j)); - } - break; - - case ResourceType.AccelerationStructure: - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - accelerationStructureBindingList.Add(new(default, topLevelAccelerationStructure.Metal().AccelerationStructure.GpuResourceID, binding.Index + j)); - } - break; - } - } - - resourceStartIndex += binding.Count; - } - - bufferBindings = [.. bufferBindingList]; - textureBindings = [.. textureBindingList]; - samplerBindings = [.. samplerBindingList]; - accelerationStructureBindings = [.. accelerationStructureBindingList]; - - Bind(ArgumentTable); } public void Bind(MTL4ArgumentTable argumentTable) { - foreach (Binding binding in bufferBindings) + foreach (Binding? binding in bufferBindings) { - binding.Buffer(argumentTable); + binding?.Buffer(argumentTable); } - foreach (Binding binding in textureBindings) + foreach (Binding? binding in textureBindings) { - binding.Texture(argumentTable); + binding?.Texture(argumentTable); } - foreach (Binding binding in samplerBindings) + foreach (Binding? binding in samplerBindings) { - binding.Sampler(argumentTable); + binding?.Sampler(argumentTable); } + } + + protected override void SetImpl(uint slot, IBindableResource[] resources) + { + ResourceSlot resourceSlot = Desc.Slots[slot]; - foreach (Binding binding in accelerationStructureBindings) + uint index = 0; + switch (resourceSlot.Type) { - binding.AccelerationStructure(argumentTable); + case ResourceType.ConstantBuffer: + case ResourceType.StructuredBuffer: + case ResourceType.StructuredBufferReadWrite: + case ResourceType.AccelerationStructure: + index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); + break; + + case ResourceType.Texture: + case ResourceType.TextureReadWrite: + index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count); + break; + + case ResourceType.Sampler: + index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); + break; } + + switch (resourceSlot.Type) + { + case ResourceType.ConstantBuffer: + case ResourceType.StructuredBuffer: + case ResourceType.StructuredBufferReadWrite: + case ResourceType.AccelerationStructure: + foreach (IBindableResource resource in resources) + { + if (resource is Buffer buffer) + { + bufferBindings[index] = new(buffer.Metal().GpuAddress, default, index); + } + else if (resource is BufferView bufferView) + { + bufferBindings[index] = new(bufferView.Metal().GpuAddress, default, index); + } + else if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) + { + bufferBindings[index] = new(default, topLevelAccelerationStructure.Metal().AccelerationStructure.GpuResourceID, index); + } + + index++; + } + break; + + case ResourceType.Texture: + case ResourceType.TextureReadWrite: + foreach (IBindableResource resource in resources) + { + if (resource is Texture texture) + { + textureBindings[index] = new(default, texture.Metal().Texture.GpuResourceID, index); + } + else if (resource is TextureView textureView) + { + textureBindings[index] = new(default, textureView.Metal().Texture.GpuResourceID, index); + } + + index++; + } + break; + + case ResourceType.Sampler: + foreach (IBindableResource resource in resources) + { + if (resource is Sampler sampler) + { + samplerBindings[index] = new(default, sampler.Metal().SamplerState.GpuResourceID, index); + } + + index++; + } + break; + } + + Bind(ArgumentTable); } protected override void PreprocessImpl(CommandBuffer commandBuffer) @@ -134,7 +144,14 @@ private readonly struct Binding(nuint gpuAddress, MTLResourceID resourceID, uint { public void Buffer(MTL4ArgumentTable argumentTable) { - argumentTable.SetAddress(gpuAddress, index); + if (gpuAddress != default) + { + argumentTable.SetAddress(gpuAddress, index); + } + else + { + argumentTable.SetResource(resourceID, index); + } } public void Texture(MTL4ArgumentTable argumentTable) @@ -146,10 +163,5 @@ public void Sampler(MTL4ArgumentTable argumentTable) { argumentTable.SetSamplerState(resourceID, index); } - - public void AccelerationStructure(MTL4ArgumentTable argumentTable) - { - argumentTable.SetResource(resourceID, index); - } } } From e738c895a173972795b4d864b96e364f2d5a7c79 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 20:10:53 +0800 Subject: [PATCH 004/466] Refactor Vulkan resource binding to use ResourceSlot[] Remove ResourceLayout from Vulkan backend; pipelines and descriptor sets now use ResourceSlot[] directly for layout creation and binding. Descriptor set layouts are generated on-the-fly. Updated DirectX backend for consistency. Simplifies and unifies resource binding across backends. --- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 42 ++-- .../DXGraphicsPipeline.cs | 42 ++-- .../DXMeshShadingPipeline.cs | 42 ++-- .../Zenith.NET.DirectX12/DXResourceTable.cs | 17 +- sources/Zenith.NET.DirectX12/Extensions.cs | 4 +- sources/Zenith.NET.Vulkan/Extensions.cs | 66 +++++- .../Zenith.NET.Vulkan/VKComputePipeline.cs | 18 +- .../VKDescriptorAllocator.cs | 45 ++-- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 5 - .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 18 +- .../VKMeshShadingPipeline.cs | 18 +- sources/Zenith.NET.Vulkan/VKResourceLayout.cs | 98 -------- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 214 ++++++++++-------- 13 files changed, 316 insertions(+), 313 deletions(-) delete mode 100644 sources/Zenith.NET.Vulkan/VKResourceLayout.cs diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index 069c0929..cd235eb5 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -20,34 +20,34 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : // ResourceSlots { + desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); + List parameters = []; - if (desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + + if (cbvSrvUavRanges.Length > 0) { - if (cbvSrvUavRanges.Length > 0) + parameters.Add(new() { - parameters.Add(new() + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } + NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) + } + }); + } - if (samplerRanges.Length > 0) + if (samplerRanges.Length > 0) + { + parameters.Add(new() { - parameters.Add(new() + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } + NumDescriptorRanges = (uint)samplerRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) + } + }); } RootSignatureDesc rootSignatureDesc = new() diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index b3911bca..d68162a5 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -108,34 +108,34 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) // ResourceSlots { + desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); + List parameters = []; - if (desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + + if (cbvSrvUavRanges.Length > 0) { - if (cbvSrvUavRanges.Length > 0) + parameters.Add(new() { - parameters.Add(new() + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } + NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) + } + }); + } - if (samplerRanges.Length > 0) + if (samplerRanges.Length > 0) + { + parameters.Add(new() { - parameters.Add(new() + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } + NumDescriptorRanges = (uint)samplerRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) + } + }); } RootSignatureDesc rootSignatureDesc = new() diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 78c245e8..30394ca8 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -105,34 +105,34 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc // ResourceSlots { + desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); + List parameters = []; - if (desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + + if (cbvSrvUavRanges.Length > 0) { - if (cbvSrvUavRanges.Length > 0) + parameters.Add(new() { - parameters.Add(new() + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } + NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) + } + }); + } - if (samplerRanges.Length > 0) + if (samplerRanges.Length > 0) + { + parameters.Add(new() { - parameters.Add(new() + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } + NumDescriptorRanges = (uint)samplerRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) + } + }); } RootSignatureDesc rootSignatureDesc = new() diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index 7fb842db..3b4c9279 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -11,17 +11,16 @@ internal class DXResourceTable : ResourceTable public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - if (desc.Slots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) + desc.Slots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); + + if (cbvSrvUavRanges.Length > 0) { - if (cbvSrvUavRanges.Length > 0) - { - cbvSrvUavToken = Context.CbvSrvUavAllocator.Allocate((uint)cbvSrvUavRanges.Sum(static item => item.NumDescriptors)); - } + cbvSrvUavToken = Context.CbvSrvUavAllocator.Allocate((uint)cbvSrvUavRanges.Sum(static item => item.NumDescriptors)); + } - if (samplerRanges.Length > 0) - { - samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.NumDescriptors)); - } + if (samplerRanges.Length > 0) + { + samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.NumDescriptors)); } srvTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 0095e9c7..fbbf3abe 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -31,7 +31,7 @@ internal bool IsSuccess() extension(ResourceSlot[] resourceSlots) { - internal bool DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges) + internal void DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges) { List cbvSrvUavRangeList = []; List samplerRangeList = []; @@ -87,8 +87,6 @@ internal bool DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRan cbvSrvUavRanges = [.. cbvSrvUavRangeList]; samplerRanges = [.. samplerRangeList]; - - return cbvSrvUavRanges.Length > 0 || samplerRanges.Length > 0; } } diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index efbf2e95..ad6ebae5 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -45,6 +45,64 @@ internal void AddNext(out TNext next) } } + extension(ResourceSlot[] resourceSlots) + { + internal void Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptorCounts counts) + { + bindings = new DescriptorSetLayoutBinding[resourceSlots.Length]; + + uint uniformBufferCount = 0; + uint storageBufferCount = 0; + uint sampledImageCount = 0; + uint storageImageCount = 0; + uint samplerCount = 0; + uint accelerationStructureCount = 0; + + for (int i = 0; i < resourceSlots.Length; i++) + { + ResourceSlot resourceSlot = resourceSlots[i]; + + bindings[i] = new() + { + Binding = (uint)i, + DescriptorType = VKFormats.Vulkan(resourceSlot.Type), + DescriptorCount = resourceSlot.Count, + StageFlags = VkShaderStageFlags.All + }; + + switch (resourceSlot.Type) + { + case ResourceType.ConstantBuffer: + uniformBufferCount += resourceSlot.Count; + break; + + case ResourceType.StructuredBuffer: + case ResourceType.StructuredBufferReadWrite: + storageBufferCount += resourceSlot.Count; + break; + + case ResourceType.Texture: + sampledImageCount += resourceSlot.Count; + break; + + case ResourceType.TextureReadWrite: + storageImageCount += resourceSlot.Count; + break; + + case ResourceType.Sampler: + samplerCount += resourceSlot.Count; + break; + + case ResourceType.AccelerationStructure: + accelerationStructureCount += resourceSlot.Count; + break; + } + } + + counts = new(uniformBufferCount, storageBufferCount, sampledImageCount, storageImageCount, samplerCount, accelerationStructureCount); + } + } + extension(CommandBuffer commandBuffer) { internal VKCommandBuffer Vulkan() @@ -133,14 +191,6 @@ internal VKTopLevelAccelerationStructure Vulkan() } } - extension(ResourceLayout resourceLayout) - { - internal VKResourceLayout Vulkan() - { - return (VKResourceLayout)resourceLayout; - } - } - extension(ResourceTable resourceTable) { internal VKResourceTable Vulkan() diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index add6f6a1..53f20f19 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -18,16 +18,28 @@ public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : Stage = desc.Compute.Vulkan().GetPipelineShaderStageCreateInfo(scope) }; - // ResourceLayout + // ResourceSlots { + desc.ResourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out _); + + DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)desc.ResourceSlots.Length, + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + }; + + context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = desc.ResourceLayout is null ? 0u : 1u, - PSetLayouts = desc.ResourceLayout is null ? null : (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [desc.ResourceLayout.Vulkan().DescriptorSetLayout]) + SetLayoutCount = 1, + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); + context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); createInfo.Layout = PipelineLayout; } diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs b/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs index 33a3ccb1..73ee945b 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs @@ -7,36 +7,45 @@ internal unsafe class VKDescriptorAllocator(VKGraphicsContext context) : Graphic private readonly Lock @lock = new(); private readonly List available = []; - public VKDescriptorToken Allocate(VKResourceLayout resourceLayout) + public VKDescriptorToken Allocate(ResourceSlot[] resourceSlots) { using Lock.Scope _ = @lock.EnterScope(); - if (available.FirstOrDefault(item => item.CanAllocate(resourceLayout.Counts)) is not VKDescriptorPool pool) + using ZenithMarshal.Scope scope = new(); + + resourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptorCounts counts); + + if (available.FirstOrDefault(item => item.CanAllocate(counts)) is not VKDescriptorPool pool) { available.Add(pool = new(context)); } - fixed (DescriptorSetLayout* pSetLayouts = &resourceLayout.DescriptorSetLayout) + DescriptorSetLayoutCreateInfo createInfo = new() { - DescriptorSetAllocateInfo allocateInfo = new() - { - SType = StructureType.DescriptorSetAllocateInfo, - DescriptorPool = pool.Pool, - DescriptorSetCount = 1, - PSetLayouts = pSetLayouts - }; - - DescriptorSet set; - context.Vk.AllocateDescriptorSets(context.Device, &allocateInfo, &set).Success(); - - return new() { Pool = pool, Set = set }; - } + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)resourceSlots.Length, + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + }; + + context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + + DescriptorSetAllocateInfo allocateInfo = new() + { + SType = StructureType.DescriptorSetAllocateInfo, + DescriptorPool = pool.Pool, + DescriptorSetCount = 1, + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) + }; + + context.Vk.AllocateDescriptorSets(context.Device, &allocateInfo, out DescriptorSet set).Success(); + context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); + + return new() { Pool = pool, Set = set }; } public void Free(VKDescriptorToken token) { - DescriptorSet set = token.Set; - context.Vk.FreeDescriptorSets(context.Device, token.Pool.Pool, 1, &set).Success(); + context.Vk.FreeDescriptorSets(context.Device, token.Pool.Pool, 1, &token.Set).Success(); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 5fd94b41..938d8fbd 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -480,11 +480,6 @@ protected override Sampler CreateSamplerImpl(SamplerDesc desc) return new VKSampler(this, desc); } - protected override ResourceLayout CreateResourceLayoutImpl(ResourceLayoutDesc desc) - { - return new VKResourceLayout(this, desc); - } - protected override ResourceTable CreateResourceTableImpl(ResourceTableDesc desc) { return new VKResourceTable(this, desc); diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 536f9d1a..7887f8ee 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -148,16 +148,28 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) createInfo.PNext = &rendering; } - // ResourceLayout + // ResourceSlots { + desc.ResourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out _); + + DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)desc.ResourceSlots.Length, + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + }; + + context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = desc.ResourceLayout is null ? 0u : 1u, - PSetLayouts = desc.ResourceLayout is null ? null : (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [desc.ResourceLayout.Vulkan().DescriptorSetLayout]) + SetLayoutCount = 1, + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); + context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); createInfo.Layout = PipelineLayout; } diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 02b06649..0f319705 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -165,16 +165,28 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc createInfo.PStages = stages; } - // ResourceLayout + // ResourceSlots { + desc.ResourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out _); + + DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)desc.ResourceSlots.Length, + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + }; + + context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = desc.ResourceLayout is null ? 0u : 1u, - PSetLayouts = desc.ResourceLayout is null ? null : (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [desc.ResourceLayout.Vulkan().DescriptorSetLayout]) + SetLayoutCount = 1, + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); + context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); createInfo.Layout = PipelineLayout; } diff --git a/sources/Zenith.NET.Vulkan/VKResourceLayout.cs b/sources/Zenith.NET.Vulkan/VKResourceLayout.cs deleted file mode 100644 index 9f5129fd..00000000 --- a/sources/Zenith.NET.Vulkan/VKResourceLayout.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKResourceLayout : ResourceLayout -{ - public DescriptorSetLayout DescriptorSetLayout; - - public VKResourceLayout(VKGraphicsContext context, ResourceLayoutDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - DescriptorSetLayoutBinding* bindings = (DescriptorSetLayoutBinding*)ZenithMarshal.Allocate(scope, (uint)desc.Bindings.Length); - - uint uniformBufferCount = 0; - uint storageBufferCount = 0; - uint sampledImageCount = 0; - uint storageImageCount = 0; - uint samplerCount = 0; - uint accelerationStructureCount = 0; - - for (int i = 0; i < desc.Bindings.Length; i++) - { - ResourceBinding binding = desc.Bindings[i]; - - bindings[i] = new() - { - Binding = binding.Index, - DescriptorType = VKFormats.Vulkan(binding.Type), - DescriptorCount = binding.Count, - StageFlags = VKFormats.Vulkan(binding.StageFlags) - }; - - switch (binding.Type) - { - case ResourceType.ConstantBuffer: - uniformBufferCount += binding.Count; - break; - - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - storageBufferCount += binding.Count; - break; - - case ResourceType.Texture: - sampledImageCount += binding.Count; - break; - - case ResourceType.TextureReadWrite: - storageImageCount += binding.Count; - break; - - case ResourceType.Sampler: - samplerCount += binding.Count; - break; - - case ResourceType.AccelerationStructure: - accelerationStructureCount += binding.Count; - break; - } - } - - DescriptorSetLayoutCreateInfo createInfo = new() - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)desc.Bindings.Length, - PBindings = bindings - }; - - context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout).Success(); - - Counts = new(uniformBufferCount, storageBufferCount, sampledImageCount, storageImageCount, samplerCount, accelerationStructureCount); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKDescriptorCounts Counts { get; } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.DescriptorSetLayout, - ObjectHandle = DescriptorSetLayout.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index 1c1cd3a3..4f7660e1 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -2,144 +2,158 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKResourceTable : ResourceTable +internal unsafe class VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : ResourceTable(context, desc) { - public VKDescriptorToken DescriptorToken; + private readonly VKTextureView?[] srvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + private readonly VKTextureView?[] uavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; - public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); + public VKDescriptorToken DescriptorToken = context.DescriptorAllocator.Allocate(desc.Slots); - DescriptorToken = context.DescriptorAllocator.Allocate(desc.Layout.Vulkan()); + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - WriteDescriptorSet* descriptorWrites = (WriteDescriptorSet*)ZenithMarshal.Allocate(scope, (uint)desc.Layout.Desc.Bindings.Length); - WriteDescriptorSetAccelerationStructureKHR* accelerationStructureWrites = (WriteDescriptorSetAccelerationStructureKHR*)ZenithMarshal.Allocate(scope, (uint)desc.Layout.Desc.Bindings.Length); + protected override void SetImpl(uint slot, IBindableResource[] resources) + { + ResourceSlot resourceSlot = Desc.Slots[slot]; - uint resourceStartIndex = 0; - List srvTextureViews = []; - List uavTextureViews = []; + DescriptorBufferInfo[] bufferInfos = new DescriptorBufferInfo[resources.Length]; + DescriptorImageInfo[] imageInfos = new DescriptorImageInfo[resources.Length]; + AccelerationStructureKHR[] accelerationStructures = new AccelerationStructureKHR[resources.Length]; - for (int i = 0; i < desc.Layout.Desc.Bindings.Length; i++) + switch (resourceSlot.Type) { - ResourceBinding binding = desc.Layout.Desc.Bindings[i]; - - DescriptorImageInfo* imageInfos = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, binding.Count); - DescriptorBufferInfo* bufferInfos = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, binding.Count); - AccelerationStructureKHR* accelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, binding.Count); + case ResourceType.ConstantBuffer: + case ResourceType.StructuredBuffer: + case ResourceType.StructuredBufferReadWrite: + for (int i = 0; i < resources.Length; i++) + { + IBindableResource resource = resources[i]; + + if (resource is Buffer buffer) + { + bufferInfos[i] = buffer.Vulkan().View.BufferInfo; + } + else if (resource is BufferView bufferView) + { + bufferInfos[i] = bufferView.Vulkan().BufferInfo; + } + } + break; - descriptorWrites[i] = new() - { - SType = StructureType.WriteDescriptorSet, - DstSet = DescriptorToken.Set, - DstBinding = binding.Index, - DstArrayElement = 0, - DescriptorCount = binding.Count, - DescriptorType = VKFormats.Vulkan(binding.Type), - PImageInfo = imageInfos, - PBufferInfo = bufferInfos, - PNext = accelerationStructureWrites + i - }; - - accelerationStructureWrites[i] = new() - { - SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, - AccelerationStructureCount = binding.Count, - PAccelerationStructures = accelerationStructures - }; + case ResourceType.Texture: + { + uint index = (uint)Desc.Slots.Take((int)slot).Sum(static item => item.Count); - for (uint j = 0; j < binding.Count; j++) - { - IBindableResource resource = desc.Resources[(int)(resourceStartIndex + j)]; + for (int i = 0; i < resources.Length; i++) + { + IBindableResource resource = resources[i]; - switch (binding.Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - if (resource is Buffer buffer) + if (resource is Texture texture) { - bufferInfos[j] = buffer.Vulkan().View.BufferInfo; + imageInfos[i] = texture.Vulkan().View.SrvImageInfo; + + srvTextureViews[index + i] = texture.Vulkan().View; } - else if (resource is BufferView bufferView) + else if (resource is TextureView textureView) { - bufferInfos[j] = bufferView.Vulkan().BufferInfo; - } - break; + imageInfos[i] = textureView.Vulkan().SrvImageInfo; - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - if (binding.Type is ResourceType.Texture) - { - if (resource is Texture texture) - { - imageInfos[j] = texture.Vulkan().View.SrvImageInfo; - - srvTextureViews.Add(texture.Vulkan().View); - } - else if (resource is TextureView textureView) - { - imageInfos[j] = textureView.Vulkan().SrvImageInfo; - - srvTextureViews.Add(textureView.Vulkan()); - } + srvTextureViews[index + i] = textureView.Vulkan(); } - else if (resource is Texture texture) + } + } + break; + + case ResourceType.TextureReadWrite: + { + uint index = (uint)Desc.Slots.Take((int)slot).Sum(static item => item.Count); + + for (int i = 0; i < resources.Length; i++) + { + IBindableResource resource = resources[i]; + + if (resource is Texture texture) { - imageInfos[j] = texture.Vulkan().View.UavImageInfo; + imageInfos[i] = texture.Vulkan().View.UavImageInfo; - uavTextureViews.Add(texture.Vulkan().View); + uavTextureViews[index + i] = texture.Vulkan().View; } else if (resource is TextureView textureView) { - imageInfos[j] = textureView.Vulkan().UavImageInfo; + imageInfos[i] = textureView.Vulkan().UavImageInfo; - uavTextureViews.Add(textureView.Vulkan()); + uavTextureViews[index + i] = textureView.Vulkan(); } - break; - - case ResourceType.Sampler: - if (resource is Sampler sampler) - { - imageInfos[j] = new() { Sampler = sampler.Vulkan().Sampler }; - } - break; + } + } + break; - case ResourceType.AccelerationStructure: - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - accelerationStructures[j] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; - } - break; + case ResourceType.Sampler: + for (int i = 0; i < resources.Length; i++) + { + if (resources[i] is Sampler sampler) + { + imageInfos[i] = new() { Sampler = sampler.Vulkan().Sampler }; + } } - } + break; - resourceStartIndex += binding.Count; + case ResourceType.AccelerationStructure: + for (int i = 0; i < resources.Length; i++) + { + if (resources[i] is TopLevelAccelerationStructure topLevelAccelerationStructure) + { + accelerationStructures[i] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; + } + } + break; } - context.Vk.UpdateDescriptorSets(context.Device, (uint)desc.Layout.Desc.Bindings.Length, descriptorWrites, 0, (CopyDescriptorSet*)null); + fixed (DescriptorBufferInfo* pBufferInfo = bufferInfos) + { + fixed (DescriptorImageInfo* pImageInfo = imageInfos) + { + fixed (AccelerationStructureKHR* pAccelerationStructures = accelerationStructures) + { + WriteDescriptorSet descriptorWrite = new() + { + SType = StructureType.WriteDescriptorSet, + DstSet = DescriptorToken.Set, + DstBinding = slot, + DstArrayElement = 0, + DescriptorCount = (uint)resources.Length, + DescriptorType = VKFormats.Vulkan(resourceSlot.Type), + PBufferInfo = pBufferInfo, + PImageInfo = pImageInfo + }; + + if (resourceSlot.Type is ResourceType.AccelerationStructure) + { + WriteDescriptorSetAccelerationStructureKHR accelerationStructureWrite = new() + { + SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, + AccelerationStructureCount = (uint)resources.Length, + PAccelerationStructures = pAccelerationStructures + }; + } - SrvTextureViews = [.. srvTextureViews]; - UavTextureViews = [.. uavTextureViews]; + Context.Vk.UpdateDescriptorSets(Context.Device, 1, &descriptorWrite, 0, (CopyDescriptorSet*)null); + } + } + } } - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKTextureView[] SrvTextureViews { get; } - - public VKTextureView[] UavTextureViews { get; } - protected override void PreprocessImpl(CommandBuffer commandBuffer) { VKCommandBuffer vkCommandBuffer = commandBuffer.Vulkan(); - foreach (VKTextureView textureView in SrvTextureViews) + foreach (VKTextureView? textureView in srvTextureViews) { - textureView.TransitionLayout(vkCommandBuffer, ImageLayout.ShaderReadOnlyOptimal); + textureView?.TransitionLayout(vkCommandBuffer, ImageLayout.ShaderReadOnlyOptimal); } - foreach (VKTextureView textureView in UavTextureViews) + foreach (VKTextureView? textureView in uavTextureViews) { - textureView.TransitionLayout(vkCommandBuffer, ImageLayout.General); + textureView?.TransitionLayout(vkCommandBuffer, ImageLayout.General); } } From ae6295c449cde768dd831fcd24602464b27b14b0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 20:38:10 +0800 Subject: [PATCH 005/466] Refactor resource binding to use ResourceSlot arrays Removed ResourceLayout/ResourceLayoutDesc in favor of explicit ResourceSlot[]-based binding across all renderers. Eliminated BindingHelper and backend-specific binding logic. ResourceTable now uses a Write method for resource assignment. This simplifies and unifies resource binding, making it more explicit and less error-prone. --- .../CornellBox/Helpers/BindingHelper.cs | 89 ------------------- .../Renderers/PathTracingRenderer.cs | 42 +++++---- .../Renderers/RasterizationRenderer.cs | 27 +++--- .../ImGuiRenderer.cs | 65 ++++---------- sources/Zenith.NET/ResourceTable.cs | 2 +- 5 files changed, 50 insertions(+), 175 deletions(-) delete mode 100644 sources/Experiments/CornellBox/Helpers/BindingHelper.cs diff --git a/sources/Experiments/CornellBox/Helpers/BindingHelper.cs b/sources/Experiments/CornellBox/Helpers/BindingHelper.cs deleted file mode 100644 index bcfd50ef..00000000 --- a/sources/Experiments/CornellBox/Helpers/BindingHelper.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Zenith.NET; - -namespace CornellBox.Helpers; - -internal static class BindingHelper -{ - public static ResourceBinding[] Bindings(params ResourceBinding[] bindings) - { - switch (App.Context.Backend) - { - case Backend.DirectX12: - { - uint cbvIndex = 0; - uint srvIndex = 0; - uint uavIndex = 0; - uint samplerIndex = 0; - - for (int i = 0; i < bindings.Length; i++) - { - ref ResourceBinding binding = ref bindings[i]; - - binding = binding with - { - Index = binding.Type switch - { - ResourceType.ConstantBuffer => cbvIndex++, - - ResourceType.StructuredBuffer or - ResourceType.Texture or - ResourceType.AccelerationStructure => srvIndex++, - - ResourceType.StructuredBufferReadWrite or - ResourceType.TextureReadWrite => uavIndex++, - - ResourceType.Sampler => samplerIndex++, - - _ => binding.Index - } - }; - } - } - break; - - case Backend.Metal: - { - uint bufferIndex = 0; - uint textureIndex = 0; - uint samplerIndex = 0; - - for (int i = 0; i < bindings.Length; i++) - { - ref ResourceBinding binding = ref bindings[i]; - - binding = binding with - { - Index = binding.Type switch - { - ResourceType.ConstantBuffer or - ResourceType.StructuredBuffer or - ResourceType.StructuredBufferReadWrite or - ResourceType.AccelerationStructure => bufferIndex++, - - ResourceType.Texture or - ResourceType.TextureReadWrite => textureIndex++, - - ResourceType.Sampler => samplerIndex++, - - _ => binding.Index - } - }; - } - } - break; - - case Backend.Vulkan: - { - for (int i = 0; i < bindings.Length; i++) - { - ref ResourceBinding binding = ref bindings[i]; - - binding = binding with { Index = (uint)i }; - } - } - break; - } - - return bindings; - } -} diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index d75ec775..74e71f14 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -12,13 +12,23 @@ internal unsafe class PathTracingRenderer : Renderer { private const uint ThreadGroupSize = 16; + private readonly ResourceSlot[] resourceSlots = + [ + new() { Type = ResourceType.AccelerationStructure, Count = 1 }, + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 }, + new() { Type = ResourceType.TextureReadWrite, Count = 1 }, + new() { Type = ResourceType.TextureReadWrite, Count = 1 } + ]; + private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Buffer materialBuffer; private readonly Buffer cameraBuffer; private readonly BottomLevelAccelerationStructure blas; private readonly TopLevelAccelerationStructure tlas; - private readonly ResourceLayout resourceLayout; private readonly ComputePipeline pipeline; private Texture? accumulationTexture; @@ -106,26 +116,12 @@ public PathTracingRenderer() commandBuffer.Submit(waitForCompletion: true); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.AccelerationStructure, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.TextureReadWrite, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.TextureReadWrite, Count = 1, StageFlags = ShaderStageFlags.Compute } - ) - }); - using Shader computeShader = App.Context.LoadShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStageFlags.Compute); pipeline = App.Context.CreateComputePipeline(new() { Compute = computeShader, - ResourceLayout = resourceLayout, + ResourceSlots = resourceSlots, ThreadGroupSizeX = ThreadGroupSize, ThreadGroupSizeY = ThreadGroupSize, ThreadGroupSizeZ = 1 @@ -178,11 +174,14 @@ public override void Render(CommandBuffer commandBuffer) Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess }); - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [tlas, cameraBuffer, vertexBuffer, indexBuffer, materialBuffer, accumulationTexture, Color] - }); + resourceTable = App.Context.CreateResourceTable(new() { Slots = resourceSlots }); + resourceTable.Write(0, tlas); + resourceTable.Write(1, cameraBuffer); + resourceTable.Write(2, vertexBuffer); + resourceTable.Write(3, indexBuffer); + resourceTable.Write(4, materialBuffer); + resourceTable.Write(5, accumulationTexture); + resourceTable.Write(6, Color); } commandBuffer.SetPipeline(pipeline); @@ -214,7 +213,6 @@ public override void Dispose() accumulationTexture?.Dispose(); pipeline.Dispose(); - resourceLayout.Dispose(); tlas.Dispose(); blas.Dispose(); cameraBuffer.Dispose(); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index ce9a8cc4..70b07816 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -10,12 +10,17 @@ namespace CornellBox.Renderers; internal unsafe class RasterizationRenderer : Renderer { + private readonly ResourceSlot[] resourceSlots = + [ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 } + ]; + private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Buffer materialBuffer; private readonly Buffer constantBuffer; private readonly uint indexCount; - private readonly ResourceLayout resourceLayout; private readonly ResourceTable resourceTable; private readonly GraphicsPipeline pipeline; @@ -56,20 +61,9 @@ public RasterizationRenderer() Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite }); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Vertex | ShaderStageFlags.Pixel }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ) - }); - - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constantBuffer, materialBuffer] - }); + resourceTable = App.Context.CreateResourceTable(new() { Slots = resourceSlots }); + resourceTable.Write(0, constantBuffer); + resourceTable.Write(1, materialBuffer); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); @@ -88,7 +82,7 @@ public RasterizationRenderer() }, Vertex = vertexShader, Pixel = pixelShader, - ResourceLayout = resourceLayout, + ResourceSlots = resourceSlots, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = FrameBuffer.Output @@ -133,7 +127,6 @@ public override void Dispose() pipeline.Dispose(); resourceTable.Dispose(); - resourceLayout.Dispose(); constantBuffer.Dispose(); materialBuffer.Dispose(); indexBuffer.Dispose(); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 99b5f875..fa3e834f 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -72,16 +72,6 @@ float4 PSMain(VSOutput input) : SV_TARGET "44584243EE63AC19F937568890164F4A4BFF155601000000D4100000070000003C0000004C000000D800000014010000240200001C0900003809000053464930080000000000000000000000495347318400000003000000080000000000000068000000000000000100000003000000000000000F000000000000000000000074000000000000000000000003000000010000000303000000000000000000007D000000000000000000000003000000020000000F0F00000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200004F5347313400000001000000080000000000000028000000000000004000000003000000000000000F0000000000000053565F5461726765740000005053563008010000340000000000000000000000000000000000000000000000FFFFFFFF000000000301000301000000000000000000000000000000100000000200000018000000010000000000000000000000000000000E000000000000000300000000000000000000000000000002000000000000001800000000544558434F4F524400434F4C4F520050534D61696E000001000000000000001000000000000000000000000100440303040000010000000000000001014200030200000A00000000000000010244000302000000000000000000000100441003000000000000000000000000000000000000000F0000000F00000000000000000000000100000002000000040000000800000053544154F006000066000000BC0100004458494C0601000010000000D80600004243C0DE210C0000B30100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A32528848901420434688A500193242E4480E909122C4504151818CE183E58A0429460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000005000000032224809206485049322A484049322E384A19014124C8A8C0B84A44C107C23002500146600E608C0608E0029C620841442A61880105206A19B86CB9FB08790FC95905662F28BDB46C51863102AF70C973F610F21F921D00C0B8182551845181B630C42C8A076DB70F913F61092BF12924345029146CE43441342484820A4108CB047F0A0E1F227EC21247F25A40D6906441042CA1C41500A46249944070286118861A636180776088779980737A08572C0077AA8077928073920053EB0877218077A780779E0037360877708077A600330A0033F00033FD0033D688774808779F8057AC807782807149099C4601CD8211CE6611EDC8016CA011FE8A11EE4A11CE48014F8C01ECA611CE8E11DE4810FCC811DDE211CE8810DC0800EFC000CFC00091753BE499A224A987C16609E8588D80998081410DAE940E608400100000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000431E0808800000000000000000863C1410000100000000000000648100000011000000321E981419114C908C092647C604432225500C230045501265501E855030544AA20C0A6104A0080A847C01020222109F01A03E96C310000000CF0300814020A46700000079180000870000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304C2982010C7066120260804B241300C0A70731B06842026081AC684AE0C8FAE4EAEEC0B66824024130442D92018CD86C45016C31818C3D9103C1304CE623217D606C75626F705B3013122C9300603D8104C1B080800A80982006C00360CC6756D08B00DC360651384EEDA106C34A0A69AC2D2DCB84C597D41BDCDA5D1A5BDB94D108A678250401B0263825044138442DAB018DE0706612006831818630010A12AC21A7A7A92229A2014D304815836086770061B96A10CBE3108033318CC601803346031F4C4F424354120980DC219ACC1868551836F0CC2C00C063160C6800D360C6490066DC064CAEA8B2A4CEEAC8C6E8250501B16E30D3E3808833118C4C0180336D810C4C186C10DE400982078D506C1A0830D85D5CD41550734CCD8DEC2E8E6260844C322CD6D8E6E6E82403834E6D2CEBED8C868CCA59D7DCDD111A12BC3FB727B936BDBA0DC011EE4811EEC01C10778D00743153636BB369734B23237BA294150850CCFC5AE4C6E2EEDCD6D4A403421C373B10B63B32B939B121875C8F05CE6D0C2C8CAE49ADEC8CAD8A6044819323C17B9B2B9B73AB9B1B2B9290155890CCF852E0FAE2CC8CDED8D2E8C2EEDCD6D6E4A90D521C373B14B2BBB4B229BA20BA32B9B126C75C8F05CCADCE8E4F2A0DED2DCE8E6A60475D0850CCF65ECADCE8DAE4C6E6E4AD007000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF400000071200000190000000660A4AC09208D1150C3E53B8F0F348D3301131102CDB01056D00D97EF3CBE1011C0448440332CC41739CC863403D21816300D97EF3CFEE20083D83CD4E417B76D03D070F9CEE34B00F32C845FDCB60954C3E53B8F2F4D4E44A0D4F450935FDC36000000000000004841534814000000000000009F974238F30DDDF016CFF4F093C3E59C4458494C9407000066000000E50100004458494C06010000100000007C0700004243C0DE210C0000DC0100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A32528848901420434688A500193242E4480E909122C4504151818CE183E58A0429460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000005000000032224809206485049322A484049322E384A19014124C8A8C0B84A44C107C23002500146600E608C0608E0029C620841442A61880105206A19B86CB9FB08790FC95905662F28BDB46C51863102AF70C973F610F21F921D00C0B8182551845181B630C42C8A076DB70F913F61092BF12924345029146CE43441342484820A4108CB047F0A0E1F227EC21247F25A40D6906441042CA1C41500A46249944070286118861A636180776088779980737A08572C0077AA8077928073920053EB0877218077A780779E0037360877708077A600330A0033F00033FD0033D688774808779F8057AC807782807149099C4601CD8211CE6611EDC8016CA011FE8A11EE4A11CE48014F8C01ECA611CE8E11DE4810FCC811DDE211CE8810DC0800EFC000CFC00091753BE499A224A987C16609E8588D80998081410DAE940E608400100000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000431E0808800000000000000000863C1410000100000000000000648100000011000000321E981419114C908C092647C604432225500C4531025004255106E541A524CAA01046008AA040C81720202002F11900EA63390C010000F03C00100804427A060000000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304C2982010C7066120260804B241180C0A70731B0684202608443241D02E02130442992010CB0641713624CAC228CAD028CF86009A2070D5064491184519146043306D202200A026089DB521B026080240036AAA292CCD8DCB94D517D4DB5C1A5DDA9BDB04A1702608C5B32150260805344128A20D8BA26D5CE70D9EF20144A88AB0869E9EA488260885344120980DC2188CC186650883EDEBC4601083E1230316434F4C4F521304A2D9208CC1196C581A33D8BE4E0C06AFF9D060C300066590064CA6ACBEA8C2E4CECAE82608C5B46151D6606383EE1B3CE543830D411B6C18D4C00D800982476D101438D85060D91B547150858DCDAECD258DACCC8D6E4A105421C373B12B939B4B7B739B12104DC8F05CECC2D8ECCAE4A604461D323C9739B430B232B9A637B232B6290152860CCF45AE6CEEAD4E6EAC6C6E4A40D521C373B14B2BBB4B229BA20BA32B9B125875C8F05CCADCE8E4F2A0DED2DCE8E6A6047100000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF400000071200000190000000660A4AC09208D1150C3E53B8F0F348D3301131102CDB01056D00D97EF3CBE1011C0448440332CC41739CC863403D21816300D97EF3CFEE20083D83CD4E417B76D03D070F9CEE34B00F32C845FDCB60954C3E53B8F2F4D4E44A0D4F450935FDC3600000061200000490000001304412C100000000B00000034470088CC0014422994EC40C10E94244471141E9512A03787A07873085F427204600E22499287C60C0000002306080082604089C13278CF8801028020185063C00C1F34629000200806061A3C1F1860D088410280201818690081411824D1884102802018186A1085811864D2884102802018186B2089C11874D3884102802018186C309D011970D4884102802018186D40A1411934D588C101802018406B2021CD88C101802018406C3021CD88C103802018346E100901312C0B1AA001B68C2604C06882108C260CC268023118D1C8C788463E4634F231A291CF8841028020182078F0C5411CA40131629000200806081E7C711007D930629000200806081E7C71100768208C18240008820182075F1CC4C11A0408000000000000" ]; - private readonly ResourceLayoutDesc DxilDesc = new() - { - Bindings = - [ - new() { Type = ResourceType.ConstantBuffer, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Vertex }, - new() { Type = ResourceType.Texture, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Pixel }, - new() { Type = ResourceType.Sampler, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ] - }; - private readonly string[] Spirv = [ // Vertex Shader - Legacy @@ -94,16 +84,6 @@ float4 PSMain(VSOutput input) : SV_TARGET "0302230700060100000028001E0000000000000011000200010000000E00030000000000010000000F000A00040000000200000050534D61696E000011000000150000001D0000000D00000009000000100003000200000007000000030003000B000000010000000500050009000000696E7075742E436F6C6F7200050005000D000000696E7075742E55560000000005000400110000007465787475726500050004001500000073616D706C657200050006001800000073616D706C6564496D61676500000000050004001900000073616D706C656400050008001D000000656E747279506F696E74506172616D5F50534D61696E0000050004000200000050534D61696E000047000400090000001E00000001000000470004000D0000001E0000000000000047000400110000002100000001000000470004001100000022000000000000004700040015000000210000000200000047000400150000002200000000000000470004001D0000001E0000000000000013000200010000002100030003000000010000001600030005000000200000001700040006000000050000000400000020000400080000000100000006000000170004000A0000000500000002000000200004000C000000010000000A000000190009000E000000050000000100000002000000000000000000000001000000000000002000040010000000000000000E0000001A00020012000000200004001400000000000000120000001B000300170000000E000000200004001C00000003000000060000003B0004000800000009000000010000003B0004000C0000000D000000010000003B0004001000000011000000000000003B0004001400000015000000000000003B0004001C0000001D000000030000003600050001000000020000000000000003000000F8000200040000003D0004000600000007000000090000003D0004000A0000000B0000000D0000003D0004000E0000000F000000110000003D0004001200000013000000150000005600050017000000180000000F00000013000000570006000600000019000000180000000B0000000000000085000500060000001B00000007000000190000003E0003001D0000001B000000FD00010038000100" ]; - private readonly ResourceLayoutDesc SpirvDesc = new() - { - Bindings = - [ - new() { Type = ResourceType.ConstantBuffer, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Vertex }, - new() { Type = ResourceType.Texture, Index = 1, Count = 1, StageFlags = ShaderStageFlags.Pixel }, - new() { Type = ResourceType.Sampler, Index = 2, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ] - }; - private readonly string[] Metallib = [ // Vertex Shader - Legacy @@ -116,19 +96,15 @@ float4 PSMain(VSOutput input) : SV_TARGET "4D544C4201800200070000810E000000D81100000000000058000000000000008A00000000000000160100000000000008000000000000001E0100000000000008000000000000002601000000000000000E000000000000010000008A0000004E414D45070050534D61696E00545950450100014841534820008335EA1F82646DCCC623827DA6EBF0258B1E224AA4218D60D70CC077494E05494F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800000E00000000000052464C5408000400000000000000454E4454524C53541000260F000000000000B202000000000000555549441000C710A16106E43B598CE29DE59990D629454E445408000000454E445408000000454E4454DEC0170B0000000014000000EC0D0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000350300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C8000000892000001F0000003222480920624600212B249814212524981419270C85A4906052645C20246582609A01184620801B84610401404A9A224A98FC7F22AE898A88DF1EFE698C00184420028CA429A284C9FF2580791622FAA731026010C1108C214608E5109A23408E10D41C4130470006C30842639455CE608E01D0E80D048C0000000051180000670000001BF623F8FFFFFFFF015803C014003F002460022AA00F8870800778908777C0873630877A708771680373808736688770A0077400E8411EEAA11C00C21CE4211CDAA11CDA001EDE211DDC811ECA411E807060077600887648077768037628877308077668037B288771A08777908736B8877420077A4007200EE4000F80C11DDEA10DC4A11ED2811DE8211DDC611E00C21DDEA10DD2C11DCC611EDAC01CE0A10DDA211CE8011D007A90877A28078070877768037708077798873630077868837608077A4007801EE4A11ECA0120DCE11DDA601ED2E11CDCA11CC8A10DF4A11CE4E11DE6A10DCC011EDAA01DC2811ED001A00779A88772000877788736A0077908077880877470877368837608077A4007801EE4A11ECA0120E6811EC2611CD6A10DE0411EDE811ECA611CE8E11DE4A10DC4A11ECCC11CCA411EDA601ED2411FCA01C00380A80777988770308772680373808736688770A0077400E8411EEAA11C800D843000A4B0011AFEFFFFFF7F00DA005803C014003F0024A002FA60834104C002541B8C420016A0DAC018FFFFFFFF3F006D00AC01200115D00700491800000300000013844098300C44316130880213B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69A4AC0920CD900A301000000200000001000000000003185219DB0304800000002000000000006000121B048ADE0C000064810009000000321E981019114C908C092647C604436A255008E55004E5538002055120C5300250060000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A807789807000000007920000008010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A3680660604F0000004A63611BB44171506C1C194491C12092A33C06B11C8CA4388BA460C97224000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E72656E6465725F7461726765746169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D656F75747075745F306169722E667261676D656E745F696E7075747573657228544558434F4F5244296169722E63656E7465726169722E7065727370656374697665666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E706F736974696F6E6169722E6E6F5F7065727370656374697665506F736974696F6E5F306169722E6172675F756E757365646169722E746578747572656169722E6C6F636174696F6E5F696E6465786169722E73616D706C657465787475726532643C666C6F61742C2073616D706C653E746578747572655F316169722E73616D706C657273616D706C657273616D706C65725F31000026630000000000003082D0082308CD3082D01023084D3182D0182308CD3182D02023084D3282D0282308CD3282D03023080930C37006011ACC30A481A006330C6930ACC10C431A106B30C39006C51ACC30A481C106330C6970B4C10C431A206E304390CC30A8C11BC0C10C8412076700073304CB0C013343D0CC70387000070F14493304A130430207135559CF1561332467306595F540913643A2065BC53D50D479332871F081011C9C41183C62108DC10C091A900118C0C1193C651099C10CC52890422998C229CC30C88128A0C28D01C0711CC7711CC7711CE7066EE0066EE0066EE0066E60D1811E5896650A1C2BB0022BD8033CB0828C042628233636BB3697B637B23AB6321733B6B0B3B951103998033AA8033BB8033CC88354D8D8ECDA5CD2C8CADCE84609F42097B0343917BB32B9B9B437B751823D482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D94800F720A4B9373197B6B834B632BFB7A83A34B7B739B1B65E8033FF88364C2D2E45CCCE4C2CEDACADCE84609500100A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000310000001304412C1000000004000000C46A600480DC08008111001233000000F13000001C0000002247C890510E042B00000000188601006169722D616C6961732D73636F7065732850534D61696E296169722D616C6961732D73636F70652D73616D706C6572736169722D616C6961732D73636F70652D74657874757265732B8456508515832BB4C22AAC185EA115586183F00AAE00002306CD108260F05887A114032108CC68420060B088FF6C0311001B04C4000000020000005B06E07805000000000000007120000003000000320E1022840084060000000000000000650C00002500000012039428010000000300000021000000090000004C000000010000005800000000000000580000000200000088000000000000002A0000001C00000000000000060000000600000000000000880000000000000000000000020000000000000000000000060000000000000006000000FFFFFFFF00240000060000001B000000060000001B000000FFFFFFFF08240000000000005D0C000015000000120394A60000000050534D61696E6169722E73616D706C655F746578747572655F32642E763466333233323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000001000000AE020000524255469E02000000000000000000000000140000004149525200000A0018000400100014000A000000000000000400000000000000100000000400000001000000000000000700000008020000AC0100004801000008010000C000000054000000040000001CFEFFFF010004001400000000000E0014000000040008000C0010000E000000000000000100000018000000040000000900000073616D706C65725F310000000700000073616D706C65720068FEFFFF0200040018000000000012001800000008000C00070000001000140012000000000000040000000001000000180000000400000009000000746578747572655F31000000180000007465787475726532643C666C6F61742C2073616D706C653E00000000C4FFFFFF02300400140000001000100000000000000008000C000700100000000000000100010000040000000A000000506F736974696F6E5F30000008000C0004000800080000000030040004000000C2FFFFFF0000010118000000C80000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290050FFFFFF0030040018000000000012001400000008000000060007000C00100012000000000001012400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000B0FFFFFF0010020018000000000012001400000004000800000000000C0010001200000000000000000000001800000004000000080000006F75747075745F300000000006000000666C6F617434000008000E000400080008000000010000001000000000000A001000040008000C000A0000002C000000200000000400000005000000020000000300000004000000050000000600000001000000010000000600000050534D61696E0000454E4454" ]; - private readonly ResourceLayoutDesc MetallibDesc = new() - { - Bindings = - [ - new() { Type = ResourceType.ConstantBuffer, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Vertex }, - new() { Type = ResourceType.Texture, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Pixel }, - new() { Type = ResourceType.Sampler, Index = 0, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ] - }; + private readonly ResourceSlot[] ResourceSlots = + [ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } + ]; private readonly Buffer constants; private readonly Sampler sampler; - private readonly ResourceLayout resourceLayout; private readonly GraphicsPipeline graphicsPipeline; private readonly Dictionary textureBindings = []; private readonly Dictionary textureViewBindings = []; @@ -142,25 +118,21 @@ public ImGuiRenderer(GraphicsContext context, Output output, ImGuiColorSpace col { byte[] vertexShaderBytes = []; byte[] pixelShaderBytes = []; - ResourceLayoutDesc resourceLayoutDesc = default; switch (context.Backend) { case Backend.DirectX12: vertexShaderBytes = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Dxil[0] : Dxil[1]); pixelShaderBytes = Convert.FromHexString(Dxil[2]); - resourceLayoutDesc = DxilDesc; break; case Backend.Vulkan: vertexShaderBytes = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Spirv[0] : Spirv[1]); pixelShaderBytes = Convert.FromHexString(Spirv[2]); - resourceLayoutDesc = SpirvDesc; break; case Backend.Metal: vertexShaderBytes = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Metallib[0] : Metallib[1]); pixelShaderBytes = Convert.FromHexString(Metallib[2]); - resourceLayoutDesc = MetallibDesc; break; } @@ -208,7 +180,7 @@ public ImGuiRenderer(GraphicsContext context, Output output, ImGuiColorSpace col }, Vertex = vertex, Pixel = pixel, - ResourceLayout = resourceLayout = context.CreateResourceLayout(resourceLayoutDesc), + ResourceSlots = ResourceSlots, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = output @@ -229,11 +201,12 @@ public ImTextureID Binding(Texture texture) id++; } - resourceTableBindings[textureBindings[texture] = textureID = id] = Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constants, texture, sampler] - }); + ResourceTable resourceTable = Context.CreateResourceTable(new() { Slots = ResourceSlots }); + resourceTable.Write(0, constants); + resourceTable.Write(1, texture); + resourceTable.Write(2, sampler); + + resourceTableBindings[textureBindings[texture] = textureID = id] = resourceTable; } return textureID; @@ -249,11 +222,12 @@ public ImTextureID Binding(TextureView textureView) id++; } - resourceTableBindings[textureViewBindings[textureView] = textureID = id] = Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constants, textureView, sampler] - }); + ResourceTable resourceTable = Context.CreateResourceTable(new() { Slots = ResourceSlots }); + resourceTable.Write(0, constants); + resourceTable.Write(1, textureView); + resourceTable.Write(2, sampler); + + resourceTableBindings[textureViewBindings[textureView] = textureID = id] = resourceTable; } return textureID; @@ -490,7 +464,6 @@ protected override void Destroy() textureViewBindings.Clear(); textureBindings.Clear(); graphicsPipeline.Dispose(); - resourceLayout.Dispose(); sampler.Dispose(); constants.Dispose(); } diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs index 73f34c06..f2a62f9f 100644 --- a/sources/Zenith.NET/ResourceTable.cs +++ b/sources/Zenith.NET/ResourceTable.cs @@ -6,7 +6,7 @@ public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc d public ref readonly ResourceTableDesc Desc => ref desc; - public void Set(uint slot, params IBindableResource[] resources) + public void Write(uint slot, params IBindableResource[] resources) { if (slot >= desc.Slots.Length || resources.Length is 0 || resources.Length > desc.Slots[slot].Count) { From a58d5f95c9d7677a9f1019fb47fa2af529786881 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 20:54:19 +0800 Subject: [PATCH 006/466] Refactor DescriptorSetLayout lifetime management DescriptorSetLayouts are now owned by pipelines and resource tables, and are destroyed only when these objects are disposed, rather than immediately after use. VKDescriptorAllocator.Allocate now takes a DescriptorSetLayout directly, avoiding redundant creation and destruction. This improves resource management and enables DescriptorSetLayouts to be reused or referenced as needed. --- .../Zenith.NET.Vulkan/VKComputePipeline.cs | 8 +++-- .../VKDescriptorAllocator.cs | 14 +------- .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 8 +++-- .../VKMeshShadingPipeline.cs | 8 +++-- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 34 ++++++++++++++++--- 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index 53f20f19..4f16ccc1 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -4,6 +4,8 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKComputePipeline : ComputePipeline { + public DescriptorSetLayout DescriptorSetLayout; + public PipelineLayout PipelineLayout; public VkPipeline Pipeline; @@ -29,17 +31,16 @@ public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) }; - context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); createInfo.Layout = PipelineLayout; } @@ -68,5 +69,6 @@ protected override void Destroy() { Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); + Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); } } diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs b/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs index 73ee945b..4f407f74 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs @@ -7,28 +7,17 @@ internal unsafe class VKDescriptorAllocator(VKGraphicsContext context) : Graphic private readonly Lock @lock = new(); private readonly List available = []; - public VKDescriptorToken Allocate(ResourceSlot[] resourceSlots) + public VKDescriptorToken Allocate(DescriptorSetLayout descriptorSetLayout, VKDescriptorCounts counts) { using Lock.Scope _ = @lock.EnterScope(); using ZenithMarshal.Scope scope = new(); - resourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptorCounts counts); - if (available.FirstOrDefault(item => item.CanAllocate(counts)) is not VKDescriptorPool pool) { available.Add(pool = new(context)); } - DescriptorSetLayoutCreateInfo createInfo = new() - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)resourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) - }; - - context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); - DescriptorSetAllocateInfo allocateInfo = new() { SType = StructureType.DescriptorSetAllocateInfo, @@ -38,7 +27,6 @@ public VKDescriptorToken Allocate(ResourceSlot[] resourceSlots) }; context.Vk.AllocateDescriptorSets(context.Device, &allocateInfo, out DescriptorSet set).Success(); - context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); return new() { Pool = pool, Set = set }; } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 7887f8ee..c8fea0e2 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -4,6 +4,8 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKGraphicsPipeline : GraphicsPipeline { + public DescriptorSetLayout DescriptorSetLayout; + public PipelineLayout PipelineLayout; public VkPipeline Pipeline; @@ -159,17 +161,16 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) }; - context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); createInfo.Layout = PipelineLayout; } @@ -264,5 +265,6 @@ protected override void Destroy() { Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); + Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); } } diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 0f319705..c43088c4 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -4,6 +4,8 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKMeshShadingPipeline : MeshShadingPipeline { + public DescriptorSetLayout DescriptorSetLayout; + public PipelineLayout PipelineLayout; public VkPipeline Pipeline; @@ -176,17 +178,16 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) }; - context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); + context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - context.Vk.DestroyDescriptorSetLayout(context.Device, descriptorSetLayout, null); createInfo.Layout = PipelineLayout; } @@ -235,5 +236,6 @@ protected override void Destroy() { Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); + Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); } } diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index 4f7660e1..8bdf7a55 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -2,12 +2,35 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : ResourceTable(context, desc) +internal unsafe class VKResourceTable : ResourceTable { - private readonly VKTextureView?[] srvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; - private readonly VKTextureView?[] uavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + private readonly VKTextureView?[] srvTextureViews; + private readonly VKTextureView?[] uavTextureViews; - public VKDescriptorToken DescriptorToken = context.DescriptorAllocator.Allocate(desc.Slots); + public DescriptorSetLayout DescriptorSetLayout; + + public VKDescriptorToken DescriptorToken; + + public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) + { + using ZenithMarshal.Scope scope = new(); + + desc.Slots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptorCounts counts); + + DescriptorSetLayoutCreateInfo createInfo = new() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)desc.Slots.Length, + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + }; + + context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout).Success(); + + DescriptorToken = context.DescriptorAllocator.Allocate(DescriptorSetLayout, counts); + + srvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + uavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; @@ -134,6 +157,8 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) AccelerationStructureCount = (uint)resources.Length, PAccelerationStructures = pAccelerationStructures }; + + descriptorWrite.PNext = &accelerationStructureWrite; } Context.Vk.UpdateDescriptorSets(Context.Device, 1, &descriptorWrite, 0, (CopyDescriptorSet*)null); @@ -175,5 +200,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { Context.DescriptorAllocator.Free(DescriptorToken); + Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); } } \ No newline at end of file From 4354740fd8388141c1654620731963353ff3d917 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Apr 2026 21:04:40 +0800 Subject: [PATCH 007/466] Update Avalonia to 12.0.0 and fix DPI retrieval method Upgraded Avalonia from 11.3.13 to 12.0.0. Updated ZenithView.cs to use TopLevel.GetTopLevel(this)?.RenderScaling for DPI, replacing VisualRoot?.RenderScaling for improved compatibility with Avalonia 12. --- sources/Directory.Packages.props | 2 +- sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 8a77753e..5e778434 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,7 +5,7 @@ - + diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index c8a9b38b..0e97d32d 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs @@ -55,7 +55,7 @@ public override void Render(DrawingContext context) Typeface typeface = new(FontFamily, FontStyle, FontWeight, FontStretch); double fontSize = Math.Clamp(Bounds.Height / 15.0, 14.0, 48.0); - double dpi = VisualRoot?.RenderScaling ?? 1.0; + double dpi = TopLevel.GetTopLevel(this)?.RenderScaling ?? 1.0; FormattedText shadowText = new("ZenithView", CultureInfo.CurrentCulture, From fb6ffc26af2794928f209b0bdc40e42b794b4030 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 14:55:11 +0800 Subject: [PATCH 008/466] Refactor to use Vulkan push descriptors for resources Replaces traditional descriptor pool/allocation logic with push descriptors. Removes VKDescriptorPool, VKDescriptorAllocator, VKDescriptorToken, and VKDescriptorCounts. Descriptor set layouts now use PushDescriptorBit, and resource binding is performed via CmdPushDescriptorSet. VKResourceTable is rewritten to manage WriteDescriptorSet arrays directly. Simplifies resource binding, improves performance, and reduces complexity, but requires push descriptor support from the Vulkan implementation. --- sources/Zenith.NET.Vulkan/Extensions.cs | 41 +---- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 2 +- .../Zenith.NET.Vulkan/VKComputePipeline.cs | 5 +- .../VKDescriptorAllocator.cs | 51 ------- .../Zenith.NET.Vulkan/VKDescriptorCounts.cs | 8 - sources/Zenith.NET.Vulkan/VKDescriptorPool.cs | 124 --------------- .../Zenith.NET.Vulkan/VKDescriptorToken.cs | 10 -- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 4 - .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 5 +- .../VKMeshShadingPipeline.cs | 5 +- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 142 ++++++++---------- 11 files changed, 70 insertions(+), 327 deletions(-) delete mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorCounts.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorPool.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorToken.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index ad6ebae5..85ff077c 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -47,16 +47,9 @@ internal void AddNext(out TNext next) extension(ResourceSlot[] resourceSlots) { - internal void Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptorCounts counts) + internal DescriptorSetLayoutBinding[] Vulkan() { - bindings = new DescriptorSetLayoutBinding[resourceSlots.Length]; - - uint uniformBufferCount = 0; - uint storageBufferCount = 0; - uint sampledImageCount = 0; - uint storageImageCount = 0; - uint samplerCount = 0; - uint accelerationStructureCount = 0; + DescriptorSetLayoutBinding[] bindings = new DescriptorSetLayoutBinding[resourceSlots.Length]; for (int i = 0; i < resourceSlots.Length; i++) { @@ -69,37 +62,9 @@ internal void Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptor DescriptorCount = resourceSlot.Count, StageFlags = VkShaderStageFlags.All }; - - switch (resourceSlot.Type) - { - case ResourceType.ConstantBuffer: - uniformBufferCount += resourceSlot.Count; - break; - - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - storageBufferCount += resourceSlot.Count; - break; - - case ResourceType.Texture: - sampledImageCount += resourceSlot.Count; - break; - - case ResourceType.TextureReadWrite: - storageImageCount += resourceSlot.Count; - break; - - case ResourceType.Sampler: - samplerCount += resourceSlot.Count; - break; - - case ResourceType.AccelerationStructure: - accelerationStructureCount += resourceSlot.Count; - break; - } } - counts = new(uniformBufferCount, storageBufferCount, sampledImageCount, storageImageCount, samplerCount, accelerationStructureCount); + return bindings; } } diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 01001e66..d92c2d99 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -382,7 +382,7 @@ protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable re _ => (PipelineBindPoint.Graphics, default) }; - Context.Vk.CmdBindDescriptorSets(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, 1, ref resourceTable.Vulkan().DescriptorToken.Set, 0, null); + Context.Vk.CmdPushDescriptorSet(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, (uint)resourceTable.Desc.Slots.Length, resourceTable.Vulkan().DescriptorSets); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index 4f16ccc1..4d68e676 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -22,13 +22,12 @@ public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : // ResourceSlots { - desc.ResourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out _); - DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() { SType = StructureType.DescriptorSetLayoutCreateInfo, BindingCount = (uint)desc.ResourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, desc.ResourceSlots.Vulkan()), + Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit }; context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs b/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs deleted file mode 100644 index 4f407f74..00000000 --- a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKDescriptorAllocator(VKGraphicsContext context) : GraphicsResource(context) -{ - private readonly Lock @lock = new(); - private readonly List available = []; - - public VKDescriptorToken Allocate(DescriptorSetLayout descriptorSetLayout, VKDescriptorCounts counts) - { - using Lock.Scope _ = @lock.EnterScope(); - - using ZenithMarshal.Scope scope = new(); - - if (available.FirstOrDefault(item => item.CanAllocate(counts)) is not VKDescriptorPool pool) - { - available.Add(pool = new(context)); - } - - DescriptorSetAllocateInfo allocateInfo = new() - { - SType = StructureType.DescriptorSetAllocateInfo, - DescriptorPool = pool.Pool, - DescriptorSetCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [descriptorSetLayout]) - }; - - context.Vk.AllocateDescriptorSets(context.Device, &allocateInfo, out DescriptorSet set).Success(); - - return new() { Pool = pool, Set = set }; - } - - public void Free(VKDescriptorToken token) - { - context.Vk.FreeDescriptorSets(context.Device, token.Pool.Pool, 1, &token.Set).Success(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - foreach (VKDescriptorPool pool in available) - { - pool.Dispose(); - } - available.Clear(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorCounts.cs b/sources/Zenith.NET.Vulkan/VKDescriptorCounts.cs deleted file mode 100644 index e8efbf4a..00000000 --- a/sources/Zenith.NET.Vulkan/VKDescriptorCounts.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET.Vulkan; - -internal readonly record struct VKDescriptorCounts(uint UniformBufferCount, - uint StorageBufferCount, - uint SampledImageCount, - uint StorageImageCount, - uint SamplerCount, - uint AccelerationStructureCount); diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorPool.cs b/sources/Zenith.NET.Vulkan/VKDescriptorPool.cs deleted file mode 100644 index 5e9b9b3c..00000000 --- a/sources/Zenith.NET.Vulkan/VKDescriptorPool.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKDescriptorPool : GraphicsResource -{ - private const uint MaxSets = 100; - private const uint DescriptorCount = 1000; - - public DescriptorPool Pool; - - private uint remainingSets = MaxSets; - private uint uniformBufferCount = DescriptorCount; - private uint storageBufferCount = DescriptorCount; - private uint sampledImageCount = DescriptorCount; - private uint storageImageCount = DescriptorCount; - private uint samplerCount = DescriptorCount; - private uint accelerationStructureCount = DescriptorCount; - - public VKDescriptorPool(VKGraphicsContext context) : base(context) - { - DescriptorPoolSize[] poolSizes = new DescriptorPoolSize[context.Capabilities.RayTracingSupported ? 8 : 7]; - - poolSizes[0] = new() - { - Type = DescriptorType.UniformBuffer, - DescriptorCount = DescriptorCount - }; - - poolSizes[1] = new() - { - Type = DescriptorType.UniformBufferDynamic, - DescriptorCount = DescriptorCount - }; - - poolSizes[2] = new() - { - Type = DescriptorType.StorageBuffer, - DescriptorCount = DescriptorCount - }; - - poolSizes[3] = new() - { - Type = DescriptorType.StorageBufferDynamic, - DescriptorCount = DescriptorCount - }; - - poolSizes[4] = new() - { - Type = DescriptorType.SampledImage, - DescriptorCount = DescriptorCount - }; - - poolSizes[5] = new() - { - Type = DescriptorType.StorageImage, - DescriptorCount = DescriptorCount - }; - - poolSizes[6] = new() - { - Type = DescriptorType.Sampler, - DescriptorCount = DescriptorCount - }; - - if (context.Capabilities.RayTracingSupported) - { - poolSizes[7] = new() - { - Type = DescriptorType.AccelerationStructureKhr, - DescriptorCount = DescriptorCount - }; - } - - fixed (DescriptorPoolSize* pPoolSizes = poolSizes) - { - DescriptorPoolCreateInfo createInfo = new() - { - SType = StructureType.DescriptorPoolCreateInfo, - Flags = DescriptorPoolCreateFlags.FreeDescriptorSetBit, - MaxSets = MaxSets, - PoolSizeCount = (uint)poolSizes.Length, - PPoolSizes = pPoolSizes - }; - - context.Vk.CreateDescriptorPool(context.Device, &createInfo, null, out Pool).Success(); - } - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public bool CanAllocate(VKDescriptorCounts counts) - { - if (remainingSets < 1 - || uniformBufferCount < counts.UniformBufferCount - || storageBufferCount < counts.StorageBufferCount - || sampledImageCount < counts.SampledImageCount - || storageImageCount < counts.StorageImageCount - || samplerCount < counts.SamplerCount - || accelerationStructureCount < counts.AccelerationStructureCount) - { - return false; - } - - remainingSets--; - uniformBufferCount -= counts.UniformBufferCount; - storageBufferCount -= counts.StorageBufferCount; - sampledImageCount -= counts.SampledImageCount; - storageImageCount -= counts.StorageImageCount; - samplerCount -= counts.SamplerCount; - accelerationStructureCount -= counts.AccelerationStructureCount; - - return true; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Context.Vk.DestroyDescriptorPool(Context.Device, Pool, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs b/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs deleted file mode 100644 index 498f0fe8..00000000 --- a/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal record struct VKDescriptorToken -{ - public VKDescriptorPool Pool; - - public DescriptorSet Set; -} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 938d8fbd..3c09f733 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -48,8 +48,6 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public Vk Vk { get; } = Vk.GetApi(); - public VKDescriptorAllocator DescriptorAllocator => field ??= new(this); - public ExtDebugUtils? DebugUtils { get; private set; } public KhrSurface? Surface { get; private set; } @@ -511,8 +509,6 @@ protected override void Destroy() base.Destroy(); - DescriptorAllocator.Dispose(); - Vk.DestroyDevice(Device, null); Vk.DestroyInstance(Instance, null); diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index c8fea0e2..651e82c1 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -152,13 +152,12 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) // ResourceSlots { - desc.ResourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out _); - DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() { SType = StructureType.DescriptorSetLayoutCreateInfo, BindingCount = (uint)desc.ResourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, desc.ResourceSlots.Vulkan()), + Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit }; context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index c43088c4..25f6a11f 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -169,13 +169,12 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc // ResourceSlots { - desc.ResourceSlots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out _); - DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() { SType = StructureType.DescriptorSetLayoutCreateInfo, BindingCount = (uint)desc.ResourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, desc.ResourceSlots.Vulkan()), + Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit }; context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index 8bdf7a55..de3f11ae 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -4,43 +4,68 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKResourceTable : ResourceTable { - private readonly VKTextureView?[] srvTextureViews; - private readonly VKTextureView?[] uavTextureViews; + private readonly ZenithMarshal.Scope scope = new(); - public DescriptorSetLayout DescriptorSetLayout; - - public VKDescriptorToken DescriptorToken; + public WriteDescriptorSet* DescriptorSets; public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - using ZenithMarshal.Scope scope = new(); - - desc.Slots.Vulkan(out DescriptorSetLayoutBinding[] bindings, out VKDescriptorCounts counts); + WriteDescriptorSet[] descriptorSets = new WriteDescriptorSet[desc.Slots.Length]; - DescriptorSetLayoutCreateInfo createInfo = new() + for (int i = 0; i < descriptorSets.Length; i++) { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)desc.Slots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, bindings) - }; + ResourceSlot resourceSlot = desc.Slots[i]; - context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout).Success(); + descriptorSets[i] = new() + { + SType = StructureType.WriteDescriptorSet, + DstBinding = (uint)i, + DescriptorCount = resourceSlot.Count, + DescriptorType = VKFormats.Vulkan(resourceSlot.Type) + }; - DescriptorToken = context.DescriptorAllocator.Allocate(DescriptorSetLayout, counts); + switch (resourceSlot.Type) + { + case ResourceType.ConstantBuffer: + case ResourceType.StructuredBuffer: + case ResourceType.StructuredBufferReadWrite: + descriptorSets[i].PBufferInfo = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); + break; + + case ResourceType.Texture: + case ResourceType.TextureReadWrite: + case ResourceType.Sampler: + descriptorSets[i].PImageInfo = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); + break; + + case ResourceType.AccelerationStructure: + descriptorSets[i].PNext = (WriteDescriptorSetAccelerationStructureKHR*)ZenithMarshal.AllocateAndFill(scope, [new() + { + SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, + AccelerationStructureCount = resourceSlot.Count, + PAccelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, resourceSlot.Count) + }]); + break; + } + } - srvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; - uavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + DescriptorSets = (WriteDescriptorSet*)ZenithMarshal.AllocateAndFill(scope, descriptorSets); + + SrvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + UavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + public VKTextureView?[] SrvTextureViews { get; } + + public VKTextureView?[] UavTextureViews { get; } + protected override void SetImpl(uint slot, IBindableResource[] resources) { ResourceSlot resourceSlot = Desc.Slots[slot]; - DescriptorBufferInfo[] bufferInfos = new DescriptorBufferInfo[resources.Length]; - DescriptorImageInfo[] imageInfos = new DescriptorImageInfo[resources.Length]; - AccelerationStructureKHR[] accelerationStructures = new AccelerationStructureKHR[resources.Length]; + ref WriteDescriptorSet descriptorSet = ref DescriptorSets[slot]; switch (resourceSlot.Type) { @@ -53,11 +78,11 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) if (resource is Buffer buffer) { - bufferInfos[i] = buffer.Vulkan().View.BufferInfo; + descriptorSet.PBufferInfo[i] = buffer.Vulkan().View.BufferInfo; } else if (resource is BufferView bufferView) { - bufferInfos[i] = bufferView.Vulkan().BufferInfo; + descriptorSet.PBufferInfo[i] = bufferView.Vulkan().BufferInfo; } } break; @@ -72,15 +97,15 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) if (resource is Texture texture) { - imageInfos[i] = texture.Vulkan().View.SrvImageInfo; + descriptorSet.PImageInfo[i] = texture.Vulkan().View.SrvImageInfo; - srvTextureViews[index + i] = texture.Vulkan().View; + SrvTextureViews[index + i] = texture.Vulkan().View; } else if (resource is TextureView textureView) { - imageInfos[i] = textureView.Vulkan().SrvImageInfo; + descriptorSet.PImageInfo[i] = textureView.Vulkan().SrvImageInfo; - srvTextureViews[index + i] = textureView.Vulkan(); + SrvTextureViews[index + i] = textureView.Vulkan(); } } } @@ -96,15 +121,15 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) if (resource is Texture texture) { - imageInfos[i] = texture.Vulkan().View.UavImageInfo; + descriptorSet.PImageInfo[i] = texture.Vulkan().View.UavImageInfo; - uavTextureViews[index + i] = texture.Vulkan().View; + UavTextureViews[index + i] = texture.Vulkan().View; } else if (resource is TextureView textureView) { - imageInfos[i] = textureView.Vulkan().UavImageInfo; + descriptorSet.PImageInfo[i] = textureView.Vulkan().UavImageInfo; - uavTextureViews[index + i] = textureView.Vulkan(); + UavTextureViews[index + i] = textureView.Vulkan(); } } } @@ -115,7 +140,7 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { if (resources[i] is Sampler sampler) { - imageInfos[i] = new() { Sampler = sampler.Vulkan().Sampler }; + descriptorSet.PImageInfo[i] = new() { Sampler = sampler.Vulkan().Sampler }; } } break; @@ -125,58 +150,23 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { if (resources[i] is TopLevelAccelerationStructure topLevelAccelerationStructure) { - accelerationStructures[i] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; + ((WriteDescriptorSetAccelerationStructureKHR*)descriptorSet.PNext)->PAccelerationStructures[i] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; } } break; } - - fixed (DescriptorBufferInfo* pBufferInfo = bufferInfos) - { - fixed (DescriptorImageInfo* pImageInfo = imageInfos) - { - fixed (AccelerationStructureKHR* pAccelerationStructures = accelerationStructures) - { - WriteDescriptorSet descriptorWrite = new() - { - SType = StructureType.WriteDescriptorSet, - DstSet = DescriptorToken.Set, - DstBinding = slot, - DstArrayElement = 0, - DescriptorCount = (uint)resources.Length, - DescriptorType = VKFormats.Vulkan(resourceSlot.Type), - PBufferInfo = pBufferInfo, - PImageInfo = pImageInfo - }; - - if (resourceSlot.Type is ResourceType.AccelerationStructure) - { - WriteDescriptorSetAccelerationStructureKHR accelerationStructureWrite = new() - { - SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, - AccelerationStructureCount = (uint)resources.Length, - PAccelerationStructures = pAccelerationStructures - }; - - descriptorWrite.PNext = &accelerationStructureWrite; - } - - Context.Vk.UpdateDescriptorSets(Context.Device, 1, &descriptorWrite, 0, (CopyDescriptorSet*)null); - } - } - } } protected override void PreprocessImpl(CommandBuffer commandBuffer) { VKCommandBuffer vkCommandBuffer = commandBuffer.Vulkan(); - foreach (VKTextureView? textureView in srvTextureViews) + foreach (VKTextureView? textureView in SrvTextureViews) { textureView?.TransitionLayout(vkCommandBuffer, ImageLayout.ShaderReadOnlyOptimal); } - foreach (VKTextureView? textureView in uavTextureViews) + foreach (VKTextureView? textureView in UavTextureViews) { textureView?.TransitionLayout(vkCommandBuffer, ImageLayout.General); } @@ -184,22 +174,10 @@ protected override void PreprocessImpl(CommandBuffer commandBuffer) protected override void SetResourceName(string name) { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.DescriptorSet, - ObjectHandle = DescriptorToken.Set.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } protected override void Destroy() { - Context.DescriptorAllocator.Free(DescriptorToken); - Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); + scope.Dispose(); } } \ No newline at end of file From 5e325cab0f2e2f7649bb6f5aad9ca27385bc202f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 16:52:01 +0800 Subject: [PATCH 009/466] Rename DescriptorSets to Sets in VKResourceTable Refactored VKResourceTable to rename the DescriptorSets field to Sets for improved clarity and consistency. Updated all related variable names, allocations, and usages accordingly. No functional changes were made. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 2 +- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 31 +++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index d92c2d99..0f9521d7 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -382,7 +382,7 @@ protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable re _ => (PipelineBindPoint.Graphics, default) }; - Context.Vk.CmdPushDescriptorSet(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, (uint)resourceTable.Desc.Slots.Length, resourceTable.Vulkan().DescriptorSets); + Context.Vk.CmdPushDescriptorSet(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, (uint)resourceTable.Desc.Slots.Length, resourceTable.Vulkan().Sets); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index de3f11ae..1da00443 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -6,17 +6,17 @@ internal unsafe class VKResourceTable : ResourceTable { private readonly ZenithMarshal.Scope scope = new(); - public WriteDescriptorSet* DescriptorSets; + public WriteDescriptorSet* Sets; public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - WriteDescriptorSet[] descriptorSets = new WriteDescriptorSet[desc.Slots.Length]; + WriteDescriptorSet[] sets = new WriteDescriptorSet[desc.Slots.Length]; - for (int i = 0; i < descriptorSets.Length; i++) + for (int i = 0; i < sets.Length; i++) { ResourceSlot resourceSlot = desc.Slots[i]; - descriptorSets[i] = new() + sets[i] = new() { SType = StructureType.WriteDescriptorSet, DstBinding = (uint)i, @@ -29,27 +29,30 @@ public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base case ResourceType.ConstantBuffer: case ResourceType.StructuredBuffer: case ResourceType.StructuredBufferReadWrite: - descriptorSets[i].PBufferInfo = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); + sets[i].PBufferInfo = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); break; case ResourceType.Texture: case ResourceType.TextureReadWrite: case ResourceType.Sampler: - descriptorSets[i].PImageInfo = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); + sets[i].PImageInfo = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); break; case ResourceType.AccelerationStructure: - descriptorSets[i].PNext = (WriteDescriptorSetAccelerationStructureKHR*)ZenithMarshal.AllocateAndFill(scope, [new() - { - SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, - AccelerationStructureCount = resourceSlot.Count, - PAccelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, resourceSlot.Count) - }]); + sets[i].PNext = (WriteDescriptorSetAccelerationStructureKHR*)ZenithMarshal.AllocateAndFill(scope, + [ + new WriteDescriptorSetAccelerationStructureKHR() + { + SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, + AccelerationStructureCount = resourceSlot.Count, + PAccelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, resourceSlot.Count) + } + ]); break; } } - DescriptorSets = (WriteDescriptorSet*)ZenithMarshal.AllocateAndFill(scope, descriptorSets); + Sets = (WriteDescriptorSet*)ZenithMarshal.AllocateAndFill(scope, sets); SrvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; UavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; @@ -65,7 +68,7 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { ResourceSlot resourceSlot = Desc.Slots[slot]; - ref WriteDescriptorSet descriptorSet = ref DescriptorSets[slot]; + ref WriteDescriptorSet descriptorSet = ref Sets[slot]; switch (resourceSlot.Type) { From 7ffc769d72d6c575d36c473eedaab31aeb4e5f88 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 17:01:43 +0800 Subject: [PATCH 010/466] Refactor DXResourceTable texture views, remove unused method Renamed srvTextureViews and uavTextureViews to public properties SrvTextureViews and UavTextureViews in DXResourceTable for external access and clarity. Updated all internal references accordingly. Removed the obsolete DirectX12(ShaderStageFlags) method from DXFormats as it is no longer used. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 25 ------------------- .../Zenith.NET.DirectX12/DXResourceTable.cs | 22 ++++++++-------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index eba4d384..4e5eaa97 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -310,31 +310,6 @@ PrimitiveTopology.TriangleList or ); } - public static ShaderVisibility DirectX12(ShaderStageFlags shaderStageFlags) - { - if (shaderStageFlags.HasFlag(ShaderStageFlags.Vertex)) - { - return ShaderVisibility.Vertex; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Pixel)) - { - return ShaderVisibility.Pixel; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Amplification)) - { - return ShaderVisibility.Amplification; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Mesh)) - { - return ShaderVisibility.Mesh; - } - - return ShaderVisibility.All; - } - public static DxFillMode DirectX12(FillMode fillMode) { return fillMode switch diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index 3b4c9279..1407e06b 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -6,8 +6,6 @@ internal class DXResourceTable : ResourceTable { private readonly DXDescriptorToken cbvSrvUavToken; private readonly DXDescriptorToken samplerToken; - private readonly DXTextureView?[] srvTextureViews; - private readonly DXTextureView?[] uavTextureViews; public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { @@ -23,12 +21,16 @@ public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.NumDescriptors)); } - srvTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; - uavTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; + SrvTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; + UavTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + public DXTextureView?[] SrvTextureViews { get; } + + public DXTextureView?[] UavTextureViews { get; } + public void Bind(DXCommandBuffer commandBuffer, DXDescriptorTable cbvSrvUavTable, DXDescriptorTable samplerTable, bool isGraphics) { uint offset = 0; @@ -133,13 +135,13 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - srvTextureViews[index] = texture.DirectX12().View; + SrvTextureViews[index] = texture.DirectX12().View; } else if (resource is TextureView textureView) { Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - srvTextureViews[index] = textureView.DirectX12(); + SrvTextureViews[index] = textureView.DirectX12(); } index++; @@ -153,13 +155,13 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - uavTextureViews[index] = texture.DirectX12().View; + UavTextureViews[index] = texture.DirectX12().View; } else if (resource is TextureView textureView) { Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - uavTextureViews[index] = textureView.DirectX12(); + UavTextureViews[index] = textureView.DirectX12(); } index++; @@ -184,12 +186,12 @@ protected override void PreprocessImpl(CommandBuffer commandBuffer) { DXCommandBuffer dxCommandBuffer = commandBuffer.DirectX12(); - foreach (DXTextureView? textureView in srvTextureViews) + foreach (DXTextureView? textureView in SrvTextureViews) { textureView?.TransitionStates(dxCommandBuffer, ResourceStates.AllShaderResource); } - foreach (DXTextureView? textureView in uavTextureViews) + foreach (DXTextureView? textureView in UavTextureViews) { textureView?.TransitionStates(dxCommandBuffer, ResourceStates.UnorderedAccess); } From b0b09db8f32a3755a71e236dfeeaa0f24ad6b045 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 18:57:31 +0800 Subject: [PATCH 011/466] Refactor: Replace ResourceSlot with ResourceBinding system Refactor resource binding system across the engine by replacing ResourceSlot with the new ResourceBinding struct. Update all pipeline and resource table descriptors, renderer implementations, and backend-specific code (DirectX12, Vulkan, Metal) to use ResourceBinding[]. Rename related fields and methods for consistency. Move root signature and descriptor set layout creation logic to ResourceBinding[] extensions. Update validation and utility methods accordingly. This unifies and modernizes resource binding management, improving maintainability and extensibility. --- .../Renderers/PathTracingRenderer.cs | 6 +- .../Renderers/RasterizationRenderer.cs | 6 +- .../ImGuiRenderer.cs | 8 +- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 50 +----- .../DXGraphicsPipeline.cs | 52 +----- .../DXMeshShadingPipeline.cs | 50 +----- .../Zenith.NET.DirectX12/DXResourceTable.cs | 166 ++++++++++-------- sources/Zenith.NET.DirectX12/Extensions.cs | 64 ++++++- .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 2 +- sources/Zenith.NET.Metal/MTLResourceTable.cs | 98 +++++------ sources/Zenith.NET.Vulkan/Extensions.cs | 45 ++--- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 12 +- .../Zenith.NET.Vulkan/VKComputePipeline.cs | 14 +- .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 14 +- .../VKMeshShadingPipeline.cs | 14 +- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 50 +++--- sources/Zenith.NET/ResourceTable.cs | 10 +- .../Zenith.NET/Structs/ComputePipelineDesc.cs | 2 +- .../Structs/GraphicsPipelineDesc.cs | 2 +- .../Structs/MeshShadingPipelineDesc.cs | 2 +- .../{ResourceSlot.cs => ResourceBinding.cs} | 2 +- .../Zenith.NET/Structs/ResourceTableDesc.cs | 2 +- sources/Zenith.NET/ValidationLayer.cs | 40 ++--- 23 files changed, 305 insertions(+), 406 deletions(-) rename sources/Zenith.NET/Structs/{ResourceSlot.cs => ResourceBinding.cs} (69%) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 74e71f14..1a43f269 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -12,7 +12,7 @@ internal unsafe class PathTracingRenderer : Renderer { private const uint ThreadGroupSize = 16; - private readonly ResourceSlot[] resourceSlots = + private readonly ResourceBinding[] resourceBindings = [ new() { Type = ResourceType.AccelerationStructure, Count = 1 }, new() { Type = ResourceType.ConstantBuffer, Count = 1 }, @@ -121,7 +121,7 @@ public PathTracingRenderer() pipeline = App.Context.CreateComputePipeline(new() { Compute = computeShader, - ResourceSlots = resourceSlots, + ResourceBindings = resourceBindings, ThreadGroupSizeX = ThreadGroupSize, ThreadGroupSizeY = ThreadGroupSize, ThreadGroupSizeZ = 1 @@ -174,7 +174,7 @@ public override void Render(CommandBuffer commandBuffer) Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess }); - resourceTable = App.Context.CreateResourceTable(new() { Slots = resourceSlots }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = resourceBindings }); resourceTable.Write(0, tlas); resourceTable.Write(1, cameraBuffer); resourceTable.Write(2, vertexBuffer); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 70b07816..6bda4827 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -10,7 +10,7 @@ namespace CornellBox.Renderers; internal unsafe class RasterizationRenderer : Renderer { - private readonly ResourceSlot[] resourceSlots = + private readonly ResourceBinding[] resourceBindings = [ new() { Type = ResourceType.ConstantBuffer, Count = 1 }, new() { Type = ResourceType.StructuredBuffer, Count = 1 } @@ -61,7 +61,7 @@ public RasterizationRenderer() Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite }); - resourceTable = App.Context.CreateResourceTable(new() { Slots = resourceSlots }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = resourceBindings }); resourceTable.Write(0, constantBuffer); resourceTable.Write(1, materialBuffer); @@ -82,7 +82,7 @@ public RasterizationRenderer() }, Vertex = vertexShader, Pixel = pixelShader, - ResourceSlots = resourceSlots, + ResourceBindings = resourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = FrameBuffer.Output diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index fa3e834f..e9772c5d 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -96,7 +96,7 @@ float4 PSMain(VSOutput input) : SV_TARGET "4D544C4201800200070000810E000000D81100000000000058000000000000008A00000000000000160100000000000008000000000000001E0100000000000008000000000000002601000000000000000E000000000000010000008A0000004E414D45070050534D61696E00545950450100014841534820008335EA1F82646DCCC623827DA6EBF0258B1E224AA4218D60D70CC077494E05494F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800000E00000000000052464C5408000400000000000000454E4454524C53541000260F000000000000B202000000000000555549441000C710A16106E43B598CE29DE59990D629454E445408000000454E445408000000454E4454DEC0170B0000000014000000EC0D0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000350300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C8000000892000001F0000003222480920624600212B249814212524981419270C85A4906052645C20246582609A01184620801B84610401404A9A224A98FC7F22AE898A88DF1EFE698C00184420028CA429A284C9FF2580791622FAA731026010C1108C214608E5109A23408E10D41C4130470006C30842639455CE608E01D0E80D048C0000000051180000670000001BF623F8FFFFFFFF015803C014003F002460022AA00F8870800778908777C0873630877A708771680373808736688770A0077400E8411EEAA11C00C21CE4211CDAA11CDA001EDE211DDC811ECA411E807060077600887648077768037628877308077668037B288771A08777908736B8877420077A4007200EE4000F80C11DDEA10DC4A11ED2811DE8211DDC611E00C21DDEA10DD2C11DCC611EDAC01CE0A10DDA211CE8011D007A90877A28078070877768037708077798873630077868837608077A4007801EE4A11ECA0120DCE11DDA601ED2E11CDCA11CC8A10DF4A11CE4E11DE6A10DCC011EDAA01DC2811ED001A00779A88772000877788736A0077908077880877470877368837608077A4007801EE4A11ECA0120E6811EC2611CD6A10DE0411EDE811ECA611CE8E11DE4A10DC4A11ECCC11CCA411EDA601ED2411FCA01C00380A80777988770308772680373808736688770A0077400E8411EEAA11C800D843000A4B0011AFEFFFFFF7F00DA005803C014003F0024A002FA60834104C002541B8C420016A0DAC018FFFFFFFF3F006D00AC01200115D00700491800000300000013844098300C44316130880213B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69A4AC0920CD900A301000000200000001000000000003185219DB0304800000002000000000006000121B048ADE0C000064810009000000321E981019114C908C092647C604436A255008E55004E5538002055120C5300250060000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A807789807000000007920000008010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A3680660604F0000004A63611BB44171506C1C194491C12092A33C06B11C8CA4388BA460C97224000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E72656E6465725F7461726765746169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D656F75747075745F306169722E667261676D656E745F696E7075747573657228544558434F4F5244296169722E63656E7465726169722E7065727370656374697665666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E706F736974696F6E6169722E6E6F5F7065727370656374697665506F736974696F6E5F306169722E6172675F756E757365646169722E746578747572656169722E6C6F636174696F6E5F696E6465786169722E73616D706C657465787475726532643C666C6F61742C2073616D706C653E746578747572655F316169722E73616D706C657273616D706C657273616D706C65725F31000026630000000000003082D0082308CD3082D01023084D3182D0182308CD3182D02023084D3282D0282308CD3282D03023080930C37006011ACC30A481A006330C6930ACC10C431A106B30C39006C51ACC30A481C106330C6970B4C10C431A206E304390CC30A8C11BC0C10C8412076700073304CB0C013343D0CC70387000070F14493304A130430207135559CF1561332467306595F540913643A2065BC53D50D479332871F081011C9C41183C62108DC10C091A900118C0C1193C651099C10CC52890422998C229CC30C88128A0C28D01C0711CC7711CC7711CE7066EE0066EE0066EE0066E60D1811E5896650A1C2BB0022BD8033CB0828C042628233636BB3697B637B23AB6321733B6B0B3B951103998033AA8033BB8033CC88354D8D8ECDA5CD2C8CADCE84609F42097B0343917BB32B9B9B437B751823D482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D94800F720A4B9373197B6B834B632BFB7A83A34B7B739B1B65E8033FF88364C2D2E45CCCE4C2CEDACADCE84609500100A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000310000001304412C1000000004000000C46A600480DC08008111001233000000F13000001C0000002247C890510E042B00000000188601006169722D616C6961732D73636F7065732850534D61696E296169722D616C6961732D73636F70652D73616D706C6572736169722D616C6961732D73636F70652D74657874757265732B8456508515832BB4C22AAC185EA115586183F00AAE00002306CD108260F05887A114032108CC68420060B088FF6C0311001B04C4000000020000005B06E07805000000000000007120000003000000320E1022840084060000000000000000650C00002500000012039428010000000300000021000000090000004C000000010000005800000000000000580000000200000088000000000000002A0000001C00000000000000060000000600000000000000880000000000000000000000020000000000000000000000060000000000000006000000FFFFFFFF00240000060000001B000000060000001B000000FFFFFFFF08240000000000005D0C000015000000120394A60000000050534D61696E6169722E73616D706C655F746578747572655F32642E763466333233323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000001000000AE020000524255469E02000000000000000000000000140000004149525200000A0018000400100014000A000000000000000400000000000000100000000400000001000000000000000700000008020000AC0100004801000008010000C000000054000000040000001CFEFFFF010004001400000000000E0014000000040008000C0010000E000000000000000100000018000000040000000900000073616D706C65725F310000000700000073616D706C65720068FEFFFF0200040018000000000012001800000008000C00070000001000140012000000000000040000000001000000180000000400000009000000746578747572655F31000000180000007465787475726532643C666C6F61742C2073616D706C653E00000000C4FFFFFF02300400140000001000100000000000000008000C000700100000000000000100010000040000000A000000506F736974696F6E5F30000008000C0004000800080000000030040004000000C2FFFFFF0000010118000000C80000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290050FFFFFF0030040018000000000012001400000008000000060007000C00100012000000000001012400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000B0FFFFFF0010020018000000000012001400000004000800000000000C0010001200000000000000000000001800000004000000080000006F75747075745F300000000006000000666C6F617434000008000E000400080008000000010000001000000000000A001000040008000C000A0000002C000000200000000400000005000000020000000300000004000000050000000600000001000000010000000600000050534D61696E0000454E4454" ]; - private readonly ResourceSlot[] ResourceSlots = + private readonly ResourceBinding[] ResourceBindings = [ new() { Type = ResourceType.ConstantBuffer, Count = 1 }, new() { Type = ResourceType.Texture, Count = 1 }, @@ -180,7 +180,7 @@ public ImGuiRenderer(GraphicsContext context, Output output, ImGuiColorSpace col }, Vertex = vertex, Pixel = pixel, - ResourceSlots = ResourceSlots, + ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = output @@ -201,7 +201,7 @@ public ImTextureID Binding(Texture texture) id++; } - ResourceTable resourceTable = Context.CreateResourceTable(new() { Slots = ResourceSlots }); + ResourceTable resourceTable = Context.CreateResourceTable(new() { Bindings = ResourceBindings }); resourceTable.Write(0, constants); resourceTable.Write(1, texture); resourceTable.Write(2, sampler); @@ -222,7 +222,7 @@ public ImTextureID Binding(TextureView textureView) id++; } - ResourceTable resourceTable = Context.CreateResourceTable(new() { Slots = ResourceSlots }); + ResourceTable resourceTable = Context.CreateResourceTable(new() { Bindings = ResourceBindings }); resourceTable.Write(0, constants); resourceTable.Write(1, textureView); resourceTable.Write(2, sampler); diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index cd235eb5..40834ced 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -15,58 +15,10 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : ComputePipelineStateDesc computePipelineStateDesc = new() { + PRootSignature = RootSignature = desc.ResourceBindings.RootSignature(context), CS = desc.Compute.DirectX12().GetShaderBytecode(scope) }; - // ResourceSlots - { - desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); - - List parameters = []; - - if (cbvSrvUavRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } - - if (samplerRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } - - RootSignatureDesc rootSignatureDesc = new() - { - NumParameters = (uint)parameters.Count, - PParameters = (RootParameter*)ZenithMarshal.AllocateAndFill(scope, [.. parameters]), - Flags = RootSignatureFlags.AllowInputAssemblerInputLayout - }; - - ComPtr blob = default; - ComPtr error = default; - context.D3D12.SerializeRootSignature(&rootSignatureDesc, D3DRootSignatureVersion.Version1, ref blob, ref error).Success(); - context.Device.CreateRootSignature(0, blob.GetBufferPointer(), blob.GetBufferSize(), out RootSignature).Success(); - blob.Dispose(); - error.Dispose(); - - computePipelineStateDesc.PRootSignature = RootSignature; - } - context.Device.CreateComputePipelineState(&computePipelineStateDesc, out PipelineState).Success(); } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index d68162a5..4baafb86 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -16,9 +16,10 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() { - SampleMask = uint.MaxValue, + PRootSignature = RootSignature = desc.ResourceBindings.RootSignature(context), VS = desc.Vertex.DirectX12().GetShaderBytecode(scope), PS = desc.Pixel.DirectX12().GetShaderBytecode(scope), + SampleMask = uint.MaxValue, PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).PrimitiveTopologyType }; @@ -106,55 +107,6 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); } - // ResourceSlots - { - desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); - - List parameters = []; - - if (cbvSrvUavRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } - - if (samplerRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } - - RootSignatureDesc rootSignatureDesc = new() - { - NumParameters = (uint)parameters.Count, - PParameters = (RootParameter*)ZenithMarshal.AllocateAndFill(scope, [.. parameters]), - Flags = RootSignatureFlags.AllowInputAssemblerInputLayout - }; - - ComPtr blob = default; - ComPtr error = default; - context.D3D12.SerializeRootSignature(&rootSignatureDesc, D3DRootSignatureVersion.Version1, ref blob, ref error).Success(); - context.Device.CreateRootSignature(0, blob.GetBufferPointer(), blob.GetBufferSize(), out RootSignature).Success(); - blob.Dispose(); - error.Dispose(); - - graphicsPipelineStateDesc.PRootSignature = RootSignature; - } - // InputLayouts { List inputElementDescs = []; diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 30394ca8..76139354 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -16,6 +16,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() { + PRootSignature = RootSignature = desc.ResourceBindings.RootSignature(context), SampleMask = uint.MaxValue }; @@ -103,55 +104,6 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); } - // ResourceSlots - { - desc.ResourceSlots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); - - List parameters = []; - - if (cbvSrvUavRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } - - if (samplerRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } - - RootSignatureDesc rootSignatureDesc = new() - { - NumParameters = (uint)parameters.Count, - PParameters = (RootParameter*)ZenithMarshal.AllocateAndFill(scope, [.. parameters]), - Flags = RootSignatureFlags.AllowInputAssemblerInputLayout - }; - - ComPtr blob = default; - ComPtr error = default; - context.D3D12.SerializeRootSignature(&rootSignatureDesc, D3DRootSignatureVersion.Version1, ref blob, ref error).Success(); - context.Device.CreateRootSignature(0, blob.GetBufferPointer(), blob.GetBufferSize(), out RootSignature).Success(); - blob.Dispose(); - error.Dispose(); - - graphicsPipelineStateDesc.PRootSignature = RootSignature; - } - // PrimitiveTopology { graphicsPipelineStateDesc.PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).PrimitiveTopologyType; diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index 1407e06b..b0755511 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -9,7 +9,7 @@ internal class DXResourceTable : ResourceTable public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - desc.Slots.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); + desc.Bindings.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); if (cbvSrvUavRanges.Length > 0) { @@ -21,8 +21,8 @@ public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.NumDescriptors)); } - SrvTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; - UavTextureViews = new DXTextureView?[Desc.Slots.Sum(slot => slot.Count)]; + SrvTextureViews = new DXTextureView?[cbvSrvUavToken.Length]; + UavTextureViews = new DXTextureView?[cbvSrvUavToken.Length]; } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; @@ -64,119 +64,147 @@ public void Bind(DXCommandBuffer commandBuffer, DXDescriptorTable cbvSrvUavTable } } - protected override void SetImpl(uint slot, IBindableResource[] resources) + protected override void SetImpl(uint binding, IBindableResource[] resources) { - ResourceSlot resourceSlot = Desc.Slots[slot]; - - uint index = 0; - if (resourceSlot.Type is ResourceType.Sampler) - { - index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); - } - else - { - index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - } - - switch (resourceSlot.Type) + switch (Desc.Bindings[binding].Type) { case ResourceType.ConstantBuffer: - foreach (IBindableResource resource in resources) { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); + } + + index++; } - - index++; } break; case ResourceType.StructuredBuffer: - foreach (IBindableResource resource in resources) { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); + } + + index++; } - - index++; } break; case ResourceType.StructuredBufferReadWrite: - foreach (IBindableResource resource in resources) { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); + if (resource is Buffer buffer) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + } + else if (resource is BufferView bufferView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); + } + + index++; } - - index++; } break; case ResourceType.Texture: - foreach (IBindableResource resource in resources) { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - SrvTextureViews[index] = texture.DirectX12().View; - } - else if (resource is TextureView textureView) + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); + if (resource is Texture texture) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - SrvTextureViews[index] = textureView.DirectX12(); - } + SrvTextureViews[index] = texture.DirectX12().View; + } + else if (resource is TextureView textureView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - index++; + SrvTextureViews[index] = textureView.DirectX12(); + } + + index++; + } } break; case ResourceType.TextureReadWrite: - foreach (IBindableResource resource in resources) { - if (resource is Texture texture) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + if (resource is Texture texture) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); + + UavTextureViews[index] = texture.DirectX12().View; + } + else if (resource is TextureView textureView) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - UavTextureViews[index] = texture.DirectX12().View; + UavTextureViews[index] = textureView.DirectX12(); + } + + index++; } - else if (resource is TextureView textureView) + } + break; + + case ResourceType.Sampler: + { + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); + if (resource is Sampler sampler) + { + Context.Device.CopyDescriptorsSimple(1, samplerToken[index], sampler.DirectX12().Token.Handle, DescriptorHeapType.Sampler); + } - UavTextureViews[index] = textureView.DirectX12(); + index++; } - - index++; } break; case ResourceType.AccelerationStructure: - foreach (IBindableResource resource in resources) { - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); - } + if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) + { + Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); + } - index++; + index++; + } } break; } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index fbbf3abe..826c954e 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -1,9 +1,10 @@ using System.Diagnostics; +using Silk.NET.Core.Native; using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; -public static class Extensions +public static unsafe class Extensions { extension(GraphicsContext) { @@ -29,7 +30,7 @@ internal bool IsSuccess() } } - extension(ResourceSlot[] resourceSlots) + extension(ResourceBinding[] resourceBindings) { internal void DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges) { @@ -42,15 +43,15 @@ internal void DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRan uint samplerIndex = 0; uint cbvSrvUavRangeOffset = 0; uint samplerRangeOffset = 0; - foreach (ResourceSlot resourceSlot in resourceSlots) + foreach (ResourceBinding resourceBinding in resourceBindings) { DescriptorRange range = new() { - RangeType = DXFormats.DirectX12(resourceSlot.Type), - NumDescriptors = resourceSlot.Count + RangeType = DXFormats.DirectX12(resourceBinding.Type), + NumDescriptors = resourceBinding.Count }; - switch (resourceSlot.Type) + switch (resourceBinding.Type) { case ResourceType.ConstantBuffer: range.BaseShaderRegister = cbvIndex++; @@ -88,6 +89,57 @@ internal void DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRan cbvSrvUavRanges = [.. cbvSrvUavRangeList]; samplerRanges = [.. samplerRangeList]; } + + internal ComPtr RootSignature(DXGraphicsContext context) + { + using ZenithMarshal.Scope scope = new(); + + resourceBindings.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); + + List parameters = []; + + if (cbvSrvUavRanges.Length > 0) + { + parameters.Add(new() + { + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() + { + NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) + } + }); + } + + if (samplerRanges.Length > 0) + { + parameters.Add(new() + { + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() + { + NumDescriptorRanges = (uint)samplerRanges.Length, + PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) + } + }); + } + + RootSignatureDesc desc = new() + { + NumParameters = (uint)parameters.Count, + PParameters = (RootParameter*)ZenithMarshal.AllocateAndFill(scope, [.. parameters]), + Flags = RootSignatureFlags.AllowInputAssemblerInputLayout + }; + + ComPtr blob = default; + ComPtr error = default; + context.D3D12.SerializeRootSignature(&desc, D3DRootSignatureVersion.Version1, ref blob, ref error).Success(); + context.Device.CreateRootSignature(0, blob.GetBufferPointer(), blob.GetBufferSize(), out ComPtr rootSignature).Success(); + blob.Dispose(); + error.Dispose(); + + return rootSignature; + } } extension(CommandBuffer commandBuffer) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs index c6a42103..d2786ef3 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -10,7 +10,7 @@ internal class MTLGraphicsPipeline : GraphicsPipeline public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) { - VertexBufferStartIndex = (uint)desc.ResourceSlots.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); + VertexBufferStartIndex = (uint)desc.ResourceBindings.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); MTL4RenderPipelineDescriptor descriptor = new() { diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs index 319311d8..e00b077a 100644 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ b/sources/Zenith.NET.Metal/MTLResourceTable.cs @@ -12,9 +12,9 @@ internal class MTLResourceTable : ResourceTable public MTLResourceTable(MTLGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - bufferBindings = new Binding?[desc.Slots.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count)]; - textureBindings = new Binding?[desc.Slots.Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count)]; - samplerBindings = new Binding?[desc.Slots.Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count)]; + bufferBindings = new Binding?[desc.Bindings.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count)]; + textureBindings = new Binding?[desc.Bindings.Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count)]; + samplerBindings = new Binding?[desc.Bindings.Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count)]; MTL4ArgumentTableDescriptor descriptor = new() { @@ -45,81 +45,71 @@ public void Bind(MTL4ArgumentTable argumentTable) } } - protected override void SetImpl(uint slot, IBindableResource[] resources) + protected override void SetImpl(uint binding, IBindableResource[] resources) { - ResourceSlot resourceSlot = Desc.Slots[slot]; - - uint index = 0; - switch (resourceSlot.Type) + switch (Desc.Bindings[binding].Type) { case ResourceType.ConstantBuffer: case ResourceType.StructuredBuffer: case ResourceType.StructuredBufferReadWrite: case ResourceType.AccelerationStructure: - index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); - break; - - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count); - break; - - case ResourceType.Sampler: - index = (uint)Desc.Slots.Take((int)slot).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); - break; - } - - switch (resourceSlot.Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - case ResourceType.AccelerationStructure: - foreach (IBindableResource resource in resources) { - if (resource is Buffer buffer) - { - bufferBindings[index] = new(buffer.Metal().GpuAddress, default, index); - } - else if (resource is BufferView bufferView) - { - bufferBindings[index] = new(bufferView.Metal().GpuAddress, default, index); - } - else if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - bufferBindings[index] = new(default, topLevelAccelerationStructure.Metal().AccelerationStructure.GpuResourceID, index); + if (resource is Buffer buffer) + { + bufferBindings[index] = new(buffer.Metal().GpuAddress, default, index); + } + else if (resource is BufferView bufferView) + { + bufferBindings[index] = new(bufferView.Metal().GpuAddress, default, index); + } + else if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) + { + bufferBindings[index] = new(default, topLevelAccelerationStructure.Metal().AccelerationStructure.GpuResourceID, index); + } + + index++; } - - index++; } break; case ResourceType.Texture: case ResourceType.TextureReadWrite: - foreach (IBindableResource resource in resources) { - if (resource is Texture texture) - { - textureBindings[index] = new(default, texture.Metal().Texture.GpuResourceID, index); - } - else if (resource is TextureView textureView) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - textureBindings[index] = new(default, textureView.Metal().Texture.GpuResourceID, index); + if (resource is Texture texture) + { + textureBindings[index] = new(default, texture.Metal().Texture.GpuResourceID, index); + } + else if (resource is TextureView textureView) + { + textureBindings[index] = new(default, textureView.Metal().Texture.GpuResourceID, index); + } + + index++; } - - index++; } break; case ResourceType.Sampler: - foreach (IBindableResource resource in resources) { - if (resource is Sampler sampler) + uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); + + foreach (IBindableResource resource in resources) { - samplerBindings[index] = new(default, sampler.Metal().SamplerState.GpuResourceID, index); - } + if (resource is Sampler sampler) + { + samplerBindings[index] = new(default, sampler.Metal().SamplerState.GpuResourceID, index); + } - index++; + index++; + } } break; } diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 85ff077c..baae72f3 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -4,7 +4,7 @@ namespace Zenith.NET.Vulkan; -public static class Extensions +public static unsafe class Extensions { extension(GraphicsContext) { @@ -32,39 +32,44 @@ internal void AddNext(out TNext next) next = default; next.StructureType(); - unsafe + BaseInStructure* current = (BaseInStructure*)Unsafe.AsPointer(ref chain); + while (current->PNext is not null) { - BaseInStructure* current = (BaseInStructure*)Unsafe.AsPointer(ref chain); - while (current->PNext is not null) - { - current = current->PNext; - } - - current->PNext = (BaseInStructure*)Unsafe.AsPointer(ref next); + current = current->PNext; } + + current->PNext = (BaseInStructure*)Unsafe.AsPointer(ref next); } } - extension(ResourceSlot[] resourceSlots) + extension(ResourceBinding[] resourceBindings) { - internal DescriptorSetLayoutBinding[] Vulkan() + internal DescriptorSetLayout DescriptorSetLayout(VKGraphicsContext context) { - DescriptorSetLayoutBinding[] bindings = new DescriptorSetLayoutBinding[resourceSlots.Length]; + using ZenithMarshal.Scope scope = new(); - for (int i = 0; i < resourceSlots.Length; i++) + DescriptorSetLayoutCreateInfo createInfo = new() { - ResourceSlot resourceSlot = resourceSlots[i]; + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)resourceBindings.Length, + PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, [.. resourceBindings.Select(Vulkan)]), + Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit + }; + + context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); - bindings[i] = new() + return descriptorSetLayout; + + static DescriptorSetLayoutBinding Vulkan(ResourceBinding resourceBinding, int index) + { + return new DescriptorSetLayoutBinding { - Binding = (uint)i, - DescriptorType = VKFormats.Vulkan(resourceSlot.Type), - DescriptorCount = resourceSlot.Count, + Binding = (uint)index, + DescriptorType = VKFormats.Vulkan(resourceBinding.Type), + DescriptorCount = resourceBinding.Count, StageFlags = VkShaderStageFlags.All }; } - - return bindings; } } diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 0f9521d7..bba68a69 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -374,15 +374,15 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) { - (PipelineBindPoint pipelineBindPoint, PipelineLayout pipelineLayout) = pipeline switch + (PipelineBindPoint pipelineBindPoint, PipelineLayout pipelineLayout, uint descriptorWriteCount) = pipeline switch { - GraphicsPipeline graphicsPipeline => (PipelineBindPoint.Graphics, graphicsPipeline.Vulkan().PipelineLayout), - ComputePipeline computePipeline => (PipelineBindPoint.Compute, computePipeline.Vulkan().PipelineLayout), - MeshShadingPipeline meshShadingPipeline => (PipelineBindPoint.Graphics, meshShadingPipeline.Vulkan().PipelineLayout), - _ => (PipelineBindPoint.Graphics, default) + GraphicsPipeline graphicsPipeline => (PipelineBindPoint.Graphics, graphicsPipeline.Vulkan().PipelineLayout, (uint)graphicsPipeline.Desc.ResourceBindings.Length), + ComputePipeline computePipeline => (PipelineBindPoint.Compute, computePipeline.Vulkan().PipelineLayout, (uint)computePipeline.Desc.ResourceBindings.Length), + MeshShadingPipeline meshShadingPipeline => (PipelineBindPoint.Graphics, meshShadingPipeline.Vulkan().PipelineLayout, (uint)meshShadingPipeline.Desc.ResourceBindings.Length), + _ => (PipelineBindPoint.Graphics, default, 0) }; - Context.Vk.CmdPushDescriptorSet(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, (uint)resourceTable.Desc.Slots.Length, resourceTable.Vulkan().Sets); + Context.Vk.CmdPushDescriptorSet(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, descriptorWriteCount, resourceTable.Vulkan().Sets); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index 4d68e676..657f1c85 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -20,23 +20,13 @@ public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : Stage = desc.Compute.Vulkan().GetPipelineShaderStageCreateInfo(scope) }; - // ResourceSlots + // ResourceBindings { - DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)desc.ResourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, desc.ResourceSlots.Vulkan()), - Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit - }; - - context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); - PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout]) + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout = desc.ResourceBindings.DescriptorSetLayout(context)]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 651e82c1..ba8d7357 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -150,23 +150,13 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) createInfo.PNext = &rendering; } - // ResourceSlots + // ResourceBindings { - DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)desc.ResourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, desc.ResourceSlots.Vulkan()), - Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit - }; - - context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); - PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout]) + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout = desc.ResourceBindings.DescriptorSetLayout(context)]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 25f6a11f..1568f9c3 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -167,23 +167,13 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc createInfo.PStages = stages; } - // ResourceSlots + // ResourceBindings { - DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = new() - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)desc.ResourceSlots.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, desc.ResourceSlots.Vulkan()), - Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit - }; - - context.Vk.CreateDescriptorSetLayout(context.Device, &descriptorSetLayoutCreateInfo, null, out DescriptorSetLayout).Success(); - PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() { SType = StructureType.PipelineLayoutCreateInfo, SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout]) + PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout = desc.ResourceBindings.DescriptorSetLayout(context)]) }; context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index 1da00443..7d085a37 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -10,32 +10,32 @@ internal unsafe class VKResourceTable : ResourceTable public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) { - WriteDescriptorSet[] sets = new WriteDescriptorSet[desc.Slots.Length]; + WriteDescriptorSet[] sets = new WriteDescriptorSet[desc.Bindings.Length]; for (int i = 0; i < sets.Length; i++) { - ResourceSlot resourceSlot = desc.Slots[i]; + ResourceBinding binding = desc.Bindings[i]; sets[i] = new() { SType = StructureType.WriteDescriptorSet, DstBinding = (uint)i, - DescriptorCount = resourceSlot.Count, - DescriptorType = VKFormats.Vulkan(resourceSlot.Type) + DescriptorCount = binding.Count, + DescriptorType = VKFormats.Vulkan(binding.Type) }; - switch (resourceSlot.Type) + switch (binding.Type) { case ResourceType.ConstantBuffer: case ResourceType.StructuredBuffer: case ResourceType.StructuredBufferReadWrite: - sets[i].PBufferInfo = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); + sets[i].PBufferInfo = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, binding.Count); break; case ResourceType.Texture: case ResourceType.TextureReadWrite: case ResourceType.Sampler: - sets[i].PImageInfo = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, resourceSlot.Count); + sets[i].PImageInfo = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, binding.Count); break; case ResourceType.AccelerationStructure: @@ -44,8 +44,8 @@ public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base new WriteDescriptorSetAccelerationStructureKHR() { SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, - AccelerationStructureCount = resourceSlot.Count, - PAccelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, resourceSlot.Count) + AccelerationStructureCount = binding.Count, + PAccelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, binding.Count) } ]); break; @@ -54,8 +54,8 @@ public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base Sets = (WriteDescriptorSet*)ZenithMarshal.AllocateAndFill(scope, sets); - SrvTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; - UavTextureViews = new VKTextureView?[desc.Slots.Sum(static item => item.Count)]; + SrvTextureViews = new VKTextureView?[desc.Bindings.Sum(static item => item.Count)]; + UavTextureViews = new VKTextureView?[desc.Bindings.Sum(static item => item.Count)]; } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; @@ -64,13 +64,11 @@ public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base public VKTextureView?[] UavTextureViews { get; } - protected override void SetImpl(uint slot, IBindableResource[] resources) + protected override void SetImpl(uint binding, IBindableResource[] resources) { - ResourceSlot resourceSlot = Desc.Slots[slot]; + ref WriteDescriptorSet set = ref Sets[binding]; - ref WriteDescriptorSet descriptorSet = ref Sets[slot]; - - switch (resourceSlot.Type) + switch (Desc.Bindings[binding].Type) { case ResourceType.ConstantBuffer: case ResourceType.StructuredBuffer: @@ -81,18 +79,18 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) if (resource is Buffer buffer) { - descriptorSet.PBufferInfo[i] = buffer.Vulkan().View.BufferInfo; + set.PBufferInfo[i] = buffer.Vulkan().View.BufferInfo; } else if (resource is BufferView bufferView) { - descriptorSet.PBufferInfo[i] = bufferView.Vulkan().BufferInfo; + set.PBufferInfo[i] = bufferView.Vulkan().BufferInfo; } } break; case ResourceType.Texture: { - uint index = (uint)Desc.Slots.Take((int)slot).Sum(static item => item.Count); + uint index = (uint)Desc.Bindings.Take((int)binding).Sum(static item => item.Count); for (int i = 0; i < resources.Length; i++) { @@ -100,13 +98,13 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) if (resource is Texture texture) { - descriptorSet.PImageInfo[i] = texture.Vulkan().View.SrvImageInfo; + set.PImageInfo[i] = texture.Vulkan().View.SrvImageInfo; SrvTextureViews[index + i] = texture.Vulkan().View; } else if (resource is TextureView textureView) { - descriptorSet.PImageInfo[i] = textureView.Vulkan().SrvImageInfo; + set.PImageInfo[i] = textureView.Vulkan().SrvImageInfo; SrvTextureViews[index + i] = textureView.Vulkan(); } @@ -116,7 +114,7 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) case ResourceType.TextureReadWrite: { - uint index = (uint)Desc.Slots.Take((int)slot).Sum(static item => item.Count); + uint index = (uint)Desc.Bindings.Take((int)binding).Sum(static item => item.Count); for (int i = 0; i < resources.Length; i++) { @@ -124,13 +122,13 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) if (resource is Texture texture) { - descriptorSet.PImageInfo[i] = texture.Vulkan().View.UavImageInfo; + set.PImageInfo[i] = texture.Vulkan().View.UavImageInfo; UavTextureViews[index + i] = texture.Vulkan().View; } else if (resource is TextureView textureView) { - descriptorSet.PImageInfo[i] = textureView.Vulkan().UavImageInfo; + set.PImageInfo[i] = textureView.Vulkan().UavImageInfo; UavTextureViews[index + i] = textureView.Vulkan(); } @@ -143,7 +141,7 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { if (resources[i] is Sampler sampler) { - descriptorSet.PImageInfo[i] = new() { Sampler = sampler.Vulkan().Sampler }; + set.PImageInfo[i] = new() { Sampler = sampler.Vulkan().Sampler }; } } break; @@ -153,7 +151,7 @@ protected override void SetImpl(uint slot, IBindableResource[] resources) { if (resources[i] is TopLevelAccelerationStructure topLevelAccelerationStructure) { - ((WriteDescriptorSetAccelerationStructureKHR*)descriptorSet.PNext)->PAccelerationStructures[i] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; + ((WriteDescriptorSetAccelerationStructureKHR*)set.PNext)->PAccelerationStructures[i] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; } } break; diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs index f2a62f9f..dd351259 100644 --- a/sources/Zenith.NET/ResourceTable.cs +++ b/sources/Zenith.NET/ResourceTable.cs @@ -6,9 +6,9 @@ public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc d public ref readonly ResourceTableDesc Desc => ref desc; - public void Write(uint slot, params IBindableResource[] resources) + public void Write(uint binding, params IBindableResource[] resources) { - if (slot >= desc.Slots.Length || resources.Length is 0 || resources.Length > desc.Slots[slot].Count) + if (binding >= desc.Bindings.Length || resources.Length is 0 || resources.Length > desc.Bindings[binding].Count) { return; } @@ -21,7 +21,7 @@ public void Write(uint slot, params IBindableResource[] resources) } } - switch (desc.Slots[slot].Type) + switch (desc.Bindings[binding].Type) { case ResourceType.ConstantBuffer: case ResourceType.StructuredBuffer: @@ -67,7 +67,7 @@ public void Write(uint slot, params IBindableResource[] resources) break; } - SetImpl(slot, resources); + SetImpl(binding, resources); } internal void Preprocess(CommandBuffer commandBuffer) @@ -75,7 +75,7 @@ internal void Preprocess(CommandBuffer commandBuffer) PreprocessImpl(commandBuffer); } - protected abstract void SetImpl(uint slot, IBindableResource[] resources); + protected abstract void SetImpl(uint binding, IBindableResource[] resources); protected abstract void PreprocessImpl(CommandBuffer commandBuffer); } diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index c496810f..1011458f 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -4,7 +4,7 @@ public record struct ComputePipelineDesc { public Shader Compute; - public ResourceSlot[] ResourceSlots; + public ResourceBinding[] ResourceBindings; public uint ThreadGroupSizeX; diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 60b4c1aa..d1d2ceb4 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -8,7 +8,7 @@ public record struct GraphicsPipelineDesc public Shader Pixel; - public ResourceSlot[] ResourceSlots; + public ResourceBinding[] ResourceBindings; public InputLayout[] InputLayouts; diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index 31aa1af6..ab2cd004 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -10,7 +10,7 @@ public record struct MeshShadingPipelineDesc public Shader Pixel; - public ResourceSlot[] ResourceSlots; + public ResourceBinding[] ResourceBindings; public PrimitiveTopology PrimitiveTopology; diff --git a/sources/Zenith.NET/Structs/ResourceSlot.cs b/sources/Zenith.NET/Structs/ResourceBinding.cs similarity index 69% rename from sources/Zenith.NET/Structs/ResourceSlot.cs rename to sources/Zenith.NET/Structs/ResourceBinding.cs index 71c79858..71be2607 100644 --- a/sources/Zenith.NET/Structs/ResourceSlot.cs +++ b/sources/Zenith.NET/Structs/ResourceBinding.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct ResourceSlot +public record struct ResourceBinding { public ResourceType Type; diff --git a/sources/Zenith.NET/Structs/ResourceTableDesc.cs b/sources/Zenith.NET/Structs/ResourceTableDesc.cs index 0c98c0a2..d63357c6 100644 --- a/sources/Zenith.NET/Structs/ResourceTableDesc.cs +++ b/sources/Zenith.NET/Structs/ResourceTableDesc.cs @@ -2,5 +2,5 @@ public record struct ResourceTableDesc { - public ResourceSlot[] Slots; + public ResourceBinding[] Bindings; } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index aadf0be0..82747a43 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -349,16 +349,16 @@ internal void ValidateDesc(SamplerDesc desc) internal void ValidateDesc(ResourceTableDesc desc) { - if (desc.Slots is null || desc.Slots.Length is 0) + if (desc.Bindings is null || desc.Bindings.Length is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ResourceTableDesc.Slots")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ResourceTableDesc.Bindings")); return; } - for (int i = 0; i < desc.Slots.Length; i++) + for (int i = 0; i < desc.Bindings.Length; i++) { - CheckResourceSlot($"ResourceTableDesc.Slots[{i}]", desc.Slots[i]); + CheckResourceBinding($"ResourceTableDesc.Bindings[{i}]", desc.Bindings[i]); } } @@ -384,15 +384,15 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "GraphicsPipelineDesc.Pixel")); } - if (desc.ResourceSlots is null) + if (desc.ResourceBindings is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.ResourceSlots")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.ResourceBindings")); } else { - for (int i = 0; i < desc.ResourceSlots.Length; i++) + for (int i = 0; i < desc.ResourceBindings.Length; i++) { - CheckResourceSlot($"GraphicsPipelineDesc.ResourceSlots[{i}]", desc.ResourceSlots[i]); + CheckResourceBinding($"GraphicsPipelineDesc.ResourceBindings[{i}]", desc.ResourceBindings[i]); } } @@ -450,15 +450,15 @@ internal void ValidateDesc(ComputePipelineDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ComputePipelineDesc.Compute")); } - if (desc.ResourceSlots is null) + if (desc.ResourceBindings is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ComputePipelineDesc.ResourceSlots")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ComputePipelineDesc.ResourceBindings")); } else { - for (int i = 0; i < desc.ResourceSlots.Length; i++) + for (int i = 0; i < desc.ResourceBindings.Length; i++) { - CheckResourceSlot($"ComputePipelineDesc.ResourceSlots[{i}]", desc.ResourceSlots[i]); + CheckResourceBinding($"ComputePipelineDesc.ResourceBindings[{i}]", desc.ResourceBindings[i]); } } @@ -495,15 +495,15 @@ internal void ValidateDesc(MeshShadingPipelineDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Pixel")); } - if (desc.ResourceSlots is null) + if (desc.ResourceBindings is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.ResourceSlots")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.ResourceBindings")); } else { - for (int i = 0; i < desc.ResourceSlots.Length; i++) + for (int i = 0; i < desc.ResourceBindings.Length; i++) { - CheckResourceSlot($"MeshShadingPipelineDesc.ResourceSlots[{i}]", desc.ResourceSlots[i]); + CheckResourceBinding($"MeshShadingPipelineDesc.ResourceBindings[{i}]", desc.ResourceBindings[i]); } } @@ -724,14 +724,14 @@ internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAc } } - private void CheckResourceSlot(string name, ResourceSlot resourceSlot) + private void CheckResourceBinding(string name, ResourceBinding resourceBinding) { - if (!Enum.IsDefined(resourceSlot.Type)) + if (!Enum.IsDefined(resourceBinding.Type)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", resourceSlot.Type)); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", resourceBinding.Type)); } - if (resourceSlot.Count is 0) + if (resourceBinding.Count is 0) { ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.Count")); } From ae502f077638c19441c81fe5c1ae8e78b66fed99 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 19:18:01 +0800 Subject: [PATCH 012/466] Increase Metal argument table binding limits to 64 Raised buffer, texture, and sampler binding limits from 16 to 64 using a new ArgumentCount constant. Updated Bind methods to accept an argument count and skip out-of-bounds bindings, improving safety and flexibility. All relevant calls now pass the correct argument count. --- sources/Zenith.NET.Metal/MTLCommandEncoder.cs | 9 +++--- sources/Zenith.NET.Metal/MTLResourceTable.cs | 31 ++++++++++++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs index e0cc86ee..1686ca47 100644 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs @@ -4,6 +4,7 @@ namespace Zenith.NET.Metal; internal class MTLCommandEncoder : GraphicsResource { + private const uint ArgumentCount = 64; private const MTLStages RenderStages = MTLStages.Vertex | MTLStages.Fragment | MTLStages.Object | MTLStages.Mesh; private const MTLStages ComputeStages = MTLStages.Dispatch | MTLStages.Blit | MTLStages.AccelerationStructure; @@ -32,9 +33,9 @@ public MTLCommandEncoder(MTLGraphicsContext context, MTL4CommandBuffer commandBu MTL4ArgumentTableDescriptor descriptor = new() { - MaxBufferBindCount = 16, - MaxTextureBindCount = 16, - MaxSamplerStateBindCount = 16, + MaxBufferBindCount = ArgumentCount, + MaxTextureBindCount = ArgumentCount, + MaxSamplerStateBindCount = ArgumentCount, SupportAttributeStrides = true }; @@ -224,7 +225,7 @@ public void Bind() { Render?.SetArgumentTable(resourceTable.ArgumentTable, MTLRenderStages.Fragment); - resourceTable.Bind(ArgumentTable); + resourceTable.Bind(ArgumentTable, ArgumentCount); } foreach (KeyValuePair vertexBuffer in vertexBuffers) diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs index e00b077a..dded90a3 100644 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ b/sources/Zenith.NET.Metal/MTLResourceTable.cs @@ -27,21 +27,21 @@ public MTLResourceTable(MTLGraphicsContext context, ResourceTableDesc desc) : ba error.Success(); } - public void Bind(MTL4ArgumentTable argumentTable) + public void Bind(MTL4ArgumentTable argumentTable, uint argumentCount) { foreach (Binding? binding in bufferBindings) { - binding?.Buffer(argumentTable); + binding?.Buffer(argumentTable, argumentCount); } foreach (Binding? binding in textureBindings) { - binding?.Texture(argumentTable); + binding?.Texture(argumentTable, argumentCount); } foreach (Binding? binding in samplerBindings) { - binding?.Sampler(argumentTable); + binding?.Sampler(argumentTable, argumentCount); } } @@ -114,7 +114,7 @@ protected override void SetImpl(uint binding, IBindableResource[] resources) break; } - Bind(ArgumentTable); + Bind(ArgumentTable, uint.MaxValue); } protected override void PreprocessImpl(CommandBuffer commandBuffer) @@ -132,8 +132,13 @@ protected override void Destroy() private readonly struct Binding(nuint gpuAddress, MTLResourceID resourceID, uint index) { - public void Buffer(MTL4ArgumentTable argumentTable) + public void Buffer(MTL4ArgumentTable argumentTable, uint argumentCount) { + if (index >= argumentCount) + { + return; + } + if (gpuAddress != default) { argumentTable.SetAddress(gpuAddress, index); @@ -144,13 +149,23 @@ public void Buffer(MTL4ArgumentTable argumentTable) } } - public void Texture(MTL4ArgumentTable argumentTable) + public void Texture(MTL4ArgumentTable argumentTable, uint argumentCount) { + if (index >= argumentCount) + { + return; + } + argumentTable.SetTexture(resourceID, index); } - public void Sampler(MTL4ArgumentTable argumentTable) + public void Sampler(MTL4ArgumentTable argumentTable, uint argumentCount) { + if (index >= argumentCount) + { + return; + } + argumentTable.SetSamplerState(resourceID, index); } } From aea36948f09cd2c28f6f0ef63e505f853ac8207a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 21:21:46 +0800 Subject: [PATCH 013/466] Reduce ArgumentCount in MTLCommandEncoder to 16 Lowered the ArgumentCount constant from 64 to 16 in the MTLCommandEncoder class, reducing the maximum number of arguments that can be handled by the encoder at once. --- sources/Zenith.NET.Metal/MTLCommandEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs index 1686ca47..d597061d 100644 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs @@ -4,7 +4,7 @@ namespace Zenith.NET.Metal; internal class MTLCommandEncoder : GraphicsResource { - private const uint ArgumentCount = 64; + private const uint ArgumentCount = 16; private const MTLStages RenderStages = MTLStages.Vertex | MTLStages.Fragment | MTLStages.Object | MTLStages.Mesh; private const MTLStages ComputeStages = MTLStages.Dispatch | MTLStages.Blit | MTLStages.AccelerationStructure; From cf9d0fbd79222d65d73a1dbd9d0e41324af9a59b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 21:52:33 +0800 Subject: [PATCH 014/466] Remove GraphicShaderStages method from ZenithHelper The GraphicShaderStages method, which returned an array of common shader stage flags, has been removed from the ZenithHelper class. This cleans up unused or redundant code related to shader stage enumeration. --- sources/Zenith.NET/ZenithHelper.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/sources/Zenith.NET/ZenithHelper.cs b/sources/Zenith.NET/ZenithHelper.cs index 892b940c..d72ee11a 100644 --- a/sources/Zenith.NET/ZenithHelper.cs +++ b/sources/Zenith.NET/ZenithHelper.cs @@ -287,15 +287,4 @@ public static uint SubresourceSizeInBytes(TextureDesc desc, TextureSlice slice) return SizeInBytes(desc.Format, mipWidth, mipHeight) * mipDepth; } - - public static ShaderStageFlags[] GraphicShaderStages() - { - return - [ - ShaderStageFlags.Vertex, - ShaderStageFlags.Pixel, - ShaderStageFlags.Amplification, - ShaderStageFlags.Mesh - ]; - } } From bbeabe436fd1e60cee2cd2a3f4c3c4aabfbbc7ca Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 23:20:31 +0800 Subject: [PATCH 015/466] Refactor resource binding to use ResourceBindings array Removed ResourceLayout and BindingHelper from all tutorials and sample code. Each renderer now declares a static ResourceBindings array describing expected shader resources, which is passed to pipeline and resource table creation. ResourceTable.Write is used to assign resources by index. Updated documentation and code comments to reflect the new, backend-agnostic resource binding model for improved clarity and maintainability. --- documents/api/index.md | 3 +- documents/docs/concepts/resource-binding.md | 6 +- documents/docs/index.md | 10 +- documents/tutorials/advanced/mesh-shading.md | 33 +++--- documents/tutorials/advanced/ray-tracing.md | 52 ++++----- .../getting-started/hello-triangle.md | 6 +- .../getting-started/prerequisites.md | 101 ------------------ .../getting-started/spinning-cube.md | 24 ++--- .../getting-started/textured-quad.md | 53 +++------ documents/tutorials/index.md | 6 +- .../tutorials/intermediate/compute-shader.md | 29 ++--- .../intermediate/indirect-drawing.md | 27 ++--- 12 files changed, 96 insertions(+), 254 deletions(-) diff --git a/documents/api/index.md b/documents/api/index.md index 2e464262..eae193c8 100644 --- a/documents/api/index.md +++ b/documents/api/index.md @@ -51,8 +51,7 @@ Welcome to the Zenith.NET API Reference. This documentation is automatically gen | Type | Description | |------|-------------| -| `ResourceLayout` | Declares expected shader resource bindings | -| `ResourceTable` | Binds actual resources to a layout | +| `ResourceTable` | Manages resource bindings for shader access | | `Shader` | Compiled shader module with entry point and stage | ### Ray Tracing diff --git a/documents/docs/concepts/resource-binding.md b/documents/docs/concepts/resource-binding.md index 15772c1c..89df1b5d 100644 --- a/documents/docs/concepts/resource-binding.md +++ b/documents/docs/concepts/resource-binding.md @@ -5,6 +5,6 @@ This topic covers: -- Declaring bindings with `ResourceLayout` -- Binding resources with `ResourceTable` -- Index scheme differences across backends +- Creating and writing resources to `ResourceTable` +- Sharing binding definitions between pipelines and resource tables +- Dynamic resource updates with `ResourceTable.Write()` diff --git a/documents/docs/index.md b/documents/docs/index.md index bb3c67bc..b680bebd 100644 --- a/documents/docs/index.md +++ b/documents/docs/index.md @@ -45,14 +45,12 @@ Zenith.NET uses an explicit command recording model: ### Resource Binding -Resources are bound to shaders through two types: +Resources are bound to shaders through `ResourceTable`: -| Type | Purpose | -|------|---------| -| `ResourceLayout` | Declares *what* resources a shader expects (binding slots, types) | -| `ResourceTable` | Provides *actual* resources matching a layout | +1. **Create** — Create a `ResourceTable` that declares what each binding slot expects +2. **Write** — Call `Write()` to assign actual GPU resources to each slot -Pipelines reference a single `ResourceLayout`, and you bind a corresponding `ResourceTable` before draw/dispatch calls. +Pipelines and resource tables share the same binding definitions. Before draw/dispatch calls, bind the resource table with `SetResourceTable()`. ## Features diff --git a/documents/tutorials/advanced/mesh-shading.md b/documents/tutorials/advanced/mesh-shading.md index 19440d74..e959f176 100644 --- a/documents/tutorials/advanced/mesh-shading.md +++ b/documents/tutorials/advanced/mesh-shading.md @@ -255,10 +255,16 @@ internal unsafe class MeshShadingRenderer : IRenderer } """; + private static readonly ResourceBinding[] ResourceBindings = + [ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 } + ]; + private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Buffer constantsBuffer; - private readonly ResourceLayout resourceLayout; private readonly ResourceTable resourceTable; private readonly MeshShadingPipeline pipeline; @@ -355,21 +361,11 @@ internal unsafe class MeshShadingRenderer : IRenderer Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite }); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Amplification | ShaderStageFlags.Mesh | ShaderStageFlags.Pixel }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Mesh }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Mesh } - ) - }); - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constantsBuffer, vertexBuffer, indexBuffer] - }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = bindings }); + resourceTable.Write(0, constantsBuffer); + resourceTable.Write(1, vertexBuffer); + resourceTable.Write(2, indexBuffer); using Shader ampShader = App.Context.LoadShaderFromSource(ShaderSource, "ASMain", ShaderStageFlags.Amplification); using Shader meshShader = App.Context.LoadShaderFromSource(ShaderSource, "MSMain", ShaderStageFlags.Mesh); @@ -386,7 +382,7 @@ internal unsafe class MeshShadingRenderer : IRenderer Amplification = ampShader, Mesh = meshShader, Pixel = pixelShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output, AmplificationThreadGroupSizeX = ASGroupSize, @@ -453,7 +449,6 @@ internal unsafe class MeshShadingRenderer : IRenderer { pipeline.Dispose(); resourceTable.Dispose(); - resourceLayout.Dispose(); constantsBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); @@ -577,7 +572,7 @@ pipeline = App.Context.CreateMeshShadingPipeline(new() Amplification = ampShader, Mesh = meshShader, Pixel = pixelShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output, AmplificationThreadGroupSizeX = ASGroupSize, @@ -686,7 +681,7 @@ file struct Constants } ``` -The constant buffer is shared across all three shader stages (`Amplification | Mesh | Pixel`), so the amplification shader can read frustum planes while the pixel shader reads the light direction. +The constant buffer is shared across all three shader stages (amplification, mesh, and pixel), so the amplification shader can read frustum planes while the pixel shader reads the light direction. ## Source Code diff --git a/documents/tutorials/advanced/ray-tracing.md b/documents/tutorials/advanced/ray-tracing.md index 4b24c3a9..011723de 100644 --- a/documents/tutorials/advanced/ray-tracing.md +++ b/documents/tutorials/advanced/ray-tracing.md @@ -500,6 +500,14 @@ internal unsafe class RayTracingRenderer : IRenderer } """; + private static readonly ResourceBinding[] ResourceBindings = + [ + new() { Type = ResourceType.AccelerationStructure, Count = 1 }, + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 }, + new() { Type = ResourceType.TextureReadWrite, Count = 1 } + ]; + private readonly Buffer floorVertexBuffer; private readonly Buffer floorIndexBuffer; private readonly Buffer aabbBuffer; @@ -508,7 +516,6 @@ internal unsafe class RayTracingRenderer : IRenderer private readonly TopLevelAccelerationStructure tlas; private readonly Buffer constantsBuffer; private readonly Buffer sphereBuffer; - private readonly ResourceLayout resourceLayout; private readonly ComputePipeline pipeline; private Texture? outputTexture; @@ -655,23 +662,12 @@ internal unsafe class RayTracingRenderer : IRenderer }); sphereBuffer.Upload(spheres, 0); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.AccelerationStructure, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.TextureReadWrite, Count = 1, StageFlags = ShaderStageFlags.Compute } - ) - }); - using Shader computeShader = App.Context.LoadShaderFromSource(ShaderSource, "CSMain", ShaderStageFlags.Compute); pipeline = App.Context.CreateComputePipeline(new() { Compute = computeShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, ThreadGroupSizeX = ThreadGroupSize, ThreadGroupSizeY = ThreadGroupSize, ThreadGroupSizeZ = 1 @@ -705,11 +701,11 @@ internal unsafe class RayTracingRenderer : IRenderer Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess }); - resourceTable ??= App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [tlas, constantsBuffer, sphereBuffer, outputTexture] - }); + resourceTable ??= App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); + resourceTable.Write(0, tlas); + resourceTable.Write(1, constantsBuffer); + resourceTable.Write(2, sphereBuffer); + resourceTable.Write(3, outputTexture); CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); @@ -747,7 +743,6 @@ internal unsafe class RayTracingRenderer : IRenderer outputTexture?.Dispose(); pipeline.Dispose(); - resourceLayout.Dispose(); sphereBuffer.Dispose(); constantsBuffer.Dispose(); tlas.Dispose(); @@ -866,21 +861,18 @@ tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructur }); ``` -### Resource Layout +### Resource Bindings The compute shader accesses four resources — acceleration structure, constants, sphere data, and the output texture: ```csharp -resourceLayout = App.Context.CreateResourceLayout(new() -{ - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.AccelerationStructure, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.TextureReadWrite, Count = 1, StageFlags = ShaderStageFlags.Compute } - ) -}); +private static readonly ResourceBinding[] ResourceBindings = +[ + new() { Type = ResourceType.AccelerationStructure, Count = 1 }, + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 }, + new() { Type = ResourceType.TextureReadWrite, Count = 1 } +]; ``` ### Animated Camera diff --git a/documents/tutorials/getting-started/hello-triangle.md b/documents/tutorials/getting-started/hello-triangle.md index 1bc4f4b3..f043f455 100644 --- a/documents/tutorials/getting-started/hello-triangle.md +++ b/documents/tutorials/getting-started/hello-triangle.md @@ -88,7 +88,7 @@ internal unsafe class HelloTriangleRenderer : IRenderer }, Vertex = vertexShader, Pixel = pixelShader, - ResourceLayout = null, + ResourceBindings = [], InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output @@ -257,7 +257,7 @@ pipeline = App.Context.CreateGraphicsPipeline(new() }, Vertex = vertexShader, Pixel = pixelShader, - ResourceLayout = null, + ResourceBindings = [], InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output @@ -269,7 +269,7 @@ pipeline = App.Context.CreateGraphicsPipeline(new() | `RasterizerState` | `CullNone` | No face culling (both sides visible) | | `DepthStencilState` | `Default` | Standard depth testing | | `BlendState` | `Opaque` | No transparency | -| `ResourceLayout` | `null` | No bound resources needed | +| `ResourceBindings` | `[]` | No bound resources needed | | `PrimitiveTopology` | `TriangleList` | Every 3 vertices form a triangle | ### Rendering diff --git a/documents/tutorials/getting-started/prerequisites.md b/documents/tutorials/getting-started/prerequisites.md index 90ca0bbb..5768e569 100644 --- a/documents/tutorials/getting-started/prerequisites.md +++ b/documents/tutorials/getting-started/prerequisites.md @@ -74,7 +74,6 @@ ZenithTutorials/ ├── Program.cs ├── App.cs ├── IRenderer.cs -├── BindingHelper.cs ├── CocoaHelper.cs ├── Usings.cs ├── Assets/ @@ -268,106 +267,6 @@ internal static class App | `FrameBuffer` | The swap chain's current frame buffer | | `Run()` | Creates a renderer, runs the window loop, and cleans up on exit | -### BindingHelper.cs - -Each graphics backend (DirectX 12, Metal, Vulkan) uses different resource binding index conventions. `BindingHelper` assigns the correct indices automatically: - -```csharp -namespace ZenithTutorials; - -internal static class BindingHelper -{ - public static ResourceBinding[] Bindings(params ResourceBinding[] bindings) - { - switch (App.Context.Backend) - { - case Backend.DirectX12: - { - uint cbvIndex = 0; - uint srvIndex = 0; - uint uavIndex = 0; - uint samplerIndex = 0; - - for (int i = 0; i < bindings.Length; i++) - { - ref ResourceBinding binding = ref bindings[i]; - - binding = binding with - { - Index = binding.Type switch - { - ResourceType.ConstantBuffer => cbvIndex++, - - ResourceType.StructuredBuffer or - ResourceType.Texture or - ResourceType.AccelerationStructure => srvIndex++, - - ResourceType.StructuredBufferReadWrite or - ResourceType.TextureReadWrite => uavIndex++, - - ResourceType.Sampler => samplerIndex++, - - _ => binding.Index - } - }; - } - } - break; - - case Backend.Metal: - { - uint bufferIndex = 0; - uint textureIndex = 0; - uint samplerIndex = 0; - - for (int i = 0; i < bindings.Length; i++) - { - ref ResourceBinding binding = ref bindings[i]; - - binding = binding with - { - Index = binding.Type switch - { - ResourceType.ConstantBuffer or - ResourceType.StructuredBuffer or - ResourceType.StructuredBufferReadWrite or - ResourceType.AccelerationStructure => bufferIndex++, - - ResourceType.Texture or - ResourceType.TextureReadWrite => textureIndex++, - - ResourceType.Sampler => samplerIndex++, - - _ => binding.Index - } - }; - } - } - break; - - case Backend.Vulkan: - { - for (int i = 0; i < bindings.Length; i++) - { - ref ResourceBinding binding = ref bindings[i]; - - binding = binding with { Index = (uint)i }; - } - } - break; - } - - return bindings; - } -} -``` - -| Backend | Index Strategy | -|---------|---------------| -| **DirectX 12** | Separate counters per register type (CBV, SRV, UAV, Sampler) | -| **Metal** | Separate counters per resource category (Buffer, Texture, Sampler) | -| **Vulkan** | Sequential binding indices | - ### CocoaHelper.cs Required for macOS to create a `CAMetalLayer` for the window surface: diff --git a/documents/tutorials/getting-started/spinning-cube.md b/documents/tutorials/getting-started/spinning-cube.md index b1fae9b3..2df2ba46 100644 --- a/documents/tutorials/getting-started/spinning-cube.md +++ b/documents/tutorials/getting-started/spinning-cube.md @@ -63,10 +63,14 @@ internal unsafe class SpinningCubeRenderer : IRenderer } """; + private static readonly ResourceBinding[] ResourceBindings = + [ + new() { Type = ResourceType.ConstantBuffer, Count = 1 } + ]; + private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Buffer constantsBuffer; - private readonly ResourceLayout resourceLayout; private readonly ResourceTable resourceTable; private readonly GraphicsPipeline pipeline; @@ -119,19 +123,8 @@ internal unsafe class SpinningCubeRenderer : IRenderer Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite }); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new ResourceBinding() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Vertex } - ) - }); - - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constantsBuffer] - }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); + resourceTable.Write(0, constantsBuffer); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); @@ -150,7 +143,7 @@ internal unsafe class SpinningCubeRenderer : IRenderer }, Vertex = vertexShader, Pixel = pixelShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output @@ -199,7 +192,6 @@ internal unsafe class SpinningCubeRenderer : IRenderer { pipeline.Dispose(); resourceTable.Dispose(); - resourceLayout.Dispose(); constantsBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); diff --git a/documents/tutorials/getting-started/textured-quad.md b/documents/tutorials/getting-started/textured-quad.md index eafffde4..c5532e70 100644 --- a/documents/tutorials/getting-started/textured-quad.md +++ b/documents/tutorials/getting-started/textured-quad.md @@ -1,6 +1,6 @@ # Textured Quad -In this tutorial, you'll render a textured quad using an index buffer, a texture loaded from file, and a sampler. This introduces resource binding — connecting GPU resources like textures and samplers to shaders through resource layouts and tables. +In this tutorial, you'll render a textured quad using an index buffer, a texture loaded from file, and a sampler. This introduces resource binding — connecting GPU resources like textures and samplers to shaders through resource bindings and tables. ## Overview @@ -9,8 +9,7 @@ This tutorial covers: - Using an **index buffer** to share vertices between triangles - Loading a **texture** from an image file - Creating a **sampler** with filtering and address modes -- Defining a **resource layout** and **resource table** to bind resources to shaders -- Using `BindingHelper` for cross-backend resource binding +- Defining a **resource table** to bind resources to shaders ## The Renderer Class @@ -54,11 +53,16 @@ internal unsafe class TexturedQuadRenderer : IRenderer } """; + private static readonly ResourceBinding[] ResourceBindings = + [ + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } + ]; + private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Texture texture; private readonly Sampler sampler; - private readonly ResourceLayout resourceLayout; private readonly ResourceTable resourceTable; private readonly GraphicsPipeline pipeline; @@ -101,20 +105,9 @@ internal unsafe class TexturedQuadRenderer : IRenderer MaxLod = uint.MaxValue }); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.Texture, Count = 1, StageFlags = ShaderStageFlags.Pixel }, - new() { Type = ResourceType.Sampler, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ) - }); - - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [texture, sampler] - }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); + resourceTable.Write(0, texture); + resourceTable.Write(1, sampler); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); @@ -133,7 +126,7 @@ internal unsafe class TexturedQuadRenderer : IRenderer }, Vertex = vertexShader, Pixel = pixelShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output @@ -175,7 +168,6 @@ internal unsafe class TexturedQuadRenderer : IRenderer { pipeline.Dispose(); resourceTable.Dispose(); - resourceLayout.Dispose(); sampler.Dispose(); texture.Dispose(); indexBuffer.Dispose(); @@ -290,26 +282,15 @@ sampler = App.Context.CreateSampler(new() ### Resource Binding -Resources are exposed to shaders through a layout and table: +Resources are exposed to shaders through a resource table: ```csharp -resourceLayout = App.Context.CreateResourceLayout(new() -{ - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.Texture, Count = 1, StageFlags = ShaderStageFlags.Pixel }, - new() { Type = ResourceType.Sampler, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ) -}); - -resourceTable = App.Context.CreateResourceTable(new() -{ - Layout = resourceLayout, - Resources = [texture, sampler] -}); +resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); +resourceTable.Write(0, texture); +resourceTable.Write(1, sampler); ``` -`BindingHelper.Bindings` assigns the correct binding indices per backend. `StageFlags` controls which shader stages can access each resource. +`ResourceTable.Write(binding, resource)` assigns actual GPU resources to each slot by index. ### Rendering diff --git a/documents/tutorials/index.md b/documents/tutorials/index.md index 77947dd4..12bfa72e 100644 --- a/documents/tutorials/index.md +++ b/documents/tutorials/index.md @@ -8,9 +8,9 @@ New to Zenith.NET? Start here to set up your environment and render your first g | Tutorial | Description | |----------|-------------| -| [Prerequisites](getting-started/prerequisites.md) | Set up your development environment with `App` framework, `IRenderer` interface, and `BindingHelper` | +| [Prerequisites](getting-started/prerequisites.md) | Set up your development environment with `App` framework and `IRenderer` interface | | [Hello Triangle](getting-started/hello-triangle.md) | Create vertex buffers, compile Slang shaders, and build your first graphics pipeline | -| [Textured Quad](getting-started/textured-quad.md) | Load textures, create samplers, and bind resources with `ResourceLayout` and `ResourceTable` | +| [Textured Quad](getting-started/textured-quad.md) | Load textures, create samplers, and bind resources with `ResourceTable` | | [Spinning Cube](getting-started/spinning-cube.md) | Use constant buffers for MVP matrices and render 3D geometry with depth testing | ## Intermediate @@ -48,7 +48,7 @@ All tutorials share the same `App` framework. Run `dotnet run` and select a tuto | Stage | You Will Learn | |-------|----------------| -| **Prerequisites** | Set up the application framework, graphics context, and cross-platform resource binding | +| **Prerequisites** | Set up the application framework, graphics context, and resource binding | | **Hello Triangle** | Create GPU buffers, compile shaders, configure graphics pipelines, and submit draw commands | | **Textured Quad** | Load and sample textures, use index buffers, and bind shader resources | | **Spinning Cube** | Pass data to shaders via constant buffers, implement 3D transformations, and enable depth testing | diff --git a/documents/tutorials/intermediate/compute-shader.md b/documents/tutorials/intermediate/compute-shader.md index 8d3be520..8910d74d 100644 --- a/documents/tutorials/intermediate/compute-shader.md +++ b/documents/tutorials/intermediate/compute-shader.md @@ -48,9 +48,14 @@ internal class ComputeShaderRenderer : IRenderer } """; + private static readonly ResourceBinding[] ResourceBindings = + [ + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.TextureReadWrite, Count = 1 } + ]; + private readonly Texture inputTexture; private readonly Texture outputTexture; - private readonly ResourceLayout resourceLayout; private readonly ResourceTable resourceTable; private readonly ComputePipeline pipeline; @@ -73,27 +78,16 @@ internal class ComputeShaderRenderer : IRenderer Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess }); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.Texture, Count = 1, StageFlags = ShaderStageFlags.Compute }, - new() { Type = ResourceType.TextureReadWrite, Count = 1, StageFlags = ShaderStageFlags.Compute } - ) - }); - - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [inputTexture, outputTexture] - }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); + resourceTable.Write(0, inputTexture); + resourceTable.Write(1, outputTexture); using Shader computeShader = App.Context.LoadShaderFromSource(ShaderSource, "CSMain", ShaderStageFlags.Compute); pipeline = App.Context.CreateComputePipeline(new() { Compute = computeShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, ThreadGroupSizeX = ThreadGroupSize, ThreadGroupSizeY = ThreadGroupSize, ThreadGroupSizeZ = 1 @@ -149,7 +143,6 @@ internal class ComputeShaderRenderer : IRenderer { pipeline.Dispose(); resourceTable.Dispose(); - resourceLayout.Dispose(); outputTexture.Dispose(); inputTexture.Dispose(); } @@ -215,7 +208,7 @@ Unlike the graphics pipeline, a compute pipeline has no vertex/pixel stages or r pipeline = App.Context.CreateComputePipeline(new() { Compute = computeShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, ThreadGroupSizeX = ThreadGroupSize, ThreadGroupSizeY = ThreadGroupSize, ThreadGroupSizeZ = 1 diff --git a/documents/tutorials/intermediate/indirect-drawing.md b/documents/tutorials/intermediate/indirect-drawing.md index f2f2380c..e1882be6 100644 --- a/documents/tutorials/intermediate/indirect-drawing.md +++ b/documents/tutorials/intermediate/indirect-drawing.md @@ -77,12 +77,17 @@ internal unsafe class IndirectDrawingRenderer : IRenderer } """; + private static readonly ResourceBinding[] ResourceBindings = + [ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.StructuredBuffer, Count = 1 } + ]; + private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Buffer indirectBuffer; private readonly Buffer constantsBuffer; private readonly Buffer instanceBuffer; - private readonly ResourceLayout resourceLayout; private readonly ResourceTable resourceTable; private readonly GraphicsPipeline pipeline; @@ -159,20 +164,9 @@ internal unsafe class IndirectDrawingRenderer : IRenderer Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.MapWrite }); - resourceLayout = App.Context.CreateResourceLayout(new() - { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Vertex }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Vertex } - ) - }); - - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constantsBuffer, instanceBuffer] - }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); + resourceTable.Write(0, constantsBuffer); + resourceTable.Write(1, instanceBuffer); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); @@ -191,7 +185,7 @@ internal unsafe class IndirectDrawingRenderer : IRenderer }, Vertex = vertexShader, Pixel = pixelShader, - ResourceLayout = resourceLayout, + ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = App.FrameBuffer.Output @@ -266,7 +260,6 @@ internal unsafe class IndirectDrawingRenderer : IRenderer { pipeline.Dispose(); resourceTable.Dispose(); - resourceLayout.Dispose(); instanceBuffer.Dispose(); constantsBuffer.Dispose(); indirectBuffer.Dispose(); From 903d08679a5f7526e212fdafd3eef624c69fac97 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Apr 2026 23:23:25 +0800 Subject: [PATCH 016/466] Refactor resource bindings to static readonly fields Refactored resource binding arrays in PathTracingRenderer and RasterizationRenderer to use private static readonly fields (ResourceBindings) instead of instance fields. Updated all references accordingly to reduce duplication and share definitions across instances. --- .../Experiments/CornellBox/Renderers/PathTracingRenderer.cs | 6 +++--- .../CornellBox/Renderers/RasterizationRenderer.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 1a43f269..a34d941c 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -12,7 +12,7 @@ internal unsafe class PathTracingRenderer : Renderer { private const uint ThreadGroupSize = 16; - private readonly ResourceBinding[] resourceBindings = + private static readonly ResourceBinding[] ResourceBindings = [ new() { Type = ResourceType.AccelerationStructure, Count = 1 }, new() { Type = ResourceType.ConstantBuffer, Count = 1 }, @@ -121,7 +121,7 @@ public PathTracingRenderer() pipeline = App.Context.CreateComputePipeline(new() { Compute = computeShader, - ResourceBindings = resourceBindings, + ResourceBindings = ResourceBindings, ThreadGroupSizeX = ThreadGroupSize, ThreadGroupSizeY = ThreadGroupSize, ThreadGroupSizeZ = 1 @@ -174,7 +174,7 @@ public override void Render(CommandBuffer commandBuffer) Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess }); - resourceTable = App.Context.CreateResourceTable(new() { Bindings = resourceBindings }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); resourceTable.Write(0, tlas); resourceTable.Write(1, cameraBuffer); resourceTable.Write(2, vertexBuffer); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 6bda4827..79ce9cb7 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -10,7 +10,7 @@ namespace CornellBox.Renderers; internal unsafe class RasterizationRenderer : Renderer { - private readonly ResourceBinding[] resourceBindings = + private static readonly ResourceBinding[] ResourceBindings = [ new() { Type = ResourceType.ConstantBuffer, Count = 1 }, new() { Type = ResourceType.StructuredBuffer, Count = 1 } @@ -61,7 +61,7 @@ public RasterizationRenderer() Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite }); - resourceTable = App.Context.CreateResourceTable(new() { Bindings = resourceBindings }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); resourceTable.Write(0, constantBuffer); resourceTable.Write(1, materialBuffer); @@ -82,7 +82,7 @@ public RasterizationRenderer() }, Vertex = vertexShader, Pixel = pixelShader, - ResourceBindings = resourceBindings, + ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, Output = FrameBuffer.Output From feec34751b86e51c977fbf5fc8accd926f066237 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Apr 2026 11:24:22 +0800 Subject: [PATCH 017/466] Expand and restructure Zenith.NET documentation Major overhaul of all documentation: - Replace "under construction" notes with detailed, example-driven content for all core concepts and features. - Add new sections, tables, and code samples for graphics context, command model, resource binding, buffers, textures, samplers, graphics, compute, mesh shading, ray tracing, and best practices. - Update index and navigation to link to new granular docs. - Rewrite backend selection and UI integration docs with runtime selection/code samples. - Expand best practices with lifecycle, batching, alignment, performance, and debugging guidance. - Add full pipeline setup and shader examples for mesh shading and ray tracing. - Clarify terminology and ensure consistency throughout. - Transform docs from outline to comprehensive reference for users. --- documents/docs/best-practices.md | 102 +++++++++- documents/docs/concepts/command-model.md | 152 ++++++++++++++- documents/docs/concepts/graphics-context.md | 107 ++++++++++- documents/docs/concepts/resource-binding.md | 140 +++++++++++++- documents/docs/features/compute.md | 113 ++++++++++- documents/docs/features/graphics.md | 160 +++++++++++++++- documents/docs/features/mesh-shading.md | 156 +++++++++++++++- documents/docs/features/ray-tracing.md | 176 +++++++++++++++++- documents/docs/index.md | 87 +++------ documents/docs/platform/backend-selection.md | 54 +++++- documents/docs/platform/ui-frameworks.md | 81 +++++++- documents/docs/resources/buffers.md | 103 +++++++++- documents/docs/resources/samplers.md | 96 +++++++++- documents/docs/resources/textures.md | 131 ++++++++++++- documents/tutorials/advanced/mesh-shading.md | 2 +- .../getting-started/textured-quad.md | 2 +- 16 files changed, 1507 insertions(+), 155 deletions(-) diff --git a/documents/docs/best-practices.md b/documents/docs/best-practices.md index f95fb574..ce4224ff 100644 --- a/documents/docs/best-practices.md +++ b/documents/docs/best-practices.md @@ -1,12 +1,98 @@ # Best Practices -> [!NOTE] -> This page is under construction. +Guidelines for getting the most out of Zenith.NET. -This topic covers: +## Resource Lifecycle -- Resource lifecycle management -- Command batching strategies -- Data alignment requirements -- Common performance pitfalls -- Debugging tips +- **Dispose resources** when no longer needed. All GPU resources implement `IDisposable`. +- **Use `using` statements** for short-lived resources like shaders used only during pipeline creation: + +```csharp +using Shader vertexShader = context.LoadShaderFromSource(source, "VSMain", ShaderStageFlags.Vertex); +using Shader pixelShader = context.LoadShaderFromSource(source, "PSMain", ShaderStageFlags.Pixel); + +pipeline = context.CreateGraphicsPipeline(new() { Vertex = vertexShader, Pixel = pixelShader, /* ... */ }); +``` + +- **Create resources upfront** rather than per-frame to avoid allocation overhead. +- **Dispose in reverse order** — dispose dependent resources before the objects they reference, and dispose the `GraphicsContext` last. + +## Command Recording + +- **Batch similar operations** to minimize pipeline and resource table switches. +- **Minimize render pass switches** by grouping draws with the same frame buffer. +- **Reuse command buffers** — the queue automatically pools and recycles them. Just call `CommandBuffer()`. +- **Use `waitForCompletion: true`** sparingly — it stalls the CPU until the GPU finishes. Prefer submitting without waiting when possible. + +## Resource Binding + +- **Ensure `ResourceBinding[]` layout compatibility** between pipeline descriptors and resource tables — same types and counts at each index. +- **Call `Write()` to update bindings** at any time — avoid recreating resource tables for dynamic resource updates. +- **Pass resource tables to `BeginRenderPass`** to allow the backend to perform resource transitions. + +## Data Alignment + +Follow the [alignment constants](concepts/graphics-context.md#alignment-constants) defined in `GraphicsContext` for buffer and texture data. + +Structs passed to the GPU via constant or structured buffers must use `[StructLayout(LayoutKind.Explicit)]` with 16-byte aligned field offsets. DirectX 12, Metal, and Vulkan have different alignment rules for buffer data — using 16-byte alignment ensures compatibility across all three backends. Avoid using `float3` in shader struct declarations for the same reason; use `float4` with padding instead. Vertex input data does not require this alignment. + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 240)] +file struct Constants +{ + [FieldOffset(0)] + public Matrix4x4 Model; + + [FieldOffset(64)] + public Matrix4x4 View; + + [FieldOffset(128)] + public Matrix4x4 Projection; + + [FieldOffset(192)] + public Vector3 LightPos; // 12 bytes, padded to 16 + + [FieldOffset(208)] + public Vector3 LightColor; // 12 bytes, padded to 16 + + [FieldOffset(224)] + public Vector3 CameraPos; // 12 bytes, padded to 16 +} +``` + +## Performance + +- **Avoid per-frame resource creation.** Create buffers, textures, pipelines, and resource tables once and reuse. +- **Use `MapWrite` buffers** for frequently updated data (constant buffers, instance data). `Upload()` is convenient and efficient for these cases. +- **Use the Copy queue** for asynchronous data transfers when the Graphics queue is busy. +- **Prefer indexed drawing** (`DrawIndexed`) over non-indexed (`Draw`) to reduce vertex processing through vertex reuse. +- **Use indirect drawing** (`DrawIndexedIndirect`, `DispatchMeshIndirect`) for GPU-driven rendering to reduce CPU overhead. + +## Debugging + +- **Enable the validation layer** during development: `GraphicsContext.Create*(useValidationLayer: true)`. +- **Subscribe to `ValidationMessage`** to capture both framework-level and driver-level diagnostics. +- **Use debug events** to annotate command streams for GPU debugging tools (RenderDoc, PIX, Xcode GPU Debugger): + +```csharp +commandBuffer.BeginDebugEvent("Shadow Pass"); +// ... shadow draw calls ... +commandBuffer.EndDebugEvent(); +``` + +- **Name resources** for easier identification in GPU debuggers: + +```csharp +buffer.Name = "VertexBuffer"; +texture.Name = "Albedo"; +``` + +## Common Pitfalls + +| Pitfall | Solution | +|---------|----------| +| Forgetting to dispose GPU resources | Use `using` or explicit `Dispose()` calls | +| Mismatched resource bindings between pipeline and table | Ensure the `ResourceBinding[]` layout is compatible (same types and counts at each index) | +| Constant buffer struct size not matching shader layout | Use `LayoutKind.Explicit` with 16-byte aligned field offsets; avoid `float3` in shaders | +| Calling `WaitIdle()` every frame | Only wait when synchronization is actually needed | +| Creating pipelines per-frame | Create once, reuse across frames | diff --git a/documents/docs/concepts/command-model.md b/documents/docs/concepts/command-model.md index 9434a19e..1be33676 100644 --- a/documents/docs/concepts/command-model.md +++ b/documents/docs/concepts/command-model.md @@ -1,11 +1,149 @@ # Command Model -> [!NOTE] -> This page is under construction. +Zenith.NET uses an explicit command recording model. GPU work is recorded into command buffers and then submitted to command queues for execution. + +## Command Queues + +`GraphicsContext` provides three command queues: + +| Queue | Type | Capabilities | +|-------|------|-------------| +| `Graphics` | `CommandQueueType.Graphics` | Draw, dispatch, mesh dispatch, copy, acceleration structure builds | +| `Compute` | `CommandQueueType.Compute` | Compute dispatch, copy, acceleration structure builds | +| `Copy` | `CommandQueueType.Copy` | Data transfer only | + +Each queue has two methods: + +| Method | Description | +|--------|-------------| +| `CommandBuffer()` | Obtain a command buffer from the queue's internal pool | +| `WaitIdle()` | Block until all submitted work on this queue completes | + +## Command Buffer Workflow + +The typical workflow for each frame: + +```csharp +// 1. Obtain a command buffer +CommandBuffer commandBuffer = context.Graphics.CommandBuffer(); + +// 2. Record commands +commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetResourceTable(resourceTable); +commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); +commandBuffer.Draw(3, 1, 0, 0); +commandBuffer.EndRenderPass(); + +// 3. Submit to the GPU +commandBuffer.Submit(waitForCompletion: true); +``` + +## Render Pass Commands + +A render pass scopes all draw operations to a frame buffer: + +```csharp +commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTables...); +// ... draw commands ... +commandBuffer.EndRenderPass(); +``` + +`BeginRenderPass` accepts optional `ResourceTable` parameters for resource preprocessing (transition barriers). + +## Draw Commands + +| Method | Description | +|--------|-------------| +| `Draw(vertexCount, instanceCount, firstVertex, firstInstance)` | Direct non-indexed draw | +| `DrawIndexed(indexCount, instanceCount, firstIndex, vertexOffset, firstInstance)` | Indexed draw | +| `DrawIndirect(indirectBuffer, offsetInBytes, drawCount)` | GPU-driven non-indexed draw | +| `DrawIndexedIndirect(indirectBuffer, offsetInBytes, drawCount)` | GPU-driven indexed draw | + +## Compute Commands + +Compute dispatches do not require a render pass: + +```csharp +commandBuffer.SetPipeline(computePipeline); +commandBuffer.SetResourceTable(resourceTable); +commandBuffer.Dispatch(groupCountX, groupCountY, groupCountZ); +``` + +| Method | Description | +|--------|-------------| +| `Dispatch(groupCountX, groupCountY, groupCountZ)` | Direct compute dispatch | +| `DispatchIndirect(indirectBuffer, offsetInBytes)` | GPU-driven compute dispatch | -This topic covers: +## Mesh Shading Commands -- The relationship between `CommandQueue` and `CommandBuffer` -- Command recording workflow -- Submission and synchronization -- Command buffer pooling and reuse mechanism +| Method | Description | +|--------|-------------| +| `DispatchMesh(groupCountX, groupCountY, groupCountZ)` | Dispatch mesh shader groups | +| `DispatchMeshIndirect(indirectBuffer, offsetInBytes, dispatchCount)` | GPU-driven mesh dispatch | + +## Copy Commands + +Copy operations transfer data between buffers and textures: + +| Method | Description | +|--------|-------------| +| `CopyBuffer` | Buffer-to-buffer copy | +| `CopyTexture` | Texture-to-texture copy | +| `CopyBufferToTexture` | Buffer-to-texture copy | +| `CopyTextureToBuffer` | Texture-to-buffer copy (readback) | +| `ResolveTexture` | Resolve a multisampled texture to a single-sampled target | + +## Upload Commands + +Upload data directly within a command buffer: + +```csharp +commandBuffer.Upload(buffer, offsetInBytes, dataSpan); +commandBuffer.Upload(texture, slice, offset, extent, dataSpan); +``` + +## Acceleration Structure Commands + +| Method | Description | +|--------|-------------| +| `BuildAccelerationStructure(BottomLevelAccelerationStructureDesc)` | Build a BLAS from triangle or AABB geometry | +| `BuildAccelerationStructure(TopLevelAccelerationStructureDesc)` | Build a TLAS from BLAS instances | +| `UpdateAccelerationStructure(tlas, newDesc)` | Update an existing TLAS in-place | + +## Query Commands + +| Method | Description | +|--------|-------------| +| `BeginQuery(queryHeap, index)` | Begin an occlusion query | +| `EndQuery(queryHeap, index)` | End an occlusion query | +| `WriteTimestamp(queryHeap, index)` | Write a GPU timestamp | + +## Debug Commands + +| Method | Description | +|--------|-------------| +| `BeginDebugEvent(label)` | Begin a named debug region (visible in GPU debuggers) | +| `EndDebugEvent()` | End the current debug region | +| `InsertDebugMarker(label)` | Insert a point-in-time marker | + +## Submission + +```csharp +// Submit and return immediately +commandBuffer.Submit(); + +// Submit and block until the GPU finishes +commandBuffer.Submit(waitForCompletion: true); +``` + +## Synchronization + +Use `WaitIdle()` when you need to ensure the GPU has finished all prior work on a queue: + +```csharp +context.Graphics.WaitIdle(); +``` + +> [!NOTE] +> Command buffers are pooled and recycled by the queue. You do not need to manually manage their lifecycle — just call `CommandBuffer()` to get the next available one. diff --git a/documents/docs/concepts/graphics-context.md b/documents/docs/concepts/graphics-context.md index d68b3b89..2551250e 100644 --- a/documents/docs/concepts/graphics-context.md +++ b/documents/docs/concepts/graphics-context.md @@ -1,11 +1,104 @@ # Graphics Context -> [!NOTE] -> This page is under construction. +The `GraphicsContext` is the central hub of Zenith.NET. All GPU resources are created through it, and it provides access to the three command queues used for submitting work to the GPU. -This topic covers: +All GPU resources (buffers, textures, pipelines, resource tables, acceleration structures, etc.) implement `IDisposable` and must be explicitly disposed when no longer needed. Dispose all resources before disposing the `GraphicsContext` itself. -- GraphicsContext creation methods for each backend -- Three backend options: DirectX 12, Metal 4, Vulkan 1.4 -- Querying device capabilities via `Capabilities` -- Handling validation messages via `ValidationMessage` event +## Creating a Context + +Backend-specific contexts are created via static extension methods: + +```csharp +// Windows — DirectX 12 +GraphicsContext context = GraphicsContext.CreateDirectX12(useValidationLayer: true); + +// Apple — Metal 4 +GraphicsContext context = GraphicsContext.CreateMetal(useValidationLayer: true); + +// Cross-platform — Vulkan 1.4 +GraphicsContext context = GraphicsContext.CreateVulkan(useValidationLayer: true); +``` + +The `useValidationLayer` parameter enables runtime validation — invaluable during development, but should be disabled in production for performance. + +## Properties + +| Property | Type | Description | +|----------|------|-------------| +| `Backend` | `Backend` | The active backend (`DirectX12`, `Metal`, or `Vulkan`) | +| `Capabilities` | `Capabilities` | Device name and feature support queries | +| `Graphics` | `CommandQueue` | Queue for draw, dispatch, and copy commands | +| `Compute` | `CommandQueue` | Queue for compute dispatches and copies | +| `Copy` | `CommandQueue` | Queue for data transfer only | + +## Resource Creation + +All GPU resources are created through `GraphicsContext`: + +| Method | Returns | Description | +|--------|---------|-------------| +| `CreateSwapChain` | `SwapChain` | Presentation chain for a window surface | +| `CreateFrameBuffer` | `FrameBuffer` | Off-screen render target attachments | +| `CreateShader` | `Shader` | Compiled shader module | +| `CreateBuffer` | `Buffer` | GPU buffer (vertex, index, constant, structured, etc.) | +| `CreateBufferView` | `BufferView` | View into a buffer for shader binding | +| `CreateTexture` | `Texture` | GPU texture (2D, 3D, Cube, Array) | +| `CreateTextureView` | `TextureView` | View into a texture for shader binding | +| `CreateSampler` | `Sampler` | Texture sampling and filtering configuration | +| `CreateResourceTable` | `ResourceTable` | Declares and holds resource bindings for shaders | +| `CreateGraphicsPipeline` | `GraphicsPipeline` | Rasterization pipeline | +| `CreateComputePipeline` | `ComputePipeline` | Compute dispatch pipeline | +| `CreateMeshShadingPipeline` | `MeshShadingPipeline` | Mesh shading pipeline | +| `CreateQueryHeap` | `QueryHeap` | GPU query heap for timestamps and occlusion | + +## Alignment Constants + +Zenith.NET defines alignment constants for cross-platform compatibility: + +| Constant | Value | Purpose | +|----------|:-----:|---------| +| `ConstantBufferAlignment` | 256 bytes | Minimum alignment for constant buffer data | +| `TextureRowPitchAlignment` | 256 bytes | Alignment for texture row pitch | +| `TextureDepthPitchAlignment` | 512 bytes | Alignment for 3D texture depth slice pitch | + +## Capabilities + +Query device features before using optional capabilities: + +```csharp +Console.WriteLine($"Device: {context.Capabilities.DeviceName}"); + +if (context.Capabilities.RayTracingSupported) +{ + // Safe to use acceleration structures and RayQuery +} + +if (context.Capabilities.MeshShadingSupported) +{ + // Safe to use mesh shading pipelines +} +``` + +| Property | Type | Description | +|----------|------|-------------| +| `DeviceName` | `string` | Name of the GPU device | +| `RayTracingSupported` | `bool` | Whether BLAS/TLAS and RayQuery are available | +| `MeshShadingSupported` | `bool` | Whether mesh and amplification shaders are available | + +## Validation Messages + +When the validation layer is enabled, subscribe to `ValidationMessage` to receive diagnostic messages: + +```csharp +context.ValidationMessage += (sender, args) => +{ + Console.WriteLine($"[{args.Source} - {args.Severity}] {args.Message}"); +}; +``` + +| Property | Type | Description | +|----------|------|-------------| +| `Source` | `MessageSource` | `Framework` (Zenith.NET checks) or `GraphicsAPI` (backend driver) | +| `Severity` | `MessageSeverity` | `Error`, `Warning`, or `Message` | +| `Message` | `string` | The diagnostic message text | +| `Timestamp` | `DateTimeOffset` | When the message was generated | diff --git a/documents/docs/concepts/resource-binding.md b/documents/docs/concepts/resource-binding.md index 89df1b5d..5bbf54ab 100644 --- a/documents/docs/concepts/resource-binding.md +++ b/documents/docs/concepts/resource-binding.md @@ -1,10 +1,140 @@ # Resource Binding +Resources are bound to shaders through `ResourceTable`. Each binding in a table maps to a shader resource declaration (constant buffer, texture, sampler, etc.). + +## Binding Model + +The binding model has two components: + +1. **`ResourceBinding`** — Declares the type and count for a single binding index +2. **`ResourceTable`** — Holds the actual GPU resources, written by index + +The `ResourceTable` is created with a `ResourceBinding[]` that must be layout-compatible with the pipeline’s `ResourceBinding[]`. As long as the binding layout matches, the table can be used with the pipeline. + +## ResourceBinding + +A `ResourceBinding` declares what a single binding index expects: + +```csharp +ResourceBinding[] bindings = +[ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } +]; +``` + +| Field | Type | Description | +|-------|------|-------------| +| `Type` | `ResourceType` | The kind of resource at this binding | +| `Count` | `uint` | Number of resources at this binding (use `1` for single resources, `> 1` for arrays) | + +### Resource Types + +| ResourceType | Shader Declaration | Bindable Types | +|-------------|-------------------|----------------| +| `ConstantBuffer` | `ConstantBuffer` | `Buffer`, `BufferView` | +| `StructuredBuffer` | `StructuredBuffer` | `Buffer`, `BufferView` | +| `StructuredBufferReadWrite` | `RWStructuredBuffer` | `Buffer`, `BufferView` | +| `Texture` | `Texture2D`, `Texture3D`, etc. | `Texture`, `TextureView` | +| `TextureReadWrite` | `RWTexture2D`, etc. | `Texture`, `TextureView` | +| `Sampler` | `SamplerState` | `Sampler` | +| `AccelerationStructure` | `RaytracingAccelerationStructure` | `TopLevelAccelerationStructure` | + +## Creating a ResourceTable + +```csharp +ResourceTable resourceTable = context.CreateResourceTable(new() +{ + Bindings = bindings +}); +``` + +## Writing Resources + +Assign GPU resources to each binding index using `Write()`: + +```csharp +resourceTable.Write(0, constantBuffer); +resourceTable.Write(1, texture); +resourceTable.Write(2, sampler); +``` + +The binding index corresponds to the order in the `ResourceBinding[]` array. Resources can be updated at any time by calling `Write()` again. + +### Array Bindings + +For bindings with `Count > 1`, pass multiple resources: + +```csharp +resourceTable.Write(0, texture0, texture1, texture2); +``` + +## Connecting Pipeline and Table + +The pipeline’s `ResourceBindings` and the resource table’s `Bindings` must have compatible layouts — the same types and counts at each index, but they do not need to be the same array instance: + +```csharp +// Pipeline bindings +ResourceBinding[] pipelineBindings = +[ + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } +]; + +pipeline = context.CreateGraphicsPipeline(new() +{ + // ... + ResourceBindings = pipelineBindings, + // ... +}); + +// Table bindings — layout-compatible with the pipeline +ResourceBinding[] tableBindings = +[ + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } +]; + +resourceTable = context.CreateResourceTable(new() { Bindings = tableBindings }); +resourceTable.Write(0, texture); +resourceTable.Write(1, sampler); +``` + +## Binding During Rendering + +Set the resource table before draw or dispatch calls: + +```csharp +commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetResourceTable(resourceTable); +commandBuffer.Draw(3, 1, 0, 0); + +commandBuffer.EndRenderPass(); +``` + > [!NOTE] -> This page is under construction. +> Pass the `ResourceTable` to `BeginRenderPass` as well — this allows the backend to perform any necessary resource transitions before rendering begins. + +## Shader Binding Order + +Shader resource declarations map to binding indices in declaration order. For example: + +```hlsl +ConstantBuffer constants; // binding 0 +Texture2D albedo; // binding 1 +SamplerState linearSampler; // binding 2 +``` -This topic covers: +Corresponds to: -- Creating and writing resources to `ResourceTable` -- Sharing binding definitions between pipelines and resource tables -- Dynamic resource updates with `ResourceTable.Write()` +```csharp +ResourceBinding[] bindings = +[ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, // 0 → constants + new() { Type = ResourceType.Texture, Count = 1 }, // 1 → albedo + new() { Type = ResourceType.Sampler, Count = 1 } // 2 → linearSampler +]; +``` diff --git a/documents/docs/features/compute.md b/documents/docs/features/compute.md index 06a584ee..a7683a3e 100644 --- a/documents/docs/features/compute.md +++ b/documents/docs/features/compute.md @@ -1,10 +1,111 @@ # Compute -> [!NOTE] -> This page is under construction. +Compute pipelines run general-purpose GPU computations using compute shaders. They operate outside of render passes and work on buffers and textures through resource bindings. -This topic covers: +## Compute Pipeline -- Thread group size configuration -- Dispatching compute work with `Dispatch` -- Resource sharing with graphics pipelines +```csharp +ComputePipeline pipeline = context.CreateComputePipeline(new ComputePipelineDesc +{ + Compute = computeShader, + ResourceBindings = resourceBindings, + ThreadGroupSizeX = 16, + ThreadGroupSizeY = 16, + ThreadGroupSizeZ = 1 +}); +``` + +### ComputePipelineDesc + +| Field | Type | Description | +|-------|------|-------------| +| `Compute` | `Shader` | The compute shader | +| `ResourceBindings` | `ResourceBinding[]` | Shader resource binding declarations | +| `ThreadGroupSizeX` | `uint` | Thread group size in X (must match `[numthreads]`) | +| `ThreadGroupSizeY` | `uint` | Thread group size in Y | +| `ThreadGroupSizeZ` | `uint` | Thread group size in Z | + +The thread group size must match the `[numthreads]` attribute in the shader. + +## Shader + +Compute shaders use `[numthreads(X, Y, Z)]` to define thread group dimensions: + +```hlsl +Texture2D inputTexture; +RWTexture2D outputTexture; + +[numthreads(16, 16, 1)] +void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint width, height; + outputTexture.GetDimensions(width, height); + + if (dispatchThreadID.x >= width || dispatchThreadID.y >= height) + return; + + float4 color = inputTexture[dispatchThreadID.xy]; + outputTexture[dispatchThreadID.xy] = color; +} +``` + +Compile with `ShaderStageFlags.Compute`: + +```csharp +Shader computeShader = context.LoadShaderFromSource(source, "CSMain", ShaderStageFlags.Compute); +``` + +## Dispatching + +Compute dispatches do not use render passes: + +```csharp +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetResourceTable(resourceTable); +commandBuffer.Dispatch(groupCountX, groupCountY, groupCountZ); +``` + +Calculate dispatch group counts: + +```csharp +uint groupCountX = (width + threadGroupSize - 1) / threadGroupSize; +uint groupCountY = (height + threadGroupSize - 1) / threadGroupSize; +commandBuffer.Dispatch(groupCountX, groupCountY, 1); +``` + +### Indirect Dispatch + +For GPU-driven dispatch counts, store `IndirectDispatchArgs` in a buffer: + +```csharp +commandBuffer.DispatchIndirect(indirectBuffer, offsetInBytes: 0); +``` + +## Read-Write Resources + +Compute shaders can both read and write textures and buffers: + +| Shader Type | Resource Type | Description | +|------------|---------------|-------------| +| `Texture2D` | `ResourceType.Texture` | Read-only texture | +| `RWTexture2D` | `ResourceType.TextureReadWrite` | Read-write texture | +| `StructuredBuffer` | `ResourceType.StructuredBuffer` | Read-only buffer | +| `RWStructuredBuffer` | `ResourceType.StructuredBufferReadWrite` | Read-write buffer | + +Read-write textures require `TextureUsageFlags.UnorderedAccess`. Read-write buffers require `BufferUsageFlags.UnorderedAccess`. + +## Resource Sharing + +Compute results can be consumed by graphics pipelines and vice versa. For example, run a compute pass to process an image, then copy or display the result: + +```csharp +// Compute pass +commandBuffer.SetPipeline(computePipeline); +commandBuffer.SetResourceTable(computeTable); +commandBuffer.Dispatch(groupCountX, groupCountY, 1); + +// Copy result to frame buffer +commandBuffer.CopyTexture(outputTexture, default, default, + colorTarget, default, default, + new() { Width = width, Height = height, Depth = 1 }); +``` diff --git a/documents/docs/features/graphics.md b/documents/docs/features/graphics.md index ef2bcd26..fd22ba7f 100644 --- a/documents/docs/features/graphics.md +++ b/documents/docs/features/graphics.md @@ -1,11 +1,157 @@ # Graphics -> [!NOTE] -> This page is under construction. +The graphics pipeline handles traditional rasterization with vertex and pixel shaders, configurable render states, and input layouts. -This topic covers: +## Graphics Pipeline -- Render states: Rasterizer, DepthStencil, Blend -- Input layout configuration for vertex data -- Shader stages: Vertex, Pixel -- Output configuration +Create a pipeline by specifying shaders, render states, input layout, and output format: + +```csharp +GraphicsPipeline pipeline = context.CreateGraphicsPipeline(new GraphicsPipelineDesc +{ + RenderStates = new() + { + RasterizerState = RasterizerStates.CullBack, + DepthStencilState = DepthStencilStates.Default, + BlendState = BlendStates.Opaque + }, + Vertex = vertexShader, + Pixel = pixelShader, + ResourceBindings = resourceBindings, + InputLayouts = [inputLayout], + PrimitiveTopology = PrimitiveTopology.TriangleList, + Output = frameBuffer.Output +}); +``` + +### GraphicsPipelineDesc + +| Field | Type | Description | +|-------|------|-------------| +| `RenderStates` | `RenderStates` | Rasterizer, depth/stencil, and blend configuration | +| `Vertex` | `Shader` | Vertex shader | +| `Pixel` | `Shader` | Pixel (fragment) shader | +| `ResourceBindings` | `ResourceBinding[]` | Shader resource binding declarations | +| `InputLayouts` | `InputLayout[]` | Vertex attribute layouts | +| `PrimitiveTopology` | `PrimitiveTopology` | Primitive assembly mode | +| `Output` | `Output` | Render target format description | + +## Render States + +### Rasterizer State + +Controls face culling, fill mode, and depth bias: + +```csharp +RasterizerState rasterizerState = new() +{ + CullMode = CullMode.Back, + FillMode = FillMode.Solid, + FrontFace = FrontFace.CounterClockwise, + DepthClipEnable = true +}; +``` + +Built-in presets: `RasterizerStates.CullNone`, `RasterizerStates.CullBack`, `RasterizerStates.CullFront`. + +### Depth/Stencil State + +Controls depth testing and stencil operations: + +```csharp +DepthStencilState depthStencilState = new() +{ + DepthEnable = true, + DepthWriteEnable = true, + DepthFunc = ComparisonFunc.Less +}; +``` + +Built-in presets: `DepthStencilStates.Default` (depth enabled), `DepthStencilStates.None` (depth disabled). + +### Blend State + +Controls color blending for each render target: + +```csharp +BlendState blendState = new() +{ + RenderTarget0 = new() + { + BlendEnable = true, + SrcBlend = Blend.SrcAlpha, + DestBlend = Blend.InverseSrcAlpha, + BlendOp = BlendOp.Add, + SrcBlendAlpha = Blend.One, + DestBlendAlpha = Blend.InverseSrcAlpha, + BlendOpAlpha = BlendOp.Add, + Flags = ColorComponentFlags.All + } +}; +``` + +Built-in presets: `BlendStates.Opaque`, `BlendStates.AlphaBlend`, `BlendStates.Additive`. + +## Input Layout + +Input layouts describe how vertex data maps to shader inputs: + +```csharp +InputLayout inputLayout = new(); +inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); +inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.TexCoord }); +``` + +The order must match the shader's vertex input struct. Multiple input layouts can be used for multi-stream vertex data. + +## Shader Stages + +| Stage | Flag | Purpose | +|-------|------|---------| +| Vertex | `ShaderStageFlags.Vertex` | Transform vertices and pass data to the pixel stage | +| Pixel | `ShaderStageFlags.Pixel` | Compute the final color for each fragment | + +## Primitive Topology + +| Topology | Description | +|----------|-------------| +| `PointList` | Each vertex is a point | +| `LineList` | Every 2 vertices form a line | +| `LineStrip` | Connected line segments | +| `TriangleList` | Every 3 vertices form a triangle | +| `TriangleStrip` | Connected triangles sharing edges | + +## Rendering + +```csharp +CommandBuffer commandBuffer = context.Graphics.CommandBuffer(); + +commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetResourceTable(resourceTable); +commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); +commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); +commandBuffer.DrawIndexed(indexCount, 1, 0, 0, 0); +commandBuffer.EndRenderPass(); + +commandBuffer.Submit(waitForCompletion: true); +``` + +## Viewports and Scissors + +Custom viewports and scissors can be set within a render pass: + +```csharp +commandBuffer.SetViewports([new Viewport +{ + X = 0, Y = 0, + Width = width, Height = height, + MinDepth = 0, MaxDepth = 1 +}]); + +commandBuffer.SetScissors([new Scissor +{ + X = 0, Y = 0, + Width = width, Height = height +}]); +``` diff --git a/documents/docs/features/mesh-shading.md b/documents/docs/features/mesh-shading.md index aaff57ce..f0d00f99 100644 --- a/documents/docs/features/mesh-shading.md +++ b/documents/docs/features/mesh-shading.md @@ -1,10 +1,156 @@ # Mesh Shading +The mesh shading pipeline replaces the traditional vertex/geometry pipeline with programmable mesh and amplification shaders, enabling GPU-driven geometry processing. + > [!NOTE] -> This page is under construction. +> Mesh shading requires `context.Capabilities.MeshShadingSupported == true`. + +## Pipeline Stages + +| Stage | Shader Flag | Purpose | +|-------|-------------|---------| +| **Amplification** | `ShaderStageFlags.Amplification` | Optional. Controls how many mesh groups to dispatch (culling, LOD) | +| **Mesh** | `ShaderStageFlags.Mesh` | Required. Outputs vertices and triangles per thread group | +| **Pixel** | `ShaderStageFlags.Pixel` | Standard fragment shading | + +## Mesh Shading Pipeline + +```csharp +MeshShadingPipeline pipeline = context.CreateMeshShadingPipeline(new MeshShadingPipelineDesc +{ + RenderStates = new() + { + RasterizerState = RasterizerStates.CullBack, + DepthStencilState = DepthStencilStates.Default, + BlendState = BlendStates.Opaque + }, + Amplification = ampShader, // optional + Mesh = meshShader, + Pixel = pixelShader, + ResourceBindings = resourceBindings, + PrimitiveTopology = PrimitiveTopology.TriangleList, + Output = frameBuffer.Output, + AmplificationThreadGroupSizeX = 32, + AmplificationThreadGroupSizeY = 1, + AmplificationThreadGroupSizeZ = 1, + MeshThreadGroupSizeX = 128, + MeshThreadGroupSizeY = 1, + MeshThreadGroupSizeZ = 1 +}); +``` + +### MeshShadingPipelineDesc + +| Field | Type | Description | +|-------|------|-------------| +| `RenderStates` | `RenderStates` | Rasterizer, depth/stencil, and blend configuration | +| `Amplification` | `Shader?` | Optional amplification shader | +| `Mesh` | `Shader` | Mesh shader (required) | +| `Pixel` | `Shader` | Pixel shader | +| `ResourceBindings` | `ResourceBinding[]` | Resource binding declarations | +| `PrimitiveTopology` | `PrimitiveTopology` | Primitive assembly mode | +| `Output` | `Output` | Render target format description | +| `AmplificationThreadGroupSize*` | `uint` | Thread group size for the amplification stage | +| `MeshThreadGroupSize*` | `uint` | Thread group size for the mesh stage | + +## Amplification Shader + +The amplification shader runs per-group and decides how many mesh groups to spawn: + +```hlsl +struct Payload +{ + uint InstanceIndices[32]; +}; + +groupshared Payload s_payload; +groupshared uint s_visibleCount; + +[shader("amplification")] +[numthreads(32, 1, 1)] +void ASMain(uint groupID : SV_GroupID, uint threadID : SV_GroupThreadID) +{ + bool visible = !IsCulled(instancePosition); + + if (threadID == 0) + s_visibleCount = 0; + + GroupMemoryBarrierWithGroupSync(); + + if (visible) + { + uint offset; + InterlockedAdd(s_visibleCount, 1, offset); + s_payload.InstanceIndices[offset] = groupID * 32 + threadID; + } + + GroupMemoryBarrierWithGroupSync(); + + DispatchMesh(s_visibleCount, 1, 1, s_payload); +} +``` + +## Mesh Shader + +The mesh shader outputs vertices and triangle indices directly: + +```hlsl +struct VertexOutput +{ + float4 Position : SV_POSITION; + float3 Normal : WORLDNORMAL; + float3 Color : COLOR; +}; + +[shader("mesh")] +[numthreads(128, 1, 1)] +[outputtopology("triangle")] +void MSMain(uint groupID : SV_GroupID, uint threadID : SV_GroupThreadID, + in payload Payload meshPayload, + OutputVertices outVertices, + OutputIndices outIndices) +{ + SetMeshOutputCounts(vertexCount, triangleCount); + + if (threadID < vertexCount) + outVertices[threadID] = /* compute vertex */; + + if (threadID < triangleCount) + outIndices[threadID] = /* triangle indices */; +} +``` + +Geometry data is typically stored in `StructuredBuffer` resources rather than traditional vertex/index buffers. + +## Dispatching + +```csharp +commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetResourceTable(resourceTable); +commandBuffer.DispatchMesh(groupCountX, groupCountY, groupCountZ); +commandBuffer.EndRenderPass(); +``` + +### Indirect Dispatch + +```csharp +commandBuffer.DispatchMeshIndirect(indirectBuffer, offsetInBytes, dispatchCount); +``` + +## Common Patterns + +### Frustum Culling + +The amplification shader is commonly used for GPU-driven frustum culling: + +1. Each thread tests one instance against camera frustum planes +2. Visible instances are compacted into a `groupshared` payload using atomics +3. `DispatchMesh` spawns only the visible count -This topic covers: +### Meshlet Rendering -- Meshlet concept and data structures -- Mesh and amplification shaders -- Dispatching mesh work with `DispatchMesh` +Mesh shaders naturally map to meshlet-based rendering: +- Pre-split geometry into meshlets (groups of ≤ 64 vertices, ≤ 128 triangles) +- Each mesh shader group processes one meshlet +- Amplification shader selects visible meshlets diff --git a/documents/docs/features/ray-tracing.md b/documents/docs/features/ray-tracing.md index b988781a..ff67ab2a 100644 --- a/documents/docs/features/ray-tracing.md +++ b/documents/docs/features/ray-tracing.md @@ -1,12 +1,174 @@ # Ray Tracing +Zenith.NET supports hardware-accelerated ray tracing through a two-level acceleration structure hierarchy and `RayQuery` inline tracing in compute shaders. + > [!NOTE] -> This page is under construction. +> Ray tracing requires `context.Capabilities.RayTracingSupported == true`. + +## Acceleration Structures + +### Hierarchy + +| Level | Type | Contains | +|-------|------|----------| +| **BLAS** | `BottomLevelAccelerationStructure` | Triangle meshes or procedural AABBs | +| **TLAS** | `TopLevelAccelerationStructure` | References to BLAS instances with transforms | + +### Building a BLAS + +BLAS is built from geometry descriptions via a `CommandBuffer`: + +**Triangle geometry:** + +```csharp +BottomLevelAccelerationStructure blas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc +{ + Geometries = + [ + new() + { + Type = RayTracingGeometryType.Triangles, + Triangles = new() + { + VertexBuffer = vertexBuffer, + VertexFormat = PixelFormat.R32G32B32Float, + VertexCount = vertexCount, + VertexStrideInBytes = 12, + IndexBuffer = indexBuffer, + IndexFormat = IndexFormat.UInt32, + IndexCount = indexCount, + Transform = Matrix4x4.Identity + }, + Flags = RayTracingGeometryFlags.Opaque + } + ], + Flags = AccelerationStructureBuildFlags.PreferFastTrace +}); +``` + +**Procedural AABB geometry:** + +```csharp +BottomLevelAccelerationStructure blas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc +{ + Geometries = + [ + new() + { + Type = RayTracingGeometryType.AABBs, + AABBs = new() + { + Buffer = aabbBuffer, + Count = aabbCount, + StrideInBytes = 24 // 2 × Vector3 (min, max) + }, + Flags = RayTracingGeometryFlags.Opaque + } + ], + Flags = AccelerationStructureBuildFlags.PreferFastTrace +}); +``` + +Buffers used for BLAS input require `BufferUsageFlags.AccelerationStructure`. + +### Building a TLAS + +TLAS combines BLAS instances into a scene: + +```csharp +TopLevelAccelerationStructure tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc +{ + Instances = + [ + new() + { + AccelerationStructure = meshBlas, + ID = 0, + Mask = 0xFF, + Transform = Matrix4x4.Identity, + Flags = RayTracingInstanceFlags.None + } + ], + Flags = AccelerationStructureBuildFlags.PreferFastTrace +}); +``` + +### RayTracingInstance + +| Field | Description | +|-------|-------------| +| `AccelerationStructure` | The BLAS to instance | +| `ID` | User-defined instance ID (readable in shaders) | +| `Mask` | Visibility mask for ray filtering | +| `Transform` | 3×4 transform matrix | +| `Flags` | Instance behavior flags | + +### Updating a TLAS + +Update an existing TLAS in-place (e.g., for animated transforms): + +```csharp +commandBuffer.UpdateAccelerationStructure(tlas, newDesc); +``` + +### Build Flags + +| Flag | Description | +|------|-------------| +| `PreferFastTrace` | Optimize for ray traversal speed | +| `PreferFastBuild` | Optimize for build speed | +| `AllowUpdate` | Allow in-place updates (required for `UpdateAccelerationStructure`) | + +## RayQuery + +Zenith.NET uses `RayQuery` in compute shaders (inline ray tracing) rather than a dedicated ray tracing pipeline: + +```hlsl +RaytracingAccelerationStructure scene; + +[numthreads(16, 16, 1)] +void CSMain(uint3 id : SV_DispatchThreadID) +{ + RayDesc ray; + ray.Origin = cameraPos; + ray.Direction = rayDir; + ray.TMin = 0.001; + ray.TMax = 1000.0; + + RayQuery query; + query.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, ray); + + while (query.Proceed()) + { + if (query.CandidateType() == CANDIDATE_PROCEDURAL_PRIMITIVE) + { + // Custom intersection test for procedural geometry + float t = IntersectSphere(query.CandidateObjectRayOrigin(), + query.CandidateObjectRayDirection(), sphere); + if (t > 0) + query.CommitProceduralPrimitiveHit(t); + } + } + + if (query.CommittedStatus() == COMMITTED_TRIANGLE_HIT) + { + float t = query.CommittedRayT(); + // Shade triangle hit + } +} +``` + +## Resource Binding + +Bind the TLAS to a resource table as `ResourceType.AccelerationStructure`: -This topic covers: +```csharp +ResourceBinding[] bindings = +[ + new() { Type = ResourceType.AccelerationStructure, Count = 1 }, + new() { Type = ResourceType.TextureReadWrite, Count = 1 } +]; -- Acceleration structures: BLAS (triangles, AABBs) and TLAS (instances) -- Building and updating acceleration structures -- Resource binding for ray tracing -- `RayQuery` API usage in shaders -- Ray flags and instance flags +resourceTable.Write(0, tlas); +resourceTable.Write(1, outputTexture); +``` diff --git a/documents/docs/index.md b/documents/docs/index.md index b680bebd..519d80f9 100644 --- a/documents/docs/index.md +++ b/documents/docs/index.md @@ -15,84 +15,43 @@ Each backend targets real-world device coverage: |---------|----------| | **DirectX 12** | Targets mainstream Windows 10 and above, covering the vast majority of Windows devices | | **Metal 4** | Supports Apple Silicon (M-series) Macs and compatible iPhone/iPad models. Intel-based Macs are not supported | -| **Vulkan 1.4** | Serves as the cross-platform fallback. While Vulkan has evolved rapidly with many extensions, mobile and Linux driver support remains uneven — so adaptation is driven by actual device capabilities rather than spec version alone | - -For detailed backend selection guidance, see [Backend Selection](platform/backend-selection.md). +| **Vulkan 1.4** | Cross-platform fallback, requiring Vulkan 1.4 as the minimum version | ## Core Concepts -### Graphics Context - -The `GraphicsContext` is the central hub of Zenith.NET. It abstracts the underlying graphics API and provides: - -- **Resource Creation** - Create buffers, textures, pipelines, and other GPU resources -- **Command Queues** - Access to `Graphics`, `Compute`, and `Copy` queues -- **Capabilities** - Query device name and feature support via `Capabilities` - -Backend-specific contexts are created via extension methods: -- `GraphicsContext.CreateDirectX12(useValidationLayer)` - Windows -- `GraphicsContext.CreateMetal(useValidationLayer)` - Apple -- `GraphicsContext.CreateVulkan(useValidationLayer)` - Cross-platform - -### Command Model - -Zenith.NET uses an explicit command recording model: - -1. **Get a CommandBuffer** - Call `queue.CommandBuffer()` to obtain a buffer from the pool -2. **Record Commands** - Record draw calls, dispatches, copies, and state changes -3. **Submit** - Call `commandBuffer.Submit()` to execute on the GPU -4. **Synchronize** - Use `queue.WaitIdle()` to wait for all submitted work to complete - -### Resource Binding +| Topic | Description | +|-------|-------------| +| [Graphics Context](concepts/graphics-context.md) | The central hub for resource creation, command queues, capabilities, and validation | +| [Command Model](concepts/command-model.md) | Command buffers, submission, synchronization, and the full command API | +| [Resource Binding](concepts/resource-binding.md) | `ResourceTable`, `ResourceBinding`, and how resources connect to shaders | -Resources are bound to shaders through `ResourceTable`: +## Resources -1. **Create** — Create a `ResourceTable` that declares what each binding slot expects -2. **Write** — Call `Write()` to assign actual GPU resources to each slot - -Pipelines and resource tables share the same binding definitions. Before draw/dispatch calls, bind the resource table with `SetResourceTable()`. +| Topic | Description | +|-------|-------------| +| [Buffers](resources/buffers.md) | Vertex, index, constant, and structured buffers with upload and map operations | +| [Textures](resources/textures.md) | 2D/3D/Cube textures, pixel formats, mipmaps, and multisampling | +| [Samplers](resources/samplers.md) | Address modes, filtering, anisotropic sampling, and LOD control | ## Features | Feature | Description | |---------|-------------| -| **Graphics** | Traditional rasterization with vertex and pixel shaders | -| **Compute** | General-purpose GPU compute with compute shaders | -| **Ray Tracing** | Hardware-accelerated BLAS/TLAS with `RayQuery` in any shader stage | -| **Mesh Shading** | Modern GPU-driven geometry with mesh and amplification shaders | - -## Platform Support - -| Platform | DirectX 12 | Metal 4 | Vulkan 1.4 | -|----------|:----------:|:-----:|:------:| -| Windows | Yes | No | Yes | -| Apple | No | Yes | Yes | -| Android | No | No | Yes | -| Linux | No | No | Yes | - -## Best Practices - -### Resource Management - -- **Dispose resources** when no longer needed using `using` statements or `IDisposable` patterns -- **Create resources upfront** rather than per-frame to avoid allocation overhead -- **Reuse command buffers** - the queue automatically pools and recycles them - -### Command Recording +| [Graphics](features/graphics.md) | Rasterization with vertex/pixel shaders, render states, and input layouts | +| [Compute](features/compute.md) | General-purpose GPU computing with dispatch and read-write resources | +| [Ray Tracing](features/ray-tracing.md) | BLAS/TLAS acceleration structures and `RayQuery` inline tracing | +| [Mesh Shading](features/mesh-shading.md) | GPU-driven geometry with mesh and amplification shaders | -- **Batch similar operations** to reduce pipeline and resource table switches -- **Minimize render pass switches** by grouping draws with the same targets -- Call `queue.WaitIdle()` only when synchronization is required +## Platform -### Data Alignment +| Topic | Description | +|-------|-------------| +| [Backend Selection](platform/backend-selection.md) | Platform-backend mapping, runtime detection, and capability checks | +| [UI Framework Integration](platform/ui-frameworks.md) | Avalonia, MAUI, WinForms, WinUI, and WPF view controls | -Zenith.NET defines alignment constants in `GraphicsContext` for cross-platform compatibility: +## [Best Practices](best-practices.md) -| Constant | Value | Purpose | -|----------|:-----:|---------| -| `ConstantBufferAlignment` | 256 bytes | Minimum alignment for constant buffer data | -| `TextureRowPitchAlignment` | 256 bytes | Alignment for texture row pitch | -| `TextureDepthPitchAlignment` | 512 bytes | Alignment for 3D texture depth slice pitch | +Resource lifecycle, command batching, data alignment, performance tips, and debugging guidance. ## Next Steps diff --git a/documents/docs/platform/backend-selection.md b/documents/docs/platform/backend-selection.md index 6673bbb3..bdd0748b 100644 --- a/documents/docs/platform/backend-selection.md +++ b/documents/docs/platform/backend-selection.md @@ -1,11 +1,51 @@ # Backend Selection -> [!NOTE] -> This page is under construction. +Zenith.NET supports three graphics backends. Each backend targets specific platforms and hardware. -This topic covers: +## Runtime Selection -- Platform and backend mapping -- Runtime detection and selection -- Checking capabilities before use -- Validation layer configuration +Select the backend based on the current platform: + +```csharp +GraphicsContext context; + +if (OperatingSystem.IsWindows()) +{ + context = GraphicsContext.CreateDirectX12(useValidationLayer: true); +} +else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) +{ + context = GraphicsContext.CreateMetal(useValidationLayer: true); +} +else +{ + context = GraphicsContext.CreateVulkan(useValidationLayer: true); +} +``` + +After creation, check which backend was selected: + +```csharp +Console.WriteLine($"Backend: {context.Backend}"); +Console.WriteLine($"Device: {context.Capabilities.DeviceName}"); +``` + +## Validation Layer + +Pass `useValidationLayer: true` during development to enable diagnostic messages. Disable in production for optimal performance. See [Graphics Context — Validation Messages](../concepts/graphics-context.md#validation-messages) for details. + +## Capability Checks + +Some features are not available on all hardware. Always check before using optional capabilities. See [Graphics Context — Capabilities](../concepts/graphics-context.md#capabilities) for the full list. + +## NuGet Packages + +Each backend is a separate NuGet package. Reference only the backends you need: + +| Package | Backend | +|---------|---------| +| `Zenith.NET.DirectX12` | DirectX 12 (Windows) | +| `Zenith.NET.Metal` | Metal 4 (Apple) | +| `Zenith.NET.Vulkan` | Vulkan 1.4 (Cross-platform) | + +All backends depend on the core `Zenith.NET` package, which provides the shared API surface. diff --git a/documents/docs/platform/ui-frameworks.md b/documents/docs/platform/ui-frameworks.md index b8d8a8da..5bb2bed6 100644 --- a/documents/docs/platform/ui-frameworks.md +++ b/documents/docs/platform/ui-frameworks.md @@ -1,13 +1,76 @@ # UI Framework Integration -> [!NOTE] -> This page is under construction. +Zenith.NET provides view controls for major .NET UI frameworks through the `Zenith.NET.Views.*` packages. -This topic covers: +## IZenithView -- `IZenithView` interface -- Avalonia integration -- .NET MAUI integration -- Windows Forms integration -- WinUI 3 and Uno Platform integration -- WPF integration +All view implementations share the `IZenithView` interface: + +| Member | Description | +|--------|-------------| +| `GraphicsContext` | The graphics context used for rendering (assign before use) | +| `UpdateRequested` | Event fired each frame for logic updates | +| `RenderRequested` | Event fired each frame for GPU rendering | +| `UI(Action)` | Execute an action on the UI thread | +| `EnsureResources()` | Create/recreate swap chain and frame buffer resources | +| `Tick()` | Drive one frame update + render cycle | +| `Present()` | Present the rendered frame | +| `ReleaseResources()` | Dispose GPU resources | + +### Event Args + +| Type | Properties | +|------|-----------| +| `UpdateEventArgs` | `DeltaSeconds`, `TotalSeconds` | +| `RenderEventArgs` | `DeltaSeconds`, `TotalSeconds`, `FrameBuffer` | + +## Supported Frameworks + +| Package | Framework | Platforms | +|---------|-----------|-----------| +| `Zenith.NET.Views.Avalonia` | Avalonia | Windows, macOS, Linux | +| `Zenith.NET.Views.Maui` | .NET MAUI | Windows, macOS, Android, iOS | +| `Zenith.NET.Views.WinForms` | Windows Forms | Windows | +| `Zenith.NET.Views.WinUI` | WinUI 3 / Uno Platform | Windows (+ Uno targets) | +| `Zenith.NET.Views.WPF` | WPF | Windows | + +## Basic Usage + +All views follow the same pattern: + +```csharp +// 1. Create a graphics context +GraphicsContext context = GraphicsContext.CreateDirectX12(useValidationLayer: false); + +// 2. Assign to the view +zenithView.GraphicsContext = context; + +// 3. Subscribe to events +zenithView.UpdateRequested += (sender, args) => +{ + // Update logic (animations, input, etc.) +}; + +zenithView.RenderRequested += (sender, args) => +{ + CommandBuffer cmd = context.Graphics.CommandBuffer(); + cmd.BeginRenderPass(args.FrameBuffer, clearValue); + // ... draw commands ... + cmd.EndRenderPass(); + cmd.Submit(waitForCompletion: true); +}; +``` + +## ZenithViewHelper + +`ZenithViewHelper` provides shared defaults for all view implementations: + +| Property | Value | Description | +|----------|-------|-------------| +| `ColorFormat` | `B8G8R8A8UNorm` (desktop) / `R8G8B8A8UNorm` (Android) | Default swap chain color format | +| `DepthStencilFormat` | `D32FloatS8UInt` | Default depth/stencil format | +| `Output` | Derived from above | Pre-configured `Output` for pipeline creation | + +## FrameScheduler + +`FrameScheduler` drives the render loop for views that don't have a built-in frame tick mechanism. It calls `Tick()` and `Present()` on the view at the appropriate cadence. diff --git a/documents/docs/resources/buffers.md b/documents/docs/resources/buffers.md index eca9431e..9f43241b 100644 --- a/documents/docs/resources/buffers.md +++ b/documents/docs/resources/buffers.md @@ -1,11 +1,100 @@ # Buffers -> [!NOTE] -> This page is under construction. +Buffers are linear GPU memory used for vertex data, index data, constant data, structured data, and indirect draw arguments. -This topic covers: +## Creating a Buffer -- Buffer types: Vertex, Index, Constant, Structured -- Creating and using `BufferView` -- Uploading data with the `Upload` method -- Map/Unmap operations +```csharp +Buffer buffer = context.CreateBuffer(new BufferDesc +{ + SizeInBytes = 1024, + StrideInBytes = 16, + Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite +}); +``` + +### BufferDesc + +| Field | Type | Description | +|-------|------|-------------| +| `SizeInBytes` | `uint` | Total buffer size in bytes | +| `StrideInBytes` | `uint` | Element stride (e.g., vertex size, struct size) | +| `Flags` | `BufferUsageFlags` | Usage flags that determine how the buffer can be used | + +### Usage Flags + +| Flag | Description | +|------|-------------| +| `Vertex` | Vertex buffer binding | +| `Index` | Index buffer binding | +| `Indirect` | Indirect draw/dispatch argument source | +| `AccelerationStructure` | Input for BLAS/TLAS builds | +| `Constant` | Constant buffer for shader uniforms | +| `ShaderResource` | Read-only structured buffer in shaders | +| `UnorderedAccess` | Read-write buffer in compute shaders | +| `MapRead` | CPU-readable via `Map()` | +| `MapWrite` | CPU-writable via `Map()` | + +Flags can be combined: `BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite`. + +## Uploading Data + +The simplest way to fill a buffer is `Upload()`: + +```csharp +Vertex[] vertices = [ /* ... */ ]; +buffer.Upload(vertices, offsetInBytes: 0); +``` + +`Upload()` works on any buffer. If `MapRead` or `MapWrite` is set, it uses direct CPU mapping; otherwise, it internally uploads through the Copy queue. + +## Map / Unmap + +For fine-grained control over CPU access, use `Map()` and `Unmap()`: + +```csharp +MappedMemory mapped = buffer.Map(); + +// Write directly to GPU memory +Unsafe.CopyBlock((void*)mapped.Pointer, source, mapped.SizeInBytes); + +buffer.Unmap(); +``` + +`MappedMemory` provides a `Pointer` (native address) and `SizeInBytes`. + +## Buffer Views + +A `BufferView` references a sub-region of a buffer for shader binding: + +```csharp +BufferView view = context.CreateBufferView(new BufferViewDesc +{ + Buffer = buffer, + OffsetInBytes = 0, + SizeInBytes = 512, + StrideInBytes = 16 +}); +``` + +Buffer views implement `IBindableResource` and can be written to resource tables. + +## Buffer Roles by Flag + +| Role | Required Flags | Shader Type | +|------|---------------|-------------| +| Vertex buffer | `Vertex` | — (bound via `SetVertexBuffer`) | +| Index buffer | `Index` | — (bound via `SetIndexBuffer`) | +| Constant buffer | `Constant` | `ConstantBuffer` | +| Structured buffer (read) | `ShaderResource` | `StructuredBuffer` | +| Structured buffer (read-write) | `UnorderedAccess` | `RWStructuredBuffer` | +| Indirect arguments | `Indirect` | — (passed to `Draw*Indirect` / `DispatchIndirect`) | +| Acceleration structure input | `AccelerationStructure` | — (used in BLAS builds) | + +## Command Buffer Uploads + +You can also upload data through a `CommandBuffer` for GPU-timeline uploads: + +```csharp +commandBuffer.Upload(buffer, offsetInBytes, dataSpan); +``` diff --git a/documents/docs/resources/samplers.md b/documents/docs/resources/samplers.md index 8243386e..b6d293d0 100644 --- a/documents/docs/resources/samplers.md +++ b/documents/docs/resources/samplers.md @@ -1,10 +1,94 @@ # Samplers -> [!NOTE] -> This page is under construction. +Samplers define how textures are read in shaders — controlling filtering, address wrapping, and level of detail. -This topic covers: +## Creating a Sampler -- Address modes: Wrap, Clamp, Mirror, Border -- Filter options for minification, magnification, and mipmapping -- Level of Detail (LOD) configuration +```csharp +Sampler sampler = context.CreateSampler(new SamplerDesc +{ + U = AddressMode.Wrap, + V = AddressMode.Wrap, + W = AddressMode.Wrap, + Filter = Filter.MinLinearMagLinearMipLinear, + MaxLod = uint.MaxValue +}); +``` + +### SamplerDesc + +| Field | Type | Description | +|-------|------|-------------| +| `U` | `AddressMode` | Horizontal address mode | +| `V` | `AddressMode` | Vertical address mode | +| `W` | `AddressMode` | Depth address mode (3D textures) | +| `Filter` | `Filter` | Minification, magnification, and mipmap filtering | +| `ComparisonFunc` | `ComparisonFunc` | Comparison function for shadow sampling | +| `MaxAnisotropy` | `uint` | Maximum anisotropic filtering level | +| `MinLod` | `float` | Minimum mip LOD clamp | +| `MaxLod` | `float` | Maximum mip LOD clamp | +| `LodBias` | `float` | LOD bias offset | +| `BorderColor` | `BorderColor` | Border color when using `AddressMode.Border` | + +## Address Modes + +Address modes control what happens when UV coordinates fall outside `[0, 1]`: + +| Mode | Behavior | +|------|----------| +| `Wrap` | Tile the texture by repeating | +| `Mirror` | Tile with mirrored repetition | +| `Clamp` | Clamp to the edge texel | +| `Border` | Return the border color | + +## Filter Modes + +Filter modes combine minification, magnification, and mipmap filters: + +| Filter | Min | Mag | Mip | +|--------|-----|-----|-----| +| `MinPointMagPointMipPoint` | Point | Point | Point | +| `MinLinearMagLinearMipLinear` | Linear | Linear | Linear | +| `MinLinearMagLinearMipPoint` | Linear | Linear | Point | +| `Anisotropic` | Anisotropic | Anisotropic | Linear | + +All 9 combinations of Point/Linear for each stage are available. `Anisotropic` provides the highest quality and should be paired with `MaxAnisotropy`. + +### Anisotropic Filtering + +```csharp +Sampler sampler = context.CreateSampler(new() +{ + U = AddressMode.Wrap, + V = AddressMode.Wrap, + W = AddressMode.Wrap, + Filter = Filter.Anisotropic, + MaxAnisotropy = 16, + MaxLod = uint.MaxValue +}); +``` + +## Level of Detail + +| Field | Purpose | +|-------|---------| +| `MinLod` | Clamp the minimum mip level (0 = highest resolution) | +| `MaxLod` | Clamp the maximum mip level (`uint.MaxValue` = allow all levels) | +| `LodBias` | Shift the computed LOD (positive = blurrier, negative = sharper) | + +## Shader Usage + +In Slang/HLSL shaders, declare a `SamplerState` and sample textures: + +```hlsl +Texture2D albedo; +SamplerState linearSampler; + +float4 color = albedo.Sample(linearSampler, uv); +``` + +Bind the sampler to a resource table at the corresponding binding index: + +```csharp +resourceTable.Write(1, sampler); +``` diff --git a/documents/docs/resources/textures.md b/documents/docs/resources/textures.md index 5013a40c..a25cf01c 100644 --- a/documents/docs/resources/textures.md +++ b/documents/docs/resources/textures.md @@ -1,12 +1,127 @@ # Textures -> [!NOTE] -> This page is under construction. +Textures are GPU image resources used for rendering, sampling, compute read/write, and render targets. -This topic covers: +## Creating a Texture -- Texture types: 2D, 3D, Cube, Array -- Creating and using `TextureView` -- Pixel formats and sample counts -- MipMaps generation -- Uploading texture data +```csharp +Texture texture = context.CreateTexture(new TextureDesc +{ + Type = TextureType.Texture2D, + Format = PixelFormat.R8G8B8A8UNorm, + Width = 512, + Height = 512, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Flags = TextureUsageFlags.ShaderResource +}); +``` + +### TextureDesc + +| Field | Type | Description | +|-------|------|-------------| +| `Type` | `TextureType` | Dimensionality and array/cube configuration | +| `Format` | `PixelFormat` | Pixel format (color, depth, compressed) | +| `Width` | `uint` | Width in pixels | +| `Height` | `uint` | Height in pixels | +| `Depth` | `uint` | Depth (for 3D textures, otherwise `1`) | +| `MipLevels` | `uint` | Number of mipmap levels | +| `ArrayLayers` | `uint` | Number of array layers | +| `SampleCount` | `SampleCount` | Multisample count | +| `Flags` | `TextureUsageFlags` | Usage flags | + +### Texture Types + +| Type | Description | +|------|-------------| +| `Texture1D` | 1D texture | +| `Texture1DArray` | Array of 1D textures | +| `Texture2D` | Standard 2D texture | +| `Texture2DArray` | Array of 2D textures | +| `Texture3D` | Volume texture | +| `TextureCube` | Cube map (6 faces) | +| `TextureCubeArray` | Array of cube maps | + +### Usage Flags + +| Flag | Description | +|------|-------------| +| `RenderTarget` | Color render target attachment | +| `DepthStencil` | Depth/stencil render target attachment | +| `ShaderResource` | Read-only in shaders (`Texture2D`, etc.) | +| `UnorderedAccess` | Read-write in compute shaders (`RWTexture2D`, etc.) | + +## Pixel Formats + +Common formats: + +| Category | Formats | +|----------|---------| +| Color (8-bit) | `R8G8B8A8UNorm`, `B8G8R8A8UNorm`, `R8G8B8A8SRgb` | +| Color (16-bit) | `R16G16B16A16Float` | +| Color (32-bit) | `R32G32B32A32Float` | +| Depth | `D16UNorm`, `D32Float` | +| Depth + Stencil | `D24UNormS8UInt`, `D32FloatS8UInt` | +| Compressed | `BC7UNorm`, `BC7SRgb`, `ETC2R8G8B8A8UNorm`, `ASTC4x4UNorm` | + +## Uploading Data + +Upload pixel data directly: + +```csharp +byte[] pixelData = [ /* ... */ ]; +texture.Upload(pixelData, slice: default, offset: default, extent: new() +{ + Width = texture.Desc.Width, + Height = texture.Desc.Height, + Depth = 1 +}); +``` + +### Loading from File + +With the `Zenith.NET.Extensions.ImageSharp` package: + +```csharp +Texture texture = context.LoadTextureFromFile("image.png", generateMipMaps: true); +``` + +## Texture Views + +A `TextureView` references a subset of mip levels or array layers: + +```csharp +TextureView view = context.CreateTextureView(new TextureViewDesc +{ + Texture = texture, + FirstMipLevel = 0, + MipLevelCount = 1, + FirstArrayLayer = 0, + ArrayLayerCount = 1 +}); +``` + +Texture views implement `IBindableResource` and can be written to resource tables. + +## Mipmaps + +Set `MipLevels` to the desired number of levels, or use the `generateMipMaps` parameter when loading from file. In shaders, configure the sampler's `MaxLod` to allow mipmap sampling: + +```csharp +Sampler sampler = context.CreateSampler(new() +{ + Filter = Filter.MinLinearMagLinearMipLinear, + MaxLod = uint.MaxValue +}); +``` + +## Multisampling + +Set `SampleCount` to `Count2`, `Count4`, `Count8`, `Count16`, or `Count32`. Multisampled textures can be resolved to single-sampled targets: + +```csharp +commandBuffer.ResolveTexture(msaaTexture, slice, resolvedTexture, slice); +``` diff --git a/documents/tutorials/advanced/mesh-shading.md b/documents/tutorials/advanced/mesh-shading.md index e959f176..f8db3e06 100644 --- a/documents/tutorials/advanced/mesh-shading.md +++ b/documents/tutorials/advanced/mesh-shading.md @@ -362,7 +362,7 @@ internal unsafe class MeshShadingRenderer : IRenderer }); - resourceTable = App.Context.CreateResourceTable(new() { Bindings = bindings }); + resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); resourceTable.Write(0, constantsBuffer); resourceTable.Write(1, vertexBuffer); resourceTable.Write(2, indexBuffer); diff --git a/documents/tutorials/getting-started/textured-quad.md b/documents/tutorials/getting-started/textured-quad.md index c5532e70..07767cb8 100644 --- a/documents/tutorials/getting-started/textured-quad.md +++ b/documents/tutorials/getting-started/textured-quad.md @@ -290,7 +290,7 @@ resourceTable.Write(0, texture); resourceTable.Write(1, sampler); ``` -`ResourceTable.Write(binding, resource)` assigns actual GPU resources to each slot by index. +`ResourceTable.Write(binding, resource)` assigns actual GPU resources to each binding by index. ### Rendering From cd253fe9582e78e9d8a9e85375c7c726dd9dd5a6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Apr 2026 12:11:46 +0800 Subject: [PATCH 018/466] Set Metal shader library label in SetResourceName Assign the provided resource name to the Metal shader library's Label property in MTLShader.SetResourceName. This helps with debugging and profiling by making the resource easier to identify. --- sources/Zenith.NET.Metal/MTLShader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/Zenith.NET.Metal/MTLShader.cs b/sources/Zenith.NET.Metal/MTLShader.cs index f5a419a3..17729069 100644 --- a/sources/Zenith.NET.Metal/MTLShader.cs +++ b/sources/Zenith.NET.Metal/MTLShader.cs @@ -22,6 +22,7 @@ public MTLShader(MTLGraphicsContext context, ShaderDesc desc) : base(context, de protected override void SetResourceName(string name) { + Library.Label = name; } protected override void Destroy() From 5d9c9d7484e81b7bbc6fa908d63e4cad1752394a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Apr 2026 14:03:44 +0800 Subject: [PATCH 019/466] Simplify lambda syntax in LamdaNativeContext instantiation Refactored the lambda expression passed to LamdaNativeContext constructor from (proc) => to proc => for conciseness. This is a minor syntactic change with no impact on functionality. --- sources/Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 3c09f733..0a3970a6 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -422,7 +422,7 @@ protected override void Initialize(bool useValidationLayer, (GraphicsQueue, ComputeQueue, CopyQueue, QueueFamilyIndices) = getQueues(); - LamdaNativeContext context = new((proc) => Vk.GetDeviceProcAddr(Device, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); + LamdaNativeContext context = new(proc => Vk.GetDeviceProcAddr(Device, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; ExternalMemoryWin32 = enabledExtensions.Contains(KhrExternalMemoryWin32.ExtensionName) ? new(context) : null; From 521a742e005d0a757d5bb88f91fc55e9bc89b428 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Apr 2026 14:19:01 +0800 Subject: [PATCH 020/466] Remove unused Own overloads from NSAutorelease Removed the Own and Own methods from the NSAutorelease class. These methods handled object retention with NSAutoreleasePool for functions with two or three arguments, but are no longer needed or have been refactored. --- sources/Zenith.NET.Metal/NSAutorelease.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sources/Zenith.NET.Metal/NSAutorelease.cs b/sources/Zenith.NET.Metal/NSAutorelease.cs index 8033430c..31cab3d8 100644 --- a/sources/Zenith.NET.Metal/NSAutorelease.cs +++ b/sources/Zenith.NET.Metal/NSAutorelease.cs @@ -17,18 +17,4 @@ public static T Own(Func func, T1 arg1) where T : NSObject, INativ return func(arg1).Retain(); } - - public static T Own(Func func, T1 arg1, T2 arg2) where T : NSObject, INativeObject - { - using NSAutoreleasePool _ = new(); - - return func(arg1, arg2).Retain(); - } - - public static T Own(Func func, T1 arg1, T2 arg2, T3 arg3) where T : NSObject, INativeObject - { - using NSAutoreleasePool _ = new(); - - return func(arg1, arg2, arg3).Retain(); - } } From d3a8ab44c0c30beb6d3948f60a5546b5789169b4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 10 Apr 2026 09:55:57 +0800 Subject: [PATCH 021/466] Update UI invocation to use Dispatcher.Invoke directly Replaced Dispatcher.UIThread.Invoke with Dispatcher.Invoke in the IZenithView.UI method to reflect API changes or simplify the code. This ensures UI actions are invoked correctly on the dispatcher. --- sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index 0e97d32d..111fdb3f 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.UIThread.Invoke(action); + Dispatcher.Invoke(action); } void IZenithView.EnsureResources() From 4987cd85cb32031373bde5583fe579649d5a5f64 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 11 Apr 2026 15:48:33 +0800 Subject: [PATCH 022/466] Rename SetResourceTable to PushResourceTable API-wide Renamed SetResourceTable to PushResourceTable across the codebase, including all documentation, usage examples, renderer implementations, and backend integrations (DirectX, Metal, Vulkan). Updated abstract and platform-specific methods to match. This clarifies resource table binding semantics in command buffer workflows. --- documents/docs/concepts/command-model.md | 4 ++-- documents/docs/concepts/resource-binding.md | 2 +- documents/docs/features/compute.md | 4 ++-- documents/docs/features/graphics.md | 2 +- documents/docs/features/mesh-shading.md | 2 +- documents/tutorials/advanced/mesh-shading.md | 2 +- documents/tutorials/advanced/ray-tracing.md | 2 +- documents/tutorials/getting-started/spinning-cube.md | 2 +- documents/tutorials/getting-started/textured-quad.md | 4 ++-- documents/tutorials/intermediate/compute-shader.md | 4 ++-- documents/tutorials/intermediate/indirect-drawing.md | 2 +- .../Experiments/CornellBox/Renderers/PathTracingRenderer.cs | 2 +- .../CornellBox/Renderers/RasterizationRenderer.cs | 2 +- .../Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 2 +- sources/Zenith.NET.DirectX12/DXCommandBuffer.cs | 2 +- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 4 ++-- sources/Zenith.NET.Metal/MTLCommandEncoder.cs | 2 +- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 2 +- sources/Zenith.NET/CommandBuffer.cs | 6 +++--- 19 files changed, 26 insertions(+), 26 deletions(-) diff --git a/documents/docs/concepts/command-model.md b/documents/docs/concepts/command-model.md index 1be33676..53ef2445 100644 --- a/documents/docs/concepts/command-model.md +++ b/documents/docs/concepts/command-model.md @@ -30,7 +30,7 @@ CommandBuffer commandBuffer = context.Graphics.CommandBuffer(); // 2. Record commands commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.Draw(3, 1, 0, 0); commandBuffer.EndRenderPass(); @@ -66,7 +66,7 @@ Compute dispatches do not require a render pass: ```csharp commandBuffer.SetPipeline(computePipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.Dispatch(groupCountX, groupCountY, groupCountZ); ``` diff --git a/documents/docs/concepts/resource-binding.md b/documents/docs/concepts/resource-binding.md index 5bbf54ab..47460aaf 100644 --- a/documents/docs/concepts/resource-binding.md +++ b/documents/docs/concepts/resource-binding.md @@ -109,7 +109,7 @@ Set the resource table before draw or dispatch calls: commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.Draw(3, 1, 0, 0); commandBuffer.EndRenderPass(); diff --git a/documents/docs/features/compute.md b/documents/docs/features/compute.md index a7683a3e..86462766 100644 --- a/documents/docs/features/compute.md +++ b/documents/docs/features/compute.md @@ -61,7 +61,7 @@ Compute dispatches do not use render passes: ```csharp commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.Dispatch(groupCountX, groupCountY, groupCountZ); ``` @@ -101,7 +101,7 @@ Compute results can be consumed by graphics pipelines and vice versa. For exampl ```csharp // Compute pass commandBuffer.SetPipeline(computePipeline); -commandBuffer.SetResourceTable(computeTable); +commandBuffer.PushResourceTable(computeTable); commandBuffer.Dispatch(groupCountX, groupCountY, 1); // Copy result to frame buffer diff --git a/documents/docs/features/graphics.md b/documents/docs/features/graphics.md index fd22ba7f..3695cf9e 100644 --- a/documents/docs/features/graphics.md +++ b/documents/docs/features/graphics.md @@ -128,7 +128,7 @@ CommandBuffer commandBuffer = context.Graphics.CommandBuffer(); commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); commandBuffer.DrawIndexed(indexCount, 1, 0, 0, 0); diff --git a/documents/docs/features/mesh-shading.md b/documents/docs/features/mesh-shading.md index f0d00f99..2abaa3ba 100644 --- a/documents/docs/features/mesh-shading.md +++ b/documents/docs/features/mesh-shading.md @@ -127,7 +127,7 @@ Geometry data is typically stored in `StructuredBuffer` resources rather than tr ```csharp commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTable); commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.DispatchMesh(groupCountX, groupCountY, groupCountZ); commandBuffer.EndRenderPass(); ``` diff --git a/documents/tutorials/advanced/mesh-shading.md b/documents/tutorials/advanced/mesh-shading.md index f8db3e06..d82c682b 100644 --- a/documents/tutorials/advanced/mesh-shading.md +++ b/documents/tutorials/advanced/mesh-shading.md @@ -433,7 +433,7 @@ internal unsafe class MeshShadingRenderer : IRenderer }, resourceTable); commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.DispatchMesh(DispatchGroupCount, 1, 1); commandBuffer.EndRenderPass(); diff --git a/documents/tutorials/advanced/ray-tracing.md b/documents/tutorials/advanced/ray-tracing.md index 011723de..a24e6a75 100644 --- a/documents/tutorials/advanced/ray-tracing.md +++ b/documents/tutorials/advanced/ray-tracing.md @@ -710,7 +710,7 @@ internal unsafe class RayTracingRenderer : IRenderer CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); uint dispatchX = (App.Width + ThreadGroupSize - 1) / ThreadGroupSize; uint dispatchY = (App.Height + ThreadGroupSize - 1) / ThreadGroupSize; diff --git a/documents/tutorials/getting-started/spinning-cube.md b/documents/tutorials/getting-started/spinning-cube.md index 2df2ba46..f3630bdd 100644 --- a/documents/tutorials/getting-started/spinning-cube.md +++ b/documents/tutorials/getting-started/spinning-cube.md @@ -174,7 +174,7 @@ internal unsafe class SpinningCubeRenderer : IRenderer }, resourceTable); commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); commandBuffer.DrawIndexed(36, 1, 0, 0, 0); diff --git a/documents/tutorials/getting-started/textured-quad.md b/documents/tutorials/getting-started/textured-quad.md index 07767cb8..49323f70 100644 --- a/documents/tutorials/getting-started/textured-quad.md +++ b/documents/tutorials/getting-started/textured-quad.md @@ -150,7 +150,7 @@ internal unsafe class TexturedQuadRenderer : IRenderer }, resourceTable); commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); commandBuffer.DrawIndexed(6, 1, 0, 0, 0); @@ -306,7 +306,7 @@ commandBuffer.BeginRenderPass(App.FrameBuffer, new() }, resourceTable); commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); +commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); commandBuffer.DrawIndexed(6, 1, 0, 0, 0); diff --git a/documents/tutorials/intermediate/compute-shader.md b/documents/tutorials/intermediate/compute-shader.md index 8910d74d..83321e0b 100644 --- a/documents/tutorials/intermediate/compute-shader.md +++ b/documents/tutorials/intermediate/compute-shader.md @@ -108,7 +108,7 @@ internal class ComputeShaderRenderer : IRenderer uint dispatchY = (inputTexture.Desc.Height + ThreadGroupSize - 1) / ThreadGroupSize; commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.Dispatch(dispatchX, dispatchY, 1); processed = true; @@ -252,7 +252,7 @@ if (!processed) uint dispatchY = (inputTexture.Desc.Height + ThreadGroupSize - 1) / ThreadGroupSize; commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.Dispatch(dispatchX, dispatchY, 1); processed = true; diff --git a/documents/tutorials/intermediate/indirect-drawing.md b/documents/tutorials/intermediate/indirect-drawing.md index e1882be6..9f93b4e2 100644 --- a/documents/tutorials/intermediate/indirect-drawing.md +++ b/documents/tutorials/intermediate/indirect-drawing.md @@ -238,7 +238,7 @@ internal unsafe class IndirectDrawingRenderer : IRenderer }, resourceTable); commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); commandBuffer.DrawIndexedIndirect(indirectBuffer, 0, 1); diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index a34d941c..e454a2c3 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -185,7 +185,7 @@ public override void Render(CommandBuffer commandBuffer) } commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.Dispatch((App.Width + ThreadGroupSize - 1) / ThreadGroupSize, (App.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 79ce9cb7..eeff94c9 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -113,7 +113,7 @@ public override void Render(CommandBuffer commandBuffer) }, resourceTable); commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); commandBuffer.DrawIndexed(indexCount, 1, 0, 0, 0); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index e9772c5d..677d0e72 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -430,7 +430,7 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa } commandBuffer.SetScissors([scissor]); - commandBuffer.SetResourceTable(resourceTableBindings[drawCmd.TexRef.GetTexID()]); + commandBuffer.PushResourceTable(resourceTableBindings[drawCmd.TexRef.GetTexID()]); commandBuffer.DrawIndexed(drawCmd.ElemCount, 1, (uint)(drawCmd.IdxOffset + indexOffset), (int)(drawCmd.VtxOffset + vertexOffset), 0); } } diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 7f08263a..0716c6c2 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -386,7 +386,7 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf GraphicsCommandList4.IASetIndexBuffer(&view); } - protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) + protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) { if (cbvSrvUavTable is null || samplerTable is null) { diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 8f8e8601..37146667 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -231,9 +231,9 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf CommandEncoder.SetIndexBuffer(buffer, offsetInBytes, format); } - protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) + protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) { - CommandEncoder.SetResourceTable(resourceTable); + CommandEncoder.PushResourceTable(resourceTable); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs index d597061d..3c6c245e 100644 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs @@ -201,7 +201,7 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat format IndexType = MTLFormats.Metal(format); } - public void SetResourceTable(ResourceTable resourceTable) + public void PushResourceTable(ResourceTable resourceTable) { currentResourceTable = resourceTable; diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index bba68a69..6e85dfd5 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -372,7 +372,7 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf Context.Vk.CmdBindIndexBuffer(CommandBuffer, buffer.Vulkan().Buffer, offsetInBytes, VKFormats.Vulkan(format)); } - protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) + protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) { (PipelineBindPoint pipelineBindPoint, PipelineLayout pipelineLayout, uint descriptorWriteCount) = pipeline switch { diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 443a9877..ad18c953 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -216,14 +216,14 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat format SetIndexBufferImpl(pipeline, buffer, offsetInBytes, format); } - public void SetResourceTable(ResourceTable resourceTable) + public void PushResourceTable(ResourceTable resourceTable) { if (currentPipeline is null) { return; } - SetResourceTableImpl(currentPipeline, resourceTable); + PushResourceTableImpl(currentPipeline, resourceTable); if (currentFrameBuffer is null) { @@ -428,7 +428,7 @@ protected override void Destroy() protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format); - protected abstract void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable); + protected abstract void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable); protected abstract void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); From 9e31a1c4a9ac14b1e3e68103c959637c54d56da1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 13 Apr 2026 14:46:23 +0800 Subject: [PATCH 023/466] Update docfx.json: add sitemap, remove _gitUrlPattern Added sitemap configuration with baseUrl for SEO improvements. Removed the _gitUrlPattern property to simplify source file link handling. --- documents/docfx.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documents/docfx.json b/documents/docfx.json index 28f27224..dc96a069 100644 --- a/documents/docfx.json +++ b/documents/docfx.json @@ -49,8 +49,7 @@ "_gitContribute": { "repo": "https://github.com/qian-o/Zenith.NET", "branch": "master" - }, - "_gitUrlPattern": "https://github.com/qian-o/Zenith.NET/blob/master/{path}#L{line}" + } }, "output": "_site", "sitemap": { From e3b69987a82518308ea0901c291f369f6b27e6f4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 16 Apr 2026 14:01:30 +0800 Subject: [PATCH 024/466] Update Avalonia to version 12.0.1 Upgraded the Avalonia package from 12.0.0 to 12.0.1 in Directory.Packages.props. No other package versions were modified. --- sources/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 5e778434..a2cbb6f9 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,7 +5,7 @@ - + From 8921695d0f197ad4ec34274ff8206c56a07c5360 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 21 Apr 2026 18:31:09 +0800 Subject: [PATCH 025/466] Add initial draft of RHI redesign document in Chinese - Outline design goals focusing on API surface, frame loop efficiency, and compatibility with DirectX 12, Vulkan 1.3, and Metal 4. - Establish naming and code style conventions, including the use of `record struct` for public value types and specific suffixes for byte-related fields. - Introduce common types such as `TextureSubresource`, `BufferRange`, and various attachment descriptors. - Define resource handles for `Buffer`, `Texture`, and `Sampler`, and remove obsolete types. - Implement `CommandQueue` and `CommandBuffer` classes with streamlined submission and synchronization methods. - Detail resource state transitions and memory barriers for efficient resource management. - Provide a comprehensive overview of command buffer operations, including rendering and resource binding. - Include a frame loop example demonstrating the use of the new API. - Ensure compatibility with RDG layer for future enhancements. --- rhi-redesign.draft.md | 939 +++++++++++++++++++++++++++++++++++++++ rhi-redesign.zh.draft.md | 549 +++++++++++++++++++++++ 2 files changed, 1488 insertions(+) create mode 100644 rhi-redesign.draft.md create mode 100644 rhi-redesign.zh.draft.md diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md new file mode 100644 index 00000000..259dafa3 --- /dev/null +++ b/rhi-redesign.draft.md @@ -0,0 +1,939 @@ +# RHI Redesign + +This document defines the proposed evolution of Zenith.NET's RHI: queue submission, synchronization, resource transitions, the resource and subresource model, render passes, and the swapchain. It is a temporary design note for API iteration and does not describe the current implementation. + +The redesign targets five deficiencies in the current RHI: + +1. **No cross-queue submission synchronization.** `WaitIdle()` is the only available primitive. +2. **No explicit resource state transition API.** Backends transition resources implicitly inside individual operations, which a render dependency graph (RDG) cannot rely on. +3. **`FrameBuffer` is a heavy long-lived object** that does not match the inline render-pass model used by modern APIs (`vkCmdBeginRendering`, `ID3D12GraphicsCommandList4::BeginRenderPass`, `MTLRenderPassDescriptor`), and it forces `SwapChain` to expose a `FrameBuffer`. +4. **The `Resource → View` two-level binding model duplicates work.** Every `Buffer` / `Texture` constructs a default `View` (DX12 lazily allocates CBV+SRV+UAV descriptor tokens; Vulkan eagerly allocates a full-range `VkImageView`); creating an explicit `BufferView` / `TextureView` for the same range allocates a second set. `BufferView` / `TextureView` are public types that almost never need to be public. +5. **Subresource shapes are bespoke.** `TextureSlice { MipLevel, ArrayLayer, Face }` carries a `Face` axis none of the three target APIs has, and is the same type for both copy addressing and view addressing. + +The end goal is a small, canonical RHI that maps 1:1 to DirectX 12, **Vulkan 1.3**, and Metal 4, that an RDG layer can consume without further breaking changes, and that third-party adapters (ImGui backends, asset loaders, profilers) can drive without translation. + +## API-Surface Conventions + +- All value types are `record struct` with public mutable fields. No `ref struct`. +- No `in` parameters anywhere on the public RHI. Small structs (≤ 16 B) are cheaper to pass by value; uniformity beats marginal perf wins on larger structs. +- All multi-element parameters are `ReadOnlySpan` (or `params ReadOnlySpan` in trailing position). No `T[]`, no `IEnumerable`, no `params T[]`. +- All method bodies use `{ ... }`, never expression-bodied `=>`. **Exception:** `ref` / `ref readonly` returning properties keep the `=> ref _field;` form (e.g. `public ref readonly TDesc Desc => ref desc;`), which has no equivalent block syntax that reads as cleanly. +- Every byte-denominated parameter or field carries the `*InBytes` suffix (`OffsetInBytes`, `SizeInBytes`, `RowPitchInBytes`, ...). Index-style identifiers (`slot`, `firstSlot`, `binding`) follow standard graphics terminology and stay unsuffixed. +- Abstract hooks that backends override carry the `*Core` suffix. This is a project-wide rename of every existing `*Impl` member (`WaitIdleImpl`, `SubmitImpl`, `SetImpl`, `PreprocessImpl`, `GetResultsImpl`, `ResizeImpl`, `RefreshImpl`, `CopyBufferImpl`, etc.). Where appropriate, `*Core` is exposed as a property (`CompletedValueCore`). +- The only public synchronization result type is `CommandSubmission`. Both `CommandBuffer.Submit` and `SwapChain.Present` return it. +- Existing enums and types are reused, not redefined. In particular, the existing `ShaderStageFlags` (`None / Vertex / Pixel / Compute / Amplification / Mesh`, `[Flags]`) is the stage selector for `PushResourceTable`; this design adds no new stage enum. + +## Goals + +- Keep frame-loop user code short and readable. +- Avoid per-submit, per-pass, and per-bind managed allocations on the hot path. +- Support cross-queue synchronization between `Graphics`, `Compute`, and `Copy`. +- Expose explicit resource transitions for the two shader-side states an RDG actually needs. +- Replace `FrameBuffer` with attachments passed directly to `BeginRenderPass`. +- Have `SwapChain` expose backbuffer textures and a single uniform `Present(...)` returning a `CommandSubmission`. +- Reduce public RHI handles to `Buffer`, `Texture`, `Sampler`. +- Express sub-range information at the **call site** as a value type, not as a long-lived resource. +- Cache backend descriptors / `VkImageView`s / `MtlTexture` slices on the parent resource keyed by the value-type range, so identical bindings share one backend object. + +## Non-Goals + +- Implicit / tracked resource state inside the RHI beyond a single "current state" cache used to compute the source of explicit transitions. +- One synchronization object per submission. +- Using `WaitForIdle()` as the primary synchronization model. +- Building the full render dependency graph in this iteration. +- Bindless / descriptor-buffer paths. +- Removing `ResourceTable` / `ResourceLayout`. Those are independent and stay. +- Reworking `Sampler`, pipeline state, or shader IO in this iteration. +- Exposing per-aspect (color / depth / stencil) addressing on the public surface; backend infers aspect from `Texture.Format`. +- Exposing sub-range transitions; that is an RDG concern handled through internal backend primitives. +- Owning a "first-frame image ready" wait inside `SwapChain`; the caller is responsible for seeding the first submit. + +--- + +## Part 1 — Queue Synchronization + +### Model Summary + +Each `CommandQueue` owns one **monotonic completion timeline** that backs every submission to that queue. + +- A submission produces a `CommandSubmission` value identifying one point on its queue's timeline. +- Cross-queue dependencies are expressed by passing prior `CommandSubmission` values to the next `Submit` call. +- The timeline object itself is **not** a public type. Backends override timeline operations directly on `CommandQueue`. +- A separate **binary host fence** type stays internal to backends for swapchain image acquire / present, where binary semantics are mandatory (Vulkan). + +The naming is deliberate: the term *fence* retains its Vulkan / DX12 binary meaning and is only used internally. The timeline behavior lives on `CommandQueue`. There is no public type named `Fence` for general queue synchronization. + +### Public API + +```csharp +public readonly struct CommandSubmission(CommandQueue queue, ulong value) +{ + public void Wait() + { + if (queue is null || queue.CompletedValueCore >= value) + { + return; + } + + queue.WaitCore(value); + } +} + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandSubmission Submit(params ReadOnlySpan waits) + { + return queue.Submit(this, waits); + } +} +``` + +`CommandSubmission` reaches into `CompletedValueCore` and `WaitCore` directly via `protected internal` access — there is no public `CommandQueue.Wait(ulong)` method to wrap them. `default(CommandSubmission)` carries a null queue reference and is treated as "already complete / no wait needed". + +### `CommandQueue` Surface + +The public surface is intentionally narrow: pool a command buffer, wait for the queue to drain, submit. Per-value waits are not part of the public surface — they are reached by calling `submission.Wait()`, which routes through `protected internal` hooks on the queue. + +```csharp +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +{ + private readonly Lock @lock = new(); + private readonly Queue available = []; + private readonly Queue execution = []; + + private ulong nextValue = 1; + private ulong lastSignaledValue; + + public CommandQueueType Type { get; } = type; + + /// Borrow a command buffer from the pool; the returned buffer has already had Begin called. + public CommandBuffer CommandBuffer() + { + using Lock.Scope _ = @lock.EnterScope(); + + CollectCompleted(); + + CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBufferCore() : available.Dequeue(); + + commandBuffer.Begin(); + + return commandBuffer; + } + + /// Block until every submission on this queue has completed. + public void WaitForIdle() + { + using Lock.Scope _ = @lock.EnterScope(); + + if (lastSignaledValue is not 0) + { + WaitCore(lastSignaledValue); + } + + CollectCompleted(); + } + + internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) + { + using Lock.Scope _ = @lock.EnterScope(); + + CollectCompleted(); + + commandBuffer.End(); + + ulong value = nextValue++; + + SubmitCore(commandBuffer, waits, value); + + lastSignaledValue = value; + + execution.Enqueue(new(commandBuffer, value)); + + return new(this, value); + } + + private void CollectCompleted() + { + ulong completed = CompletedValueCore; + + while (execution.Count > 0 && execution.Peek().Value <= completed) + { + CommandBuffer cmd = execution.Dequeue().CommandBuffer; + + cmd.Reset(); + + available.Enqueue(cmd); + } + } + + /// Latest value the timeline has signalled to. Reachable from in the same assembly. + protected internal abstract ulong CompletedValueCore { get; } + + protected abstract CommandBuffer CreateCommandBufferCore(); + + protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + + /// Block until the timeline reaches . Reachable from in the same assembly. + protected internal abstract void WaitCore(ulong value); + + private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); +} +``` + +### Why Not a Public `Fence` / `Timeline` Type + +The timeline is in 1:1 correspondence with its queue. There is no scenario where a user holds a fence independent of its queue, so promoting it to a public type only adds API surface and collides with the Vulkan binary `VkFence` (which has no value). + +Folding the timeline into `CommandQueue` keeps the public surface minimal and removes the naming conflict with the binary fence used by swapchain code. + +### Backend Mapping + +| Backend | Timeline object | Submit | `CompletedValueCore` | `WaitCore` | +|---|---|---|---|---| +| DX12 | one `ID3D12Fence` per queue | for each wait `queue.Wait(otherFence, otherValue)` → `ExecuteCommandLists` → `queue.Signal(fence, value)` | `fence.GetCompletedValue()` | `SetEventOnCompletion` + `WaitOne` | +| Vulkan 1.3 | one **timeline** `VkSemaphore` per queue (core in 1.2, ubiquitous in 1.3) | `VkTimelineSemaphoreSubmitInfo` packs wait `(semaphore, value)` pairs and the signal value into `VkSubmitInfo` | `vkGetSemaphoreCounterValue` | `vkWaitSemaphores` | +| Metal 4 | one `MTLSharedEvent` per queue | for each wait the command buffer encodes `EncodeWait(otherEvent, otherValue)`; before commit it encodes `EncodeSignal(event, value)` | `event.SignaledValue` | `event.Wait(value, timeout)` | + +The existing internal `VKFence` (binary `VkFence`) remains, used only by swapchain image acquire on Vulkan. + +### Usage + +```csharp +// Single-queue +graphicsCmd.Submit().Wait(); + +// Cross-queue +CommandSubmission upload = copyCmd.Submit(); +CommandSubmission cull = computeCmd.Submit(upload); +CommandSubmission frame = graphicsCmd.Submit(cull); +frame.Wait(); +``` + +--- + +## Part 2 — Canonical Subresource Types + +The following value types replace the single `TextureSlice`. They are used by transitions (Part 3), bindings (Part 5), command-buffer copies (Part 6), and render-pass attachments (Part 7). + +There are exactly two texture-subresource shapes: + +- `TextureSubresource` — single mip × single array layer. Used by RTV / DSV / `ResolveTexture`. +- `TextureSubresourceRange` — contiguous mip range × contiguous array-layer range. Used by views, transitions, and copy / upload (which validate `LevelCount == 1` at the call site). + +There is no public `TextureAspect` type — aspect (color vs depth vs stencil) is derived from `Texture.Format` by every backend that needs it. Stencil-only sampling is a rare, advanced case left to a future opt-in extension. + +### `TextureSubresource` — single subresource + +```csharp +public record struct TextureSubresource +{ + public uint MipLevel; + + public uint ArrayLayer; +} +``` + +Identifies exactly one subresource. Used by render-pass attachments (a render target is always a single mip × single array layer) and by `ResolveTexture`. Cube faces use `ArrayLayer = cubeIndex * 6 + face`; there is no `Face` field. + +### `TextureSubresourceRange` — view / transition / copy addressing + +```csharp +public record struct TextureSubresourceRange +{ + public uint BaseMipLevel; + + public uint LevelCount; + + public uint BaseArrayLayer; + + public uint LayerCount; +} +``` + +Maps to: + +- Vulkan: `VkImageSubresourceRange` directly (aspect filled in from `Texture.Format`). +- DX12: `D3D12_TEX*_ARRAY_*` view descs (`MostDetailedMip`, `MipLevels`, `FirstArraySlice`, `ArraySize`; `PlaneSlice` derived from format). +- Metal: `MTLTexture.MakeTextureView(levelRange, sliceRange)`. + +For copy / upload paths, callers set `LevelCount = 1`; the validation layer rejects multi-mip copies up-front rather than lowering them silently. + +### `Offset3D` and `Extent3D` + +```csharp +public record struct Offset3D +{ + public int X; + + public int Y; + + public int Z; +} + +public record struct Extent3D +{ + public uint Width; + + public uint Height; + + public uint Depth; +} +``` + +Used by the `Copy*` overloads to specify source / destination origin and size. Match `VkOffset3D` / `VkExtent3D`; trivially translated to DX12 `D3D12_BOX` and Metal origin / size pairs. `Z` / `Depth` collapse to `0` / `1` for 2D textures. + +### `BufferRange` + +```csharp +public record struct BufferRange +{ + public Buffer Buffer; + + public ulong OffsetInBytes; + + public ulong SizeInBytes; + + public uint StrideInBytes; + + public static implicit operator BufferRange(Buffer buffer) + { + return new() + { + Buffer = buffer, + OffsetInBytes = 0, + SizeInBytes = buffer.Desc.SizeInBytes, + StrideInBytes = buffer.Desc.StrideInBytes + }; + } +} +``` + +Field naming aligns with `VkDescriptorBufferInfo` (`buffer`, `offset`, `range`), promoted to the `*InBytes` suffix to match `BufferDesc`. `StrideInBytes = 0` falls back to `Buffer.Desc.StrideInBytes`, eliminating the current double-storage of stride. + +There is no `BufferViewType` field. The interpretation (constant / structured / byte-address / typed) is fixed by the corresponding `ResourceLayout` slot at bind time. This avoids exposing a knob whose meaning differs between DX12 (which has typed buffers natively) and Metal (which does not) and removes a redundant choice the user would otherwise have to keep in sync with their layout. + +--- + +## Part 3 — Explicit Resource Transitions + +### Why the RHI Must Expose Transitions + +Every backend has a notion of resource state: + +- DX12: `D3D12_RESOURCE_STATES` (or enhanced barriers' `D3D12_BARRIER_LAYOUT` / `D3D12_BARRIER_ACCESS`). +- Vulkan 1.3: `VkImageLayout` + `VkAccessFlags2` + `VkPipelineStageFlags2` (sync2 is core in 1.3). +- Metal: implicit hazard tracking, with explicit options via `MTLResourceUsage` and barriers between encoders. + +Today these transitions are emitted opaquely inside per-call paths. An RDG cannot batch or reorder barriers if it cannot see them. The redesign exposes the two shader-side states as first-class commands and leaves every other state implicit. + +### `TransitionState` + +```csharp +public enum TransitionState +{ + ShaderResource, + UnorderedAccess +} +``` + +`TransitionState` is shared by `Texture` and `Buffer`. The two states the user actually has to choose between are the same on both: a sampled / read-only resource (`ShaderResource`) versus an unordered read-write resource (`UnorderedAccess`). Backends pick a sensible initial state per resource at creation time, and the first explicit `Transition` call resolves the source state from the resource's tracked field. + +### Why `TransitionState` Has Only Two Values + +Every other state is implied by the operation that consumes the resource and is therefore transitioned by the RHI itself, not by the user: + +- **Render-target / depth-stencil** — implied by `BeginRenderPass`. The pass transitions every color attachment to render-target state and every depth attachment to depth-stencil state. `EndRenderPass` does **not** auto-revert; the texture stays in render-target state until the user transitions it back, which is what an RDG wants. +- **Copy source / dest** — implied by `Copy*` and `Resolve*` commands. +- **Present** — implied by `SwapChain.Present`. The swapchain transitions the current backbuffer to the platform's present state as part of `Present(...)`. +- **Vertex / index / constant / indirect buffer** — implied by `SetVertexBuffer`, `SetIndexBuffer`, `PushResourceTable` (CBV slot), and `Draw*Indirect` / `Dispatch*Indirect`. DX12 promotes / decays these automatically; Vulkan derives the access mask at barrier time. + +The user therefore only writes explicit transitions when moving a resource between **shader-side states** (`ShaderResource` ↔ `UnorderedAccess`) or when bringing a resource out of one of the implicit states back to a shader-side state for the next pass. + +### Why There Is No Per-Resource Buffer Enum + +Most backends do not need explicit per-state tracking for buffers: + +- **DX12** auto-promotes / decays buffer states between `COMMON` and the implicit usage of each operation. +- **Vulkan** has no buffer layout; access is described by `VkAccessFlags2` derived from operation usage at barrier time. +- **Metal** tracks buffer hazards implicitly. + +A buffer therefore only needs the same two-state surface a texture exposes — hence the shared `TransitionState`. + +### `MemoryBarrier()` and Implicit UAV Tracking + +`MemoryBarrier()` is part of the public API. It expresses "make all preceding writes globally visible before any following read" without naming a specific resource — exactly the semantics of a UAV / global memory barrier across shader stages. + +In addition to the explicit call, the `CommandBuffer` keeps a small set of UAV resources written by the current dispatch chain and inserts the appropriate barrier when the next dispatch reads or writes one of those resources. The cost is one hash-set lookup per dispatch; the benefit is that the hazard cannot be forgotten in the easy case. + +`MemoryBarrier()` is the escape hatch for cases the implicit tracker cannot express — e.g. cross-resource hazards, hazards spanning compute → graphics, or RDG-driven scenarios where the layer wants a coarse global flush. + +### Transition Tracking + +Each `Texture` and `Buffer` tracks its current `TransitionState`. Tracking lives on the RHI resource so the user only states the **target** state, never the previous one. + +- The user calls `cmd.Transition(tex, TransitionState.ShaderResource)`; the RHI reads the source state from the resource's tracked state, emits the backend barrier, and updates tracking. +- An RDG that wants global control can reset tracked state itself before issuing pre-planned barriers. + +### Public Commands on `CommandBuffer` + +```csharp +public void Transition(Buffer buffer, TransitionState newState); + +public void Transition(Texture texture, TransitionState newState); + +public void MemoryBarrier(); +``` + +Backends implement three primitives: + +```csharp +protected abstract void TransitionCore(Buffer buffer, TransitionState oldState, TransitionState newState); + +protected abstract void TransitionCore(Texture texture, TransitionState oldState, TransitionState newState); + +protected abstract void MemoryBarrierCore(); +``` + +The public `Transition` commands are deliberately scalar and whole-resource. Each backend coalesces consecutive `Transition` calls into one batched barrier op at the next non-transition command, so users get clean call sites without giving up batched submission to the driver. + +There is no public sub-range `Transition(Texture, TextureSubresourceRange, ...)` overload. Sub-range hazard tracking is an RDG concern; backends still expose the necessary internal primitive (`TransitionRangeCore`) for an RDG layer to call directly without touching the public surface. + +### Required States for Operations + +Each operation either *requires* a user-managed state on its resource argument or *implies* the state itself. Implicit states are transitioned by the operation; the user never writes them. + +| Operation | Required state | Source | +|---|---|---| +| `PushResourceTable` SRV resource | `ShaderResource` | user | +| `PushResourceTable` UAV resource | `UnorderedAccess` | user | +| `BeginRenderPass` color attachment | render-target (implicit) | RHI | +| `BeginRenderPass` depth attachment | depth-stencil (implicit) | RHI | +| `CopyBuffer` source / dest | copy source / dest (implicit) | RHI | +| `CopyBufferToTexture` source / dest | copy source / dest (implicit) | RHI | +| `CopyTextureToBuffer` source / dest | copy source / dest (implicit) | RHI | +| `CopyTexture` source / dest | copy source / dest (implicit) | RHI | +| `ResolveTexture` source / dest | copy source / dest (implicit) | RHI | +| `SetVertexBuffer` / `SetIndexBuffer` | vertex / index buffer (implicit) | RHI | +| `PushResourceTable` CBV slot | constant buffer (implicit) | RHI | +| `Draw*Indirect` / `Dispatch*Indirect` argument buffer | indirect argument (implicit) | RHI | +| `Dispatch` reading a UAV that an earlier dispatch wrote | UAV barrier (implicit; user may also call `MemoryBarrier()`) | RHI | +| `SwapChain.Present` backbuffer | present (implicit) | RHI | + +The validation layer enforces correctness on `PushResourceTable`: passing a UAV resource in `ShaderResource` or vice versa is a loud error. + +### Cross-Queue Ownership + +Vulkan requires explicit queue family ownership transfer for resources used on multiple queues with `VK_SHARING_MODE_EXCLUSIVE`. To keep the RHI simple: + +- All resources are created with concurrent / shared semantics (Vulkan: `VK_SHARING_MODE_CONCURRENT` across the three queues; DX12 / Metal need no equivalent). +- Cross-queue synchronization is therefore expressed entirely through `CommandSubmission` waits. The RHI does not require ownership-transfer barriers in user code. + +Concurrent sharing has a small driver-side cost on Vulkan but eliminates an entire class of barrier bugs, which matches the framework's "easy to use first" stance. + +--- + +## Part 4 — Public Resource Surface + +Three public RHI resource handles. Nothing else. + +```csharp +public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) +{ + private BufferDesc desc = desc; + + public ref readonly BufferDesc Desc => ref desc; +} + +public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) +{ + private TextureDesc desc = desc; + + public ref readonly TextureDesc Desc => ref desc; + + public static implicit operator TextureSubresourceRange(Texture texture) + { + return new() + { + BaseMipLevel = 0, + LevelCount = texture.Desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = texture.Desc.ArrayLayers + }; + } +} + +public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) +{ + private SamplerDesc desc = desc; + + public ref readonly SamplerDesc Desc => ref desc; +} +``` + +Removed from the public surface: `BufferView`, `BufferViewDesc`, `TextureView`, `TextureViewDesc`, `IBindableResource`, `TextureSlice`, `TextureAspect`, `TextureSubresourceLayers`, `BufferViewType`, `FrameBuffer`, `FrameBufferDesc`, `FrameBufferAttachment`, `RenderPassDesc`, `ClearValue`, `ClearValues`, `ClearFlags`. + +The backend infers texture aspect from `desc.Format` whenever it needs to fill in a `VkImageAspectFlags` / DX12 plane index. The implicit operator from `Texture` to `TextureSubresourceRange` covers the whole resource. + +`TextureType` (1D / 2D / 3D / Cube / CubeArray combined into one enum) stays as it is. Metal merges these the same way (`MTLTextureType` lists `Type1D / Type2D / TypeCube / Type3D / Type1DArray / Type2DArray / TypeCubeArray` in one enum), so the existing shape is canonical, not bespoke. + +### Caching on the Parent + +Each backend resource holds two private dictionaries: + +```csharp +// Conceptual; backend-internal, not exposed. +private Dictionary bufferViewCache; +private Dictionary textureViewCache; +``` + +`T_*` is whatever the backend needs: + +| Backend | Buffer cache value | Texture cache value | +|---|---|---| +| DX12 | `DXDescriptorToken` per CBV / SRV / UAV variant | `DXDescriptorToken` per SRV / UAV; RTV / DSV come from a separate `TextureSubresource`-keyed pool | +| Vulkan | `VkDescriptorBufferInfo` (no GPU object) | `VkImageView` | +| Metal | `nuint GpuAddress` | `MtlTexture` slice via `MakeTextureView` | + +The cache is populated on first bind and reused for every subsequent bind with an equal range. The default range — produced by the implicit operator from `Texture` / `Buffer` — hits the same slot every time, so the typical "bind whole resource" call path materializes one backend object for the lifetime of the resource. The cache is owned by, and disposed with, the parent. + +### Cache Invalidation + +Resources are immutable in shape (you cannot re-`BufferDesc` an existing `Buffer`), so the cache never needs partial invalidation. `Resize` on a swapchain backbuffer disposes and recreates the underlying `Texture`; the new texture starts with an empty cache. + +--- + +## Part 5 — Resource Table Binding + +### Stage-Tagged Push Instead of Set Index + +Modelling Vulkan's descriptor sets directly via `SetResourceTable(uint set, ResourceTable table)` maps cleanly to Vulkan but forces DX12 (root signature with per-stage visibility) and Metal (per-stage argument tables) into an artificial set-indexed translation layer. It also does not let the user say "this table is for the fragment stage only", which both DX12 and Metal can express natively. + +The design uses a **stage-tagged push** modelled on Metal's `setVertexBuffer:atIndex:` / `setFragmentBuffer:atIndex:` family, generalised across stages via the existing `ShaderStageFlags` enum (`None / Vertex / Pixel / Compute / Amplification / Mesh`, `[Flags]`). The verb is `Push` rather than `Set` to signal that the call is binding-now / push-style, not a long-lived assignment. + +### `ResourceTable.Write` — Typed Overloads + +```csharp +public abstract class ResourceTable +{ + public void Write(uint binding, BufferRange range); + + public void Write(uint binding, TextureSubresourceRange range); + + public void Write(uint binding, Sampler sampler); + + public void Write(uint binding, ReadOnlySpan ranges); + + public void Write(uint binding, ReadOnlySpan ranges); + + public void Write(uint binding, ReadOnlySpan samplers); +} +``` + +The implicit operators keep simple call sites a single line: + +```csharp +table.Write(0, vertexBuffer); +table.Write(1, albedoTexture); +table.Write(2, sampler); +``` + +Sub-ranges use a struct literal: + +```csharp +table.Write(3, new TextureSubresourceRange +{ + BaseMipLevel = 0, + LevelCount = 1, + BaseArrayLayer = 4, + LayerCount = 1 +}); + +table.Write(4, new BufferRange +{ + Buffer = bigBuffer, + OffsetInBytes = 256, + SizeInBytes = 1024, + StrideInBytes = 32 +}); +``` + +Validation enforces shape compatibility against the resource-layout slot (UAV slot requires `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`, etc.). There is no marker interface like `IBindableResource`. + +### Backend Mapping for `PushResourceTable` + +| Backend | Push semantics | +|---|---| +| DX12 | look up the root parameter whose visibility includes any of `stages`; bind the table's descriptor handle there | +| Vulkan 1.3 | one descriptor set per call, layout pre-baked; for shader-stage-only visibility, use `VK_KHR_push_descriptor` or a stage-disjoint set layout | +| Metal 4 | call the matching per-stage encoder method (`setVertexBuffer` / `setFragmentBuffer` / `setComputeBuffer`) for each bit set in `stages` | + +--- + +## Part 6 — Command Buffer Operations + +All command-buffer operations take canonical types and consistent naming. Signatures align with Vulkan command names and parameter shapes; DX12 / Metal map directly. No `in` parameters; every multi-element parameter is `ReadOnlySpan`; every byte-denominated parameter carries the `*InBytes` suffix. + +### Buffer Operations + +```csharp +public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); + +public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); + +public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); + +public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, + Buffer destination, ulong destinationOffsetInBytes, + ulong sizeInBytes); + +public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; +``` + +### Texture Operations + +```csharp +public void CopyTexture(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, + Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, + Extent3D extent); + +public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, + Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, + Extent3D extent); + +public void CopyTextureToBuffer(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, Extent3D extent, + Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); + +public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, + Texture destination, TextureSubresource destinationSubresource); + +public void Upload(Texture destination, TextureSubresourceRange range, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; +``` + +`Copy*` and `Upload(Texture, ...)` paths require `range.LevelCount == 1`; the validation layer rejects multi-mip arguments. `ResolveTexture` always operates on a single subresource per side and therefore takes `TextureSubresource` directly. + +### Resource Tables + +```csharp +public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); +``` + +There is no plural `PushResourceTables`; multiple stage targets are handled by setting the appropriate `ShaderStageFlags` bits in one call. + +### Transitions + +```csharp +public void Transition(Buffer buffer, TransitionState newState); + +public void Transition(Texture texture, TransitionState newState); + +public void MemoryBarrier(); +``` + +### Viewports / Scissors + +```csharp +public void SetViewports(ReadOnlySpan viewports); + +public void SetScissors(ReadOnlySpan scissors); +``` + +`SetViewports` / `SetScissors` are first-class command-buffer ops (not derived from any render-pass desc). Single-viewport / single-scissor calls pass a one-element span via `[stackalloc]` or a collection expression. The plural form matches DX12 `RSSetViewports` / `RSSetScissorRects` and Vulkan `vkCmdSetViewport` / `vkCmdSetScissor` natively; on Metal the backend asserts `viewports.Length == 1` and `scissors.Length == 1`. + +```csharp +public record struct Viewport +{ + public float X; + + public float Y; + + public float Width; + + public float Height; + + public float MinDepth; + + public float MaxDepth; +} + +public record struct Rect +{ + public int X; + + public int Y; + + public uint Width; + + public uint Height; +} +``` + +--- + +## Part 7 — Inline RenderPass (replaces `FrameBuffer`) + +### Why `FrameBuffer` Is Removed + +`FrameBuffer` predates dynamic rendering. It forces: + +- A long-lived object whose only role is bundling attachment views. +- A separate `SwapChainFrameBuffer` per backend. +- An `Output` description duplicated on `FrameBuffer`, `SwapChain`, and pipelines. + +All three target APIs natively accept inline render-pass descriptors. Removing `FrameBuffer` aligns the RHI with that model and makes attachments first-class participants of the resource transition system. + +### Attachment Descriptions + +```csharp +public enum LoadAction +{ + Load, + Clear, + DontCare +} + +public enum StoreAction +{ + Store, + DontCare, + Resolve +} + +public record struct ColorAttachmentDesc +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public Texture? ResolveTexture; + + public TextureSubresource ResolveSubresource; + + public LoadAction LoadAction; + + public StoreAction StoreAction; + + public Vector4 ClearColor; +} + +public record struct DepthStencilAttachmentDesc +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public LoadAction DepthLoadAction; + + public StoreAction DepthStoreAction; + + public LoadAction StencilLoadAction; + + public StoreAction StencilStoreAction; + + public float ClearDepth; + + public byte ClearStencil; +} +``` + +Attachment descs reference `Texture` + `TextureSubresource` (a single mip × single layer — what an RTV / DSV always is). The backend resolves the RTV / DSV through the parent resource's `TextureSubresource`-keyed pool, so identical attachments across frames share one descriptor. + +### `BeginRenderPass` + +```csharp +public void BeginRenderPass(ReadOnlySpan colorAttachments, + DepthStencilAttachmentDesc? depthStencilAttachment); + +public void EndRenderPass(); +``` + +The `depthStencilAttachment` parameter has **no default value**. Callers without a depth-stencil target must pass `null` explicitly. This forces the user to acknowledge the choice at the call site instead of silently dropping the depth attachment because of a missing argument. + +The implementation: + +1. Transitions every color attachment to render-target state and the depth attachment (if any) to depth-stencil state, using the backend's tracked-state machinery from Part 3. The user does **not** transition attachments before the pass. +2. Calls the backend `BeginRenderPassCore(...)`. + +`EndRenderPass()` does **not** auto-revert attachments to a previous state; the texture remains in render-target state until the next user-issued transition (typically to `ShaderResource` for the next pass, or implicitly to present state via `SwapChain.Present`). + +`SetViewports` / `SetScissors` are issued separately by the user inside the pass. + +### Pipeline `Output` + +`GraphicsPipelineDesc.Output` (color formats + depth format + sample count) is unchanged. A pipeline is compatible with any `BeginRenderPass` call whose attachment formats and sample count match its `Output`. This stays the user's responsibility, exactly as today; only the per-frame bundling changes. + +### Layered / Multiview + +Layered rendering is detected per-attachment from `Texture.Desc.ArrayLayers > 1` combined with the absence of an explicit single-layer `TextureSubresource`. A future extension can add an explicit `LayerCount` argument to `BeginRenderPass` if needed; the current proposal omits it to keep the entry point minimal. + +### Backend Mapping + +| Backend | Begin | End | +|---|---|---| +| DX12 | `OMSetRenderTargets` (or `BeginRenderPass` with `RENDER_PASS_RENDER_TARGET_DESC` for tile resources) | `EndRenderPass` if used, otherwise nothing | +| Vulkan 1.3 | `vkCmdBeginRendering` with `VkRenderingInfo` built from the attachment span (dynamic rendering is core in 1.3) | `vkCmdEndRendering` | +| Metal 4 | `MTL4CommandBuffer.RenderCommandEncoder(MTLRenderPassDescriptor)` built from the attachment span | encoder `EndEncoding` | + +--- + +## Part 8 — `SwapChain` Without `FrameBuffer` + +### Interaction With Queue Synchronization + +Every coupling point between swapchain and queues is expressed as a `CommandSubmission`, so no new primitive leaks out. The API surface has no `AcquireNextImage()` and no first-frame helper field. `Present(...)` returns a `CommandSubmission` representing the next backbuffer's readiness, and the very first frame's image-ready submission is the **caller's** responsibility — typically `default(CommandSubmission)`, which `CommandSubmission.Wait()` treats as "no wait needed". + +Two edges exist in a typical frame: + +1. **Submit → Present**: the present engine must not read the backbuffer until the GPU finishes drawing. `SwapChain.Present(params ReadOnlySpan waits)` accepts the submissions that produced the backbuffer contents and turns them into the appropriate GPU-side wait before scheduling present. +2. **Present → next Submit**: a backbuffer is only safe to write after the present engine releases it. `Present(...)` returns a `CommandSubmission` describing that release point; the next frame's `cmd.Submit(...)` waits on it. The very first frame uses whatever the caller decides — `default(CommandSubmission)` for the simple case, an explicit hand-off submission for advanced flight-frame strategies. + +The public API does **not** add a `cmd.Submit(SwapChain)` overload or a `SwapChain.SubmitAndPresent(...)` shortcut. Coupling submit to the swapchain on the API surface would: + +- Force every multi-pass frame (e.g. shadow + main + post) to either wrap the whole frame in one submit or pull the swapchain through every layer. +- Conflict with cross-queue scheduling, where the final write to the backbuffer may come from compute, not graphics. +- Be redundant: the existing `CommandSubmission` value already carries the necessary edge through `Present(waits)`. + +Keeping submit and present orthogonal — and bridging them through `CommandSubmission` — is what makes this RHI directly reusable by an RDG layer that schedules many passes per frame. + +### Public Surface + +```csharp +public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) +{ + private SwapChainDesc desc = desc; + + public ref readonly SwapChainDesc Desc => ref desc; + + public abstract uint Width { get; } + + public abstract uint Height { get; } + + /// Current backbuffer index. Internal — backend code only; not part of the public surface. + internal abstract uint CurrentImageIndex { get; } + + public abstract Texture CurrentColorTarget { get; } + + public abstract Texture? CurrentDepthStencilTarget { get; } + + /// + /// Submits a present operation that waits on on the GPU. + /// Returns a CommandSubmission that signals when the next frame's backbuffer is safe to write. + /// The first frame's image-ready value must be supplied by the caller (typically default(CommandSubmission)). + /// + public abstract CommandSubmission Present(params ReadOnlySpan waits); + + public void Resize(uint width, uint height) { /* unchanged */ } + + public void Refresh(Surface surface) { /* unchanged */ } +} +``` + +The backbuffer is exposed as a plain `Texture` — no view object. Users build attachment descs from `(swapChain.CurrentColorTarget, new TextureSubresource())`, which the backend resolves through the texture's `TextureSubresource`-keyed RTV pool. `CurrentImageIndex` is `internal` because no public API needs it; backend code that does need it (e.g. building per-frame command structures) reaches it through the same-assembly access. + +### Frame Loop + +```csharp +// First-frame seed: caller decides. default(CommandSubmission) is the simple case. +CommandSubmission imageReady = default; + +while (running) +{ + CommandBuffer cmd = ctx.Graphics.CommandBuffer(); + + ReadOnlySpan colors = + [ + new() + { + Texture = swapChain.CurrentColorTarget, + Subresource = new(), + LoadAction = LoadAction.Clear, + StoreAction = StoreAction.Store, + ClearColor = new(0, 0, 0, 1) + } + ]; + + DepthStencilAttachmentDesc? depth = swapChain.CurrentDepthStencilTarget is { } ds + ? new() + { + Texture = ds, + Subresource = new(), + DepthLoadAction = LoadAction.Clear, + DepthStoreAction = StoreAction.DontCare, + ClearDepth = 1.0f + } + : null; + + cmd.BeginRenderPass(colors, depth); + + Span viewports = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 }]; + Span scissors = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height }]; + + cmd.SetViewports(viewports); + cmd.SetScissors(scissors); + // draws + cmd.EndRenderPass(); + + CommandSubmission frame = cmd.Submit(imageReady); + + imageReady = swapChain.Present(frame); +} +``` + +Every step in the loop has the same shape: takes a set of `CommandSubmission` waits, returns a `CommandSubmission` for the next consumer. `Submit` and `Present` are symmetric on the API surface. + +### Backend Mapping + +| Backend | `Present` | Returned `CommandSubmission` | +|---|---|---| +| DX12 | `IDXGISwapChain::Present`; `waits` are translated into queue `Wait` calls before present | a value derived from the frame-latency waitable object signalling when the next backbuffer is releasable | +| Vulkan 1.3 | `vkQueuePresentKHR` waits on a per-present binary semaphore that the last `CommandSubmission`'s queue signals as part of its submit | adapter that wraps the next-frame `vkAcquireNextImageKHR` binary semaphore so it appears as a wait edge during the next `Submit` | +| Metal 4 | `drawable.Present()` after waiting on the `waits` timeline values | the next drawable's availability event | + +Vulkan is the only backend where binary semaphores must coexist with timelines. Internally the Vulkan swapchain holds: + +- One binary `VkSemaphore` per in-flight frame (`imageAvailable[N]`) — signaled by `vkAcquireNextImageKHR`, exposed through the value returned by `Present`. +- One binary `VkSemaphore` per in-flight frame (`renderFinished[N]`) — waited by `vkQueuePresentKHR`. +- These binaries are wrapped in a private `CommandSubmission`-compatible adapter so the public API stays uniform across backends. + +This isolates the binary-vs-timeline asymmetry inside the Vulkan swapchain, where it belongs. + +--- + +## Migration Notes + +The redesign is breaking. Affected APIs: + +- `CommandQueue.WaitIdle()` → `CommandQueue.WaitForIdle()`. +- All `*Impl` overrides project-wide → `*Core` (`SubmitImpl→SubmitCore`, `WaitIdleImpl→WaitCore`, `SetImpl→SetCore`, `PreprocessImpl→PreprocessCore`, `GetResultsImpl→GetResultsCore`, `ResizeImpl→ResizeCore`, `RefreshImpl→RefreshCore`, `CopyBufferImpl→CopyBufferCore`, `BeginRenderPassImpl→BeginRenderPassCore`, ...). +- `CommandQueue` exposes no public per-value `Wait(ulong)`. Use `submission.Wait()`. The hooks `CompletedValueCore` (now a property) and `WaitCore(ulong)` are `protected internal` so `CommandSubmission` can call them directly. +- All expression-bodied `=>` members on RHI types are rewritten with `{ ... }` bodies, **except** `ref` / `ref readonly` returning properties (`public ref readonly TDesc Desc => ref desc;`), which keep the `=>` form. +- `CommandBuffer.Submit(bool waitForCompletion)` is removed. Use `Submit()` / `Submit().Wait()`. +- `CommandBuffer.BeginRenderPass(FrameBuffer, ClearValue, params IEnumerable)` is removed. Use `BeginRenderPass(ReadOnlySpan, DepthStencilAttachmentDesc?)`; the depth-stencil parameter has no default value (pass `null` explicitly). +- `FrameBuffer`, `FrameBufferDesc`, `FrameBufferAttachment`, `RenderPassDesc`, `ClearValue`, `ClearValues`, `ClearFlags` are removed. +- `BufferView`, `BufferViewDesc`, `TextureView`, `TextureViewDesc`, `IBindableResource` are removed. +- `TextureSlice`, `TextureAspect`, `TextureSubresourceLayers`, `BufferViewType` are removed. +- `CommandBuffer.Transition(Texture, TextureSubresourceRange, TransitionState)` is **not** part of the public API; the corresponding backend primitive (`TransitionRangeCore`) remains internal for an RDG layer. +- `GraphicsContext.CreateBufferView(...)` / `CreateTextureView(...)` / `CreateFrameBuffer(...)` are removed. +- `ResourceTable.Write(uint, IBindableResource)` and its array / params variants are removed. Use the typed overloads in Part 5. +- `ResourceTable.Preprocess(CommandBuffer)` and the `preprocessResourceTables` parameter on `BeginRenderPass` are removed. +- `CommandBuffer.SetResourceTable(uint set, ResourceTable)` and `SetResourceTables(uint firstSet, ReadOnlySpan)` are removed. Use `PushResourceTable(ShaderStageFlags stages, ResourceTable table)` with the existing `ShaderStageFlags` enum. +- `Copy*` and `Upload(Texture, ...)` signatures change to take `TextureSubresourceRange` + `Offset3D` + `Extent3D`. Copy / upload paths require `LevelCount == 1`. +- All byte-denominated parameters now carry the `*InBytes` suffix (`offsetInBytes`, `sizeInBytes`, `sourceOffsetInBytes`, `destinationOffsetInBytes`, `sourceRowPitchInBytes`, `destinationRowPitchInBytes`). `BufferRange` fields rename to `OffsetInBytes`, `SizeInBytes`, `StrideInBytes`. +- `CommandBuffer.SetViewport(Viewport)` and `SetScissor(Rect)` → `SetViewports(ReadOnlySpan)` and `SetScissors(ReadOnlySpan)`. +- Render-pass attachment descs replace any `TextureView View` field with `Texture Texture` + `TextureSubresource Subresource`. +- The DX12 `DXBuffer.View` / `DXTexture.View` fields are removed; descriptor allocation moves into the per-range cache on the parent resource. +- The Vulkan auto-created `VKTexture.View` is removed; the cache lazily creates one `VkImageView` per unique `TextureSubresourceRange`. +- `SwapChain.FrameBuffer`, `SwapChain.AcquireNextImage()`, `SwapChain.FirstFrameImageReady`, and the public `SwapChain.CurrentImageIndex` are removed; replaced by `CurrentColorTarget` / `CurrentDepthStencilTarget` / `CommandSubmission Present(...)`. `CurrentImageIndex` is now `internal`. +- Cube faces are no longer addressed by a separate `Face` field. Replace `new TextureSlice { ArrayLayer = i, Face = f }` with `ArrayLayer = i * 6 + f`. +- All `in` parameters on the public RHI are removed. +- Every public method that took a managed array (`T[]`, `params T[]`, `IEnumerable`) now takes `ReadOnlySpan` (or `params ReadOnlySpan` for trailing parameters). + +The internal Vulkan `VKFence` (binary fence) is retained for swapchain use only. + +--- + +## RDG and Third-Party Compatibility + +This RHI is sufficient for a future render dependency graph to layer on top, and friendly to third-party adapters: + +- **RDG.** Each pass is a function that records a section of a `CommandBuffer`. Resource access is declared at pass build time as `Buffer` / `Texture` plus `BufferRange` / `TextureSubresourceRange`. The RDG can compute exact transitions and emit `Transition(...)` / `MemoryBarrier()` calls between passes; the backend coalesces them. For sub-range or per-aspect transitions the RDG calls into the backend's internal primitive (`TransitionRangeCore`) directly; the public surface stays minimal. All range types are `record struct` and therefore equatable, so the RDG can hash them directly when planning aliases. Cross-queue scheduling reuses `CommandSubmission` waits; no new RHI primitive is needed. Identical bindings across passes share one cached backend object on the parent for free. Inline render passes mean the RDG can build attachment arrays per pass without allocating long-lived `FrameBuffer` objects. Aliased / transient resources can be added later by introducing a transient memory allocator beneath `Texture` / `Buffer` without changing the public RHI. +- **Third-party adapters.** Libraries that already speak Vulkan / DX12 conventions (ImGui backends, glTF loaders, profilers, render-graph layers) can adopt the RHI without translation: the subresource shapes have the same names and the same axes as `VkImageSubresource*`. There is no bespoke `Face` axis or default-view object to special-case. diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md new file mode 100644 index 00000000..d188330d --- /dev/null +++ b/rhi-redesign.zh.draft.md @@ -0,0 +1,549 @@ +# RHI 重设计草案(中文临时版) + +> 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 + +## 设计目标 + +- 公共 API 表面尽量小、调用形态统一(结构体一律 `record struct`,参数无 `in`) +- 帧循环代码短、零托管分配 +- 与 DirectX 12 / Vulkan 1.3 / Metal 4 的真实命令模型 1:1 对应 +- 后续 RDG 层直接复用,无需破坏性变更 +- 第三方适配(ImGui / glTF / 性能分析器)无需翻译层 + +## 命名与代码风格约定 + +- 所有公共值类型为 `record struct`,公共字段可写;不使用 `ref struct`,不使用 `in` 参数 +- 多元素入参一律 `ReadOnlySpan` / `params ReadOnlySpan`,不使用 `T[]` / `IEnumerable` +- 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性(如 `public ref readonly TDesc Desc => ref desc;`)保留 `=>` 形式 +- 所有以字节为单位的字段与参数加 `*InBytes` 后缀 +- `slot` / `firstSlot` / `binding` 等图形领域标准术语保留无后缀 +- 后端覆写钩子统一以 `*Core` 结尾(项目内现有 `*Impl` 全部改名为 `*Core`,`SetImpl` / `PreprocessImpl` / `GetResultsImpl` / `ResizeImpl` / `RefreshImpl` / `WaitIdleImpl` / `SubmitImpl` / `CopyBufferImpl` 等等都同步更名) +- 公共同步结果只有一种:`CommandSubmission` + +## 公共类型一览 + +```csharp +// === 子资源 === + +public record struct TextureSubresource +{ + public uint MipLevel; + + public uint ArrayLayer; +} + +public record struct TextureSubresourceRange +{ + public uint BaseMipLevel; + + public uint LevelCount; + + public uint BaseArrayLayer; + + public uint LayerCount; +} + +public record struct Offset3D +{ + public int X; + + public int Y; + + public int Z; +} + +public record struct Extent3D +{ + public uint Width; + + public uint Height; + + public uint Depth; +} + +// === Buffer 子范围 === + +public record struct BufferRange +{ + public Buffer Buffer; + + public ulong OffsetInBytes; + + public ulong SizeInBytes; + + public uint StrideInBytes; + + public static implicit operator BufferRange(Buffer buffer) + { + return new() + { + Buffer = buffer, + OffsetInBytes = 0, + SizeInBytes = buffer.Desc.SizeInBytes, + StrideInBytes = buffer.Desc.StrideInBytes + }; + } +} + +// === 状态转换 === + +public enum TransitionState +{ + ShaderResource, + UnorderedAccess +} + +// === 渲染附件 === + +public enum LoadAction +{ + Load, + Clear, + DontCare +} + +public enum StoreAction +{ + Store, + DontCare, + Resolve +} + +public record struct ColorAttachmentDesc +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public Texture? ResolveTexture; + + public TextureSubresource ResolveSubresource; + + public LoadAction LoadAction; + + public StoreAction StoreAction; + + public Vector4 ClearColor; +} + +public record struct DepthStencilAttachmentDesc +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public LoadAction DepthLoadAction; + + public StoreAction DepthStoreAction; + + public LoadAction StencilLoadAction; + + public StoreAction StencilStoreAction; + + public float ClearDepth; + + public byte ClearStencil; +} + +// === 视口 / 裁剪 === + +public record struct Viewport +{ + public float X; + + public float Y; + + public float Width; + + public float Height; + + public float MinDepth; + + public float MaxDepth; +} + +public record struct Rect +{ + public int X; + + public int Y; + + public uint Width; + + public uint Height; +} + +// === 队列同步结果 === + +public readonly struct CommandSubmission(CommandQueue queue, ulong value) +{ + public void Wait() + { + if (queue is null || queue.CompletedValueCore >= value) + { + return; + } + + queue.WaitCore(value); + } +} +``` + +注:`ShaderStageFlags` 沿用项目现有定义(`None / Vertex / Pixel / Compute / Amplification / Mesh`),本设计不重新声明。 + +## 公共资源句柄 + +只有三个:`Buffer`、`Texture`、`Sampler`。 + +```csharp +public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) +{ + private TextureDesc desc = desc; + + public ref readonly TextureDesc Desc => ref desc; + + public static implicit operator TextureSubresourceRange(Texture texture) + { + return new() + { + BaseMipLevel = 0, + LevelCount = texture.Desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = texture.Desc.ArrayLayers + }; + } +} +``` + +`Buffer` / `Sampler` 同样以 `public ref readonly XxxDesc Desc => ref desc;` 暴露描述。 + +**移除**:`BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `IBindableResource` / `TextureSlice` / `TextureAspect` / `TextureSubresourceLayers` / `BufferViewType` / `FrameBuffer` 系列 / `RenderPassDesc` / `ClearValue` 系列。 + +**view 缓存**:每个后端 `Texture` / `Buffer` 内部维护 `Dictionary` / `Dictionary`,按 range 值类型为键,懒加载,与父资源同生命周期。同范围多次绑定共用一个后端对象。 + +## CommandQueue / CommandBuffer + +```csharp +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +{ + private readonly Lock @lock = new(); + private readonly Queue available = []; + private readonly Queue execution = []; + + private ulong nextValue = 1; + private ulong lastSignaledValue; + + public CommandQueueType Type { get; } = type; + + /// 从池中借出一个已 Begin 的命令缓冲。 + public CommandBuffer CommandBuffer() + { + using Lock.Scope _ = @lock.EnterScope(); + + CollectCompleted(); + + CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBufferCore() : available.Dequeue(); + + commandBuffer.Begin(); + + return commandBuffer; + } + + /// 等待此 queue 上所有已提交的命令完成。 + public void WaitForIdle() + { + using Lock.Scope _ = @lock.EnterScope(); + + if (lastSignaledValue is not 0) + { + WaitCore(lastSignaledValue); + } + + CollectCompleted(); + } + + internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) + { + using Lock.Scope _ = @lock.EnterScope(); + + CollectCompleted(); + + commandBuffer.End(); + + ulong value = nextValue++; + + SubmitCore(commandBuffer, waits, value); + + lastSignaledValue = value; + + execution.Enqueue(new(commandBuffer, value)); + + return new(this, value); + } + + private void CollectCompleted() + { + ulong completed = CompletedValueCore; + + while (execution.Count > 0 && execution.Peek().Value <= completed) + { + CommandBuffer cmd = execution.Dequeue().CommandBuffer; + + cmd.Reset(); + + available.Enqueue(cmd); + } + } + + /// 当前已完成的时间线值。同程序集内(如 )可直接访问,子类必须实现。 + protected internal abstract ulong CompletedValueCore { get; } + + protected abstract CommandBuffer CreateCommandBufferCore(); + + protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + + /// 等待此 queue 时间线达到给定值。同程序集内(如 )可直接访问,子类必须实现。 + protected internal abstract void WaitCore(ulong value); + + private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); +} + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandSubmission Submit(params ReadOnlySpan waits) + { + return queue.Submit(this, waits); + } +} +``` + +要点: +- `CommandQueue` 不再公开 `Wait(ulong)`。`CommandSubmission.Wait()` 通过 `protected internal` 直接调用 `CompletedValueCore` 与 `WaitCore`,省掉一层包装方法 +- `*Core` 后缀替代旧的 `*Impl`,覆盖项目内所有抽象覆写点 +- 后端时间线:DX12 `ID3D12Fence` / Vulkan timeline `VkSemaphore`(核心于 1.2,VK 1.3 标配) / Metal `MTLSharedEvent`。Vulkan 二进制 `VkFence` 仅在 swapchain 内部使用 + +## 资源状态转换 + +```csharp +public void Transition(Buffer buffer, TransitionState newState); + +public void Transition(Texture texture, TransitionState newState); + +public void MemoryBarrier(); +``` + +- 没有子范围 transition 重载(RDG 用例,公共 API 不暴露;后端内部仍提供给 RDG 直调) +- `MemoryBarrier()` 用于跨资源、跨阶段的全局内存同步;CommandBuffer 同时维护"上一次 dispatch 写过的 UAV 集合",下一次 dispatch 若读到同一资源会自动插入 barrier 作为 fallback +- 渲染目标 / 深度 / copy / 顶点 / 索引 / CBV / indirect / present 状态都由对应操作隐式转换 +- 用户只在 `ShaderResource` ↔ `UnorderedAccess` 之间显式转换 + +每个 `Texture` / `Buffer` 跟踪当前 `TransitionState`,初始化时由后端默认设置,第一次显式 `Transition` 时根据当前值生成 from→to barrier。 + +## ResourceTable + +```csharp +public abstract class ResourceTable +{ + public void Write(uint binding, BufferRange range); + + public void Write(uint binding, TextureSubresourceRange range); + + public void Write(uint binding, Sampler sampler); + + public void Write(uint binding, ReadOnlySpan ranges); + + public void Write(uint binding, ReadOnlySpan ranges); + + public void Write(uint binding, ReadOnlySpan samplers); +} +``` + +- `Buffer` / `Texture` 的隐式转换让单资源绑定一行代码 +- 没有 `BufferViewType` 参数,buffer 解释方式由对应槽位的 `ResourceLayout` 决定 +- 没有 `IBindableResource` 标记接口 + +## CommandBuffer 操作 + +```csharp +// 顶点 / 索引 +public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); + +public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); + +public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); + +// Buffer 拷贝 / 上传 +public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, + Buffer destination, ulong destinationOffsetInBytes, + ulong sizeInBytes); + +public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; + +// Texture 拷贝 / 上传 / Resolve +public void CopyTexture(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, + Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, + Extent3D extent); + +public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, + Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, + Extent3D extent); + +public void CopyTextureToBuffer(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, Extent3D extent, + Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); + +public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, + Texture destination, TextureSubresource destinationSubresource); + +public void Upload(Texture destination, TextureSubresourceRange range, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; + +// 状态转换 +public void Transition(Buffer buffer, TransitionState newState); + +public void Transition(Texture texture, TransitionState newState); + +public void MemoryBarrier(); + +// 资源绑定(按 stage 推送) +public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); + +// 渲染 pass +public void BeginRenderPass(ReadOnlySpan colorAttachments, + DepthStencilAttachmentDesc? depthStencilAttachment); + +public void EndRenderPass(); + +// 视口 / 裁剪(复数 + span) +public void SetViewports(ReadOnlySpan viewports); + +public void SetScissors(ReadOnlySpan scissors); + +// 绘制 / 调度 +public void Draw(...); + +public void DrawIndexed(...); + +public void Dispatch(uint groupsX, uint groupsY, uint groupsZ); +``` + +要点: +- 全部参数无 `in` +- 字节相关参数统一 `*InBytes` 后缀 +- copy / upload 用 `TextureSubresourceRange`(拷贝时校验 `LevelCount == 1`),不再有 `TextureSubresourceLayers` +- `Aspect` 字段消失,aspect 由 `Texture.Format` 推断 +- `BeginRenderPass` 直接接收 attachment span,没有 `RenderPassDesc` 类型;无深度时显式传 `null` +- `PushResourceTable` 接受 `ShaderStageFlags`,不再有 set 索引;后端按 stage 与资源 layout 直接绑定 + +### `PushResourceTable` 的设计取舍 + +- `SetResourceTable(uint set, ResourceTable)` 假设有 set 概念,仅 Vulkan 描述符集天然契合,DX12 / Metal 均需要做翻译层 +- 借鉴 Metal 的 `setVertexBuffer:atIndex:` / `setFragmentBuffer:atIndex:` 风格:调用方告诉 RHI"把这张表绑给这些 stage" +- DX12:根据 stages 选择对应 root parameter(VS/PS/CS visibility) +- Vulkan:layout 编译期固定,按 stage 走 push descriptor 或独立 set +- Metal:对应 stage 的 argument table + +## SwapChain + +```csharp +public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) +{ + private SwapChainDesc desc = desc; + + public ref readonly SwapChainDesc Desc => ref desc; + + public abstract uint Width { get; } + + public abstract uint Height { get; } + + /// 当前 backbuffer 索引。仅供后端与同程序集内部使用。 + internal abstract uint CurrentImageIndex { get; } + + public abstract Texture CurrentColorTarget { get; } + + public abstract Texture? CurrentDepthStencilTarget { get; } + + /// + /// 提交 present,等待 完成;返回的 submission 表示下一张 backbuffer 已就绪。 + /// 首帧没有上一帧 present 可等待时,外部应自行准备首张就绪信号 + /// (典型做法:传 default(CommandSubmission),由后端识别为"无需等待")。 + /// + public abstract CommandSubmission Present(params ReadOnlySpan waits); + + public void Resize(uint width, uint height); + + public void Refresh(Surface surface); +} +``` + +设计要点: +- `Acquire` 完全消失。SwapChain 内部在 `Present` 时同步前进 backbuffer 索引并准备下一张图像的就绪信号 +- `Present` 返回 `CommandSubmission`,与 `CommandBuffer.Submit` 形态完全一致 +- 不提供首帧便利字段;首帧的"零号 submission"由用户自行准备 +- backbuffer 直接是 `Texture`,无 view 类型 +- `CurrentImageIndex` 不公开,仅后端跨文件协作时使用 + +后端映射: + +| 后端 | `Present` | 下一帧就绪信号 | +|---|---|---| +| DX12 | `IDXGISwapChain::Present`,`waits` 转为 queue Wait | 内部用 frame latency event 转成 `CommandSubmission` | +| Vulkan 1.3 | `vkQueuePresentKHR` 等待 `renderFinished[N]` 二进制 semaphore | `vkAcquireNextImageKHR` 在 Present 调用末尾执行,结果包装成时间线对齐的 `CommandSubmission` | +| Metal 4 | `drawable.Present()` 后等下一个 drawable | 下一帧 drawable 的可用事件 | + +## 帧循环 + +```csharp +// 用户决定首帧 image-ready 的来源;最简单的方式是 default。 +CommandSubmission imageReady = default; + +while (running) +{ + CommandBuffer cmd = ctx.Graphics.CommandBuffer(); + + ReadOnlySpan colors = + [ + new() + { + Texture = swapChain.CurrentColorTarget, + Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, + LoadAction = LoadAction.Clear, + StoreAction = StoreAction.Store, + ClearColor = new(0, 0, 0, 1) + } + ]; + + DepthStencilAttachmentDesc? depth = swapChain.CurrentDepthStencilTarget is { } ds + ? new() + { + Texture = ds, + Subresource = new(), + DepthLoadAction = LoadAction.Clear, + DepthStoreAction = StoreAction.DontCare, + ClearDepth = 1.0f + } + : null; + + cmd.BeginRenderPass(colors, depth); + + Span viewports = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 }]; + Span scissors = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height }]; + + cmd.SetViewports(viewports); + cmd.SetScissors(scissors); + // draws + cmd.EndRenderPass(); + + CommandSubmission frame = cmd.Submit(imageReady); + + imageReady = swapChain.Present(frame); +} +``` + +形态:每一步都是"接收一组 `CommandSubmission`,返回一个 `CommandSubmission`"。 + +## RDG 兼容性 + +虽然简化版砍掉了子范围 transition,未来 RDG 层可以: +- 在 RDG 自身的命令缓冲适配器内调用后端私有方法 `TransitionRangeCore`,跳过公共 API +- 用自己的 hazard 跟踪覆盖隐式 UAV barrier;显式 `MemoryBarrier()` 仍可被 RDG 复用 +- 复用 `CommandSubmission` 处理跨队列依赖 +- 复用 `record struct` 子资源类型作为 hash key From c9e877b830e2b875ba825857962acd5ce9367209 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 22 Apr 2026 10:22:52 +0800 Subject: [PATCH 026/466] Update WindowsAppSDK and Slangc.NET package versions Updated Microsoft.WindowsAppSDK to 1.8.260416003 and Slangc.NET to 2026.7.0 to keep dependencies current and benefit from the latest improvements and fixes. --- sources/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index a2cbb6f9..b45f13f8 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -9,7 +9,7 @@ - + @@ -20,7 +20,7 @@ - + From e2a6cc68f9ab3accf6dbaf801865fad7fa206fd2 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 22 Apr 2026 14:25:28 +0800 Subject: [PATCH 027/466] Refactor RHI design document: clarify goals, naming conventions, and API structure - Updated design goals to emphasize API surface area, frame loop efficiency, and resource state management. - Introduced clear distinctions between goals and non-goals for the RHI implementation. - Revised naming conventions for public types and methods, ensuring consistency and clarity. - Enhanced documentation for resource subresource types and their intended use cases. - Removed deprecated types and interfaces, streamlining the API for resource management. - Improved command queue and command buffer handling, including submission and synchronization mechanisms. - Clarified RenderPass handling and resource binding processes, ensuring alignment with modern graphics APIs. - Added detailed mapping of backend implementations for SwapChain and frame loop operations. - Included compatibility checks for RDG requirements, ensuring the RHI meets necessary interface standards. --- rhi-redesign.draft.md | 962 +++++++++++++-------------------------- rhi-redesign.zh.draft.md | 438 ++++++++++-------- 2 files changed, 575 insertions(+), 825 deletions(-) diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index 259dafa3..d4fd41aa 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -1,243 +1,62 @@ # RHI Redesign -This document defines the proposed evolution of Zenith.NET's RHI: queue submission, synchronization, resource transitions, the resource and subresource model, render passes, and the swapchain. It is a temporary design note for API iteration and does not describe the current implementation. +> Working document for the redesign discussion. Not for commit. The Chinese mirror lives in `rhi-redesign.zh.draft.md`. -The redesign targets five deficiencies in the current RHI: +## 1. Goals and Non-Goals -1. **No cross-queue submission synchronization.** `WaitIdle()` is the only available primitive. -2. **No explicit resource state transition API.** Backends transition resources implicitly inside individual operations, which a render dependency graph (RDG) cannot rely on. -3. **`FrameBuffer` is a heavy long-lived object** that does not match the inline render-pass model used by modern APIs (`vkCmdBeginRendering`, `ID3D12GraphicsCommandList4::BeginRenderPass`, `MTLRenderPassDescriptor`), and it forces `SwapChain` to expose a `FrameBuffer`. -4. **The `Resource → View` two-level binding model duplicates work.** Every `Buffer` / `Texture` constructs a default `View` (DX12 lazily allocates CBV+SRV+UAV descriptor tokens; Vulkan eagerly allocates a full-range `VkImageView`); creating an explicit `BufferView` / `TextureView` for the same range allocates a second set. `BufferView` / `TextureView` are public types that almost never need to be public. -5. **Subresource shapes are bespoke.** `TextureSlice { MipLevel, ArrayLayer, Face }` carries a `Face` axis none of the three target APIs has, and is the same type for both copy addressing and view addressing. +**Goals** -The end goal is a small, canonical RHI that maps 1:1 to DirectX 12, **Vulkan 1.3**, and Metal 4, that an RDG layer can consume without further breaking changes, and that third-party adapters (ImGui backends, asset loaders, profilers) can drive without translation. +- Small, uniform public API surface that maps 1:1 to DirectX 12 / Vulkan 1.4 / Metal 4. +- Short frame-loop code with no managed allocations on the hot path. +- Cross-queue synchronization (Graphics / Compute / Copy) as a first-class primitive. +- Explicit resource state transitions as a stable foundation for a future RDG. +- Inline render passes; no long-lived `FrameBuffer`. +- Subresource / sub-range information expressed at the **call site** as a value type, not as a long-lived `View` object. -## API-Surface Conventions +**Non-Goals** -- All value types are `record struct` with public mutable fields. No `ref struct`. -- No `in` parameters anywhere on the public RHI. Small structs (≤ 16 B) are cheaper to pass by value; uniformity beats marginal perf wins on larger structs. -- All multi-element parameters are `ReadOnlySpan` (or `params ReadOnlySpan` in trailing position). No `T[]`, no `IEnumerable`, no `params T[]`. -- All method bodies use `{ ... }`, never expression-bodied `=>`. **Exception:** `ref` / `ref readonly` returning properties keep the `=> ref _field;` form (e.g. `public ref readonly TDesc Desc => ref desc;`), which has no equivalent block syntax that reads as cleanly. -- Every byte-denominated parameter or field carries the `*InBytes` suffix (`OffsetInBytes`, `SizeInBytes`, `RowPitchInBytes`, ...). Index-style identifiers (`slot`, `firstSlot`, `binding`) follow standard graphics terminology and stay unsuffixed. -- Abstract hooks that backends override carry the `*Core` suffix. This is a project-wide rename of every existing `*Impl` member (`WaitIdleImpl`, `SubmitImpl`, `SetImpl`, `PreprocessImpl`, `GetResultsImpl`, `ResizeImpl`, `RefreshImpl`, `CopyBufferImpl`, etc.). Where appropriate, `*Core` is exposed as a property (`CompletedValueCore`). -- The only public synchronization result type is `CommandSubmission`. Both `CommandBuffer.Submit` and `SwapChain.Present` return it. -- Existing enums and types are reused, not redefined. In particular, the existing `ShaderStageFlags` (`None / Vertex / Pixel / Compute / Amplification / Mesh`, `[Flags]`) is the stage selector for `PushResourceTable`; this design adds no new stage enum. - -## Goals - -- Keep frame-loop user code short and readable. -- Avoid per-submit, per-pass, and per-bind managed allocations on the hot path. -- Support cross-queue synchronization between `Graphics`, `Compute`, and `Copy`. -- Expose explicit resource transitions for the two shader-side states an RDG actually needs. -- Replace `FrameBuffer` with attachments passed directly to `BeginRenderPass`. -- Have `SwapChain` expose backbuffer textures and a single uniform `Present(...)` returning a `CommandSubmission`. -- Reduce public RHI handles to `Buffer`, `Texture`, `Sampler`. -- Express sub-range information at the **call site** as a value type, not as a long-lived resource. -- Cache backend descriptors / `VkImageView`s / `MtlTexture` slices on the parent resource keyed by the value-type range, so identical bindings share one backend object. - -## Non-Goals - -- Implicit / tracked resource state inside the RHI beyond a single "current state" cache used to compute the source of explicit transitions. +- Full hazard tracking inside the RHI (only "current state" is cached, used to compute the source side of explicit transitions). - One synchronization object per submission. -- Using `WaitForIdle()` as the primary synchronization model. -- Building the full render dependency graph in this iteration. -- Bindless / descriptor-buffer paths. -- Removing `ResourceTable` / `ResourceLayout`. Those are independent and stay. -- Reworking `Sampler`, pipeline state, or shader IO in this iteration. -- Exposing per-aspect (color / depth / stencil) addressing on the public surface; backend infers aspect from `Texture.Format`. -- Exposing sub-range transitions; that is an RDG concern handled through internal backend primitives. -- Owning a "first-frame image ready" wait inside `SwapChain`; the caller is responsible for seeding the first submit. - ---- - -## Part 1 — Queue Synchronization - -### Model Summary - -Each `CommandQueue` owns one **monotonic completion timeline** that backs every submission to that queue. - -- A submission produces a `CommandSubmission` value identifying one point on its queue's timeline. -- Cross-queue dependencies are expressed by passing prior `CommandSubmission` values to the next `Submit` call. -- The timeline object itself is **not** a public type. Backends override timeline operations directly on `CommandQueue`. -- A separate **binary host fence** type stays internal to backends for swapchain image acquire / present, where binary semantics are mandatory (Vulkan). - -The naming is deliberate: the term *fence* retains its Vulkan / DX12 binary meaning and is only used internally. The timeline behavior lives on `CommandQueue`. There is no public type named `Fence` for general queue synchronization. - -### Public API +- Using `WaitForIdle` as the primary synchronization mechanism. +- Implementing the RDG, bindless, or descriptor-buffer paths in this iteration. +- Reworking `ResourceTable` / `ResourceLayout` / `Sampler` / `Pipeline` / shader IO outside of what is described here. +- Exposing per-aspect (color/depth/stencil) addressing, sub-range transitions, or "first-frame image ready" waiting on the public surface. + +## 2. API-Surface Conventions + +- All public value types: `record struct` with public mutable fields. No `ref struct`. No `in` parameters. +- Multi-element parameters: `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, no `IEnumerable`. +- All method bodies use `{ ... }`. **Exception:** `ref` / `ref readonly` returning properties keep `=> ref _field;`. +- Every byte-denominated parameter or field carries an `*InBytes` suffix. +- Backend-hook naming rule: the `*Core` suffix exists **only** to disambiguate from a **same-named** non-`Core` wrapper. + - `Wait(ulong)` wrapper → `WaitCore(ulong)` + - `Submit(...)` wrapper → `SubmitCore(...)` + - `CreateCommandBuffer()` has no same-named wrapper (the public factory is `CommandBuffer()`) → no `Core` suffix. + - `GetCompletedValue()` has no wrapper → no `Core` suffix. +- Every abstract member is plain `protected abstract`; same-assembly callers go through an `internal` wrapper. +- The only public synchronization result type is `CommandSubmission`. + +## 3. Public Type Catalog ```csharp -public readonly struct CommandSubmission(CommandQueue queue, ulong value) -{ - public void Wait() - { - if (queue is null || queue.CompletedValueCore >= value) - { - return; - } - - queue.WaitCore(value); - } -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public CommandSubmission Submit(params ReadOnlySpan waits) - { - return queue.Submit(this, waits); - } -} -``` - -`CommandSubmission` reaches into `CompletedValueCore` and `WaitCore` directly via `protected internal` access — there is no public `CommandQueue.Wait(ulong)` method to wrap them. `default(CommandSubmission)` carries a null queue reference and is treated as "already complete / no wait needed". - -### `CommandQueue` Surface - -The public surface is intentionally narrow: pool a command buffer, wait for the queue to drain, submit. Per-value waits are not part of the public surface — they are reached by calling `submission.Wait()`, which routes through `protected internal` hooks on the queue. - -```csharp -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) -{ - private readonly Lock @lock = new(); - private readonly Queue available = []; - private readonly Queue execution = []; - - private ulong nextValue = 1; - private ulong lastSignaledValue; - - public CommandQueueType Type { get; } = type; - - /// Borrow a command buffer from the pool; the returned buffer has already had Begin called. - public CommandBuffer CommandBuffer() - { - using Lock.Scope _ = @lock.EnterScope(); - - CollectCompleted(); - - CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBufferCore() : available.Dequeue(); - - commandBuffer.Begin(); - - return commandBuffer; - } - - /// Block until every submission on this queue has completed. - public void WaitForIdle() - { - using Lock.Scope _ = @lock.EnterScope(); - - if (lastSignaledValue is not 0) - { - WaitCore(lastSignaledValue); - } - - CollectCompleted(); - } - - internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) - { - using Lock.Scope _ = @lock.EnterScope(); - - CollectCompleted(); - - commandBuffer.End(); - - ulong value = nextValue++; - - SubmitCore(commandBuffer, waits, value); - - lastSignaledValue = value; - - execution.Enqueue(new(commandBuffer, value)); - - return new(this, value); - } - - private void CollectCompleted() - { - ulong completed = CompletedValueCore; - - while (execution.Count > 0 && execution.Peek().Value <= completed) - { - CommandBuffer cmd = execution.Dequeue().CommandBuffer; - - cmd.Reset(); - - available.Enqueue(cmd); - } - } - - /// Latest value the timeline has signalled to. Reachable from in the same assembly. - protected internal abstract ulong CompletedValueCore { get; } - - protected abstract CommandBuffer CreateCommandBufferCore(); - - protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); - - /// Block until the timeline reaches . Reachable from in the same assembly. - protected internal abstract void WaitCore(ulong value); - - private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); -} -``` - -### Why Not a Public `Fence` / `Timeline` Type - -The timeline is in 1:1 correspondence with its queue. There is no scenario where a user holds a fence independent of its queue, so promoting it to a public type only adds API surface and collides with the Vulkan binary `VkFence` (which has no value). +// === Subresources (rule: views use range, copy/upload uses layers, single-point uses single) === -Folding the timeline into `CommandQueue` keeps the public surface minimal and removes the naming conflict with the binary fence used by swapchain code. - -### Backend Mapping - -| Backend | Timeline object | Submit | `CompletedValueCore` | `WaitCore` | -|---|---|---|---|---| -| DX12 | one `ID3D12Fence` per queue | for each wait `queue.Wait(otherFence, otherValue)` → `ExecuteCommandLists` → `queue.Signal(fence, value)` | `fence.GetCompletedValue()` | `SetEventOnCompletion` + `WaitOne` | -| Vulkan 1.3 | one **timeline** `VkSemaphore` per queue (core in 1.2, ubiquitous in 1.3) | `VkTimelineSemaphoreSubmitInfo` packs wait `(semaphore, value)` pairs and the signal value into `VkSubmitInfo` | `vkGetSemaphoreCounterValue` | `vkWaitSemaphores` | -| Metal 4 | one `MTLSharedEvent` per queue | for each wait the command buffer encodes `EncodeWait(otherEvent, otherValue)`; before commit it encodes `EncodeSignal(event, value)` | `event.SignaledValue` | `event.Wait(value, timeout)` | - -The existing internal `VKFence` (binary `VkFence`) remains, used only by swapchain image acquire on Vulkan. - -### Usage - -```csharp -// Single-queue -graphicsCmd.Submit().Wait(); - -// Cross-queue -CommandSubmission upload = copyCmd.Submit(); -CommandSubmission cull = computeCmd.Submit(upload); -CommandSubmission frame = graphicsCmd.Submit(cull); -frame.Wait(); -``` - ---- - -## Part 2 — Canonical Subresource Types - -The following value types replace the single `TextureSlice`. They are used by transitions (Part 3), bindings (Part 5), command-buffer copies (Part 6), and render-pass attachments (Part 7). - -There are exactly two texture-subresource shapes: - -- `TextureSubresource` — single mip × single array layer. Used by RTV / DSV / `ResolveTexture`. -- `TextureSubresourceRange` — contiguous mip range × contiguous array-layer range. Used by views, transitions, and copy / upload (which validate `LevelCount == 1` at the call site). - -There is no public `TextureAspect` type — aspect (color vs depth vs stencil) is derived from `Texture.Format` by every backend that needs it. Stencil-only sampling is a rare, advanced case left to a future opt-in extension. - -### `TextureSubresource` — single subresource - -```csharp public record struct TextureSubresource { public uint MipLevel; public uint ArrayLayer; } -``` -Identifies exactly one subresource. Used by render-pass attachments (a render target is always a single mip × single array layer) and by `ResolveTexture`. Cube faces use `ArrayLayer = cubeIndex * 6 + face`; there is no `Face` field. +public record struct TextureSubresourceLayers +{ + public uint MipLevel; + + public uint BaseArrayLayer; -### `TextureSubresourceRange` — view / transition / copy addressing + public uint LayerCount; +} -```csharp public record struct TextureSubresourceRange { public uint BaseMipLevel; @@ -248,26 +67,14 @@ public record struct TextureSubresourceRange public uint LayerCount; } -``` -Maps to: - -- Vulkan: `VkImageSubresourceRange` directly (aspect filled in from `Texture.Format`). -- DX12: `D3D12_TEX*_ARRAY_*` view descs (`MostDetailedMip`, `MipLevels`, `FirstArraySlice`, `ArraySize`; `PlaneSlice` derived from format). -- Metal: `MTLTexture.MakeTextureView(levelRange, sliceRange)`. - -For copy / upload paths, callers set `LevelCount = 1`; the validation layer rejects multi-mip copies up-front rather than lowering them silently. - -### `Offset3D` and `Extent3D` - -```csharp public record struct Offset3D { - public int X; + public uint X; - public int Y; + public uint Y; - public int Z; + public uint Z; } public record struct Extent3D @@ -278,13 +85,9 @@ public record struct Extent3D public uint Depth; } -``` - -Used by the `Copy*` overloads to specify source / destination origin and size. Match `VkOffset3D` / `VkExtent3D`; trivially translated to DX12 `D3D12_BOX` and Metal origin / size pairs. `Z` / `Depth` collapse to `0` / `1` for 2D textures. -### `BufferRange` +// === Buffer sub-range === -```csharp public record struct BufferRange { public Buffer Buffer; @@ -306,135 +109,91 @@ public record struct BufferRange }; } } -``` - -Field naming aligns with `VkDescriptorBufferInfo` (`buffer`, `offset`, `range`), promoted to the `*InBytes` suffix to match `BufferDesc`. `StrideInBytes = 0` falls back to `Buffer.Desc.StrideInBytes`, eliminating the current double-storage of stride. - -There is no `BufferViewType` field. The interpretation (constant / structured / byte-address / typed) is fixed by the corresponding `ResourceLayout` slot at bind time. This avoids exposing a knob whose meaning differs between DX12 (which has typed buffers natively) and Metal (which does not) and removes a redundant choice the user would otherwise have to keep in sync with their layout. - ---- - -## Part 3 — Explicit Resource Transitions -### Why the RHI Must Expose Transitions +// === Resource state (public surface) === -Every backend has a notion of resource state: - -- DX12: `D3D12_RESOURCE_STATES` (or enhanced barriers' `D3D12_BARRIER_LAYOUT` / `D3D12_BARRIER_ACCESS`). -- Vulkan 1.3: `VkImageLayout` + `VkAccessFlags2` + `VkPipelineStageFlags2` (sync2 is core in 1.3). -- Metal: implicit hazard tracking, with explicit options via `MTLResourceUsage` and barriers between encoders. - -Today these transitions are emitted opaquely inside per-call paths. An RDG cannot batch or reorder barriers if it cannot see them. The redesign exposes the two shader-side states as first-class commands and leaves every other state implicit. - -### `TransitionState` - -```csharp public enum TransitionState { ShaderResource, UnorderedAccess } -``` -`TransitionState` is shared by `Texture` and `Buffer`. The two states the user actually has to choose between are the same on both: a sampled / read-only resource (`ShaderResource`) versus an unordered read-write resource (`UnorderedAccess`). Backends pick a sensible initial state per resource at creation time, and the first explicit `Transition` call resolves the source state from the resource's tracked field. +// === RenderPass attachments === -### Why `TransitionState` Has Only Two Values - -Every other state is implied by the operation that consumes the resource and is therefore transitioned by the RHI itself, not by the user: - -- **Render-target / depth-stencil** — implied by `BeginRenderPass`. The pass transitions every color attachment to render-target state and every depth attachment to depth-stencil state. `EndRenderPass` does **not** auto-revert; the texture stays in render-target state until the user transitions it back, which is what an RDG wants. -- **Copy source / dest** — implied by `Copy*` and `Resolve*` commands. -- **Present** — implied by `SwapChain.Present`. The swapchain transitions the current backbuffer to the platform's present state as part of `Present(...)`. -- **Vertex / index / constant / indirect buffer** — implied by `SetVertexBuffer`, `SetIndexBuffer`, `PushResourceTable` (CBV slot), and `Draw*Indirect` / `Dispatch*Indirect`. DX12 promotes / decays these automatically; Vulkan derives the access mask at barrier time. - -The user therefore only writes explicit transitions when moving a resource between **shader-side states** (`ShaderResource` ↔ `UnorderedAccess`) or when bringing a resource out of one of the implicit states back to a shader-side state for the next pass. - -### Why There Is No Per-Resource Buffer Enum +public enum LoadAction +{ + Load, + Clear, + DontCare +} -Most backends do not need explicit per-state tracking for buffers: +public enum StoreAction +{ + Store, + DontCare, + Resolve +} -- **DX12** auto-promotes / decays buffer states between `COMMON` and the implicit usage of each operation. -- **Vulkan** has no buffer layout; access is described by `VkAccessFlags2` derived from operation usage at barrier time. -- **Metal** tracks buffer hazards implicitly. +public record struct ColorAttachment +{ + public Texture Texture; -A buffer therefore only needs the same two-state surface a texture exposes — hence the shared `TransitionState`. + public TextureSubresource Subresource; -### `MemoryBarrier()` and Implicit UAV Tracking + public Texture? ResolveTexture; -`MemoryBarrier()` is part of the public API. It expresses "make all preceding writes globally visible before any following read" without naming a specific resource — exactly the semantics of a UAV / global memory barrier across shader stages. + public TextureSubresource ResolveSubresource; -In addition to the explicit call, the `CommandBuffer` keeps a small set of UAV resources written by the current dispatch chain and inserts the appropriate barrier when the next dispatch reads or writes one of those resources. The cost is one hash-set lookup per dispatch; the benefit is that the hazard cannot be forgotten in the easy case. + public LoadAction LoadAction; -`MemoryBarrier()` is the escape hatch for cases the implicit tracker cannot express — e.g. cross-resource hazards, hazards spanning compute → graphics, or RDG-driven scenarios where the layer wants a coarse global flush. + public StoreAction StoreAction; -### Transition Tracking + public Vector4 ClearColor; +} -Each `Texture` and `Buffer` tracks its current `TransitionState`. Tracking lives on the RHI resource so the user only states the **target** state, never the previous one. +public record struct DepthStencilAttachment +{ + public Texture Texture; -- The user calls `cmd.Transition(tex, TransitionState.ShaderResource)`; the RHI reads the source state from the resource's tracked state, emits the backend barrier, and updates tracking. -- An RDG that wants global control can reset tracked state itself before issuing pre-planned barriers. + public TextureSubresource Subresource; -### Public Commands on `CommandBuffer` + public LoadAction DepthLoadAction; -```csharp -public void Transition(Buffer buffer, TransitionState newState); + public StoreAction DepthStoreAction; -public void Transition(Texture texture, TransitionState newState); + public LoadAction StencilLoadAction; -public void MemoryBarrier(); -``` + public StoreAction StencilStoreAction; -Backends implement three primitives: + public float ClearDepth; -```csharp -protected abstract void TransitionCore(Buffer buffer, TransitionState oldState, TransitionState newState); + public byte ClearStencil; +} -protected abstract void TransitionCore(Texture texture, TransitionState oldState, TransitionState newState); +// === Queue submission result === -protected abstract void MemoryBarrierCore(); +public readonly struct CommandSubmission(CommandQueue queue, ulong value) +{ + public void Wait() + { + queue.Wait(value); + } +} ``` -The public `Transition` commands are deliberately scalar and whole-resource. Each backend coalesces consecutive `Transition` calls into one batched barrier op at the next non-transition command, so users get clean call sites without giving up batched submission to the driver. - -There is no public sub-range `Transition(Texture, TextureSubresourceRange, ...)` overload. Sub-range hazard tracking is an RDG concern; backends still expose the necessary internal primitive (`TransitionRangeCore`) for an RDG layer to call directly without touching the public surface. - -### Required States for Operations - -Each operation either *requires* a user-managed state on its resource argument or *implies* the state itself. Implicit states are transitioned by the operation; the user never writes them. - -| Operation | Required state | Source | -|---|---|---| -| `PushResourceTable` SRV resource | `ShaderResource` | user | -| `PushResourceTable` UAV resource | `UnorderedAccess` | user | -| `BeginRenderPass` color attachment | render-target (implicit) | RHI | -| `BeginRenderPass` depth attachment | depth-stencil (implicit) | RHI | -| `CopyBuffer` source / dest | copy source / dest (implicit) | RHI | -| `CopyBufferToTexture` source / dest | copy source / dest (implicit) | RHI | -| `CopyTextureToBuffer` source / dest | copy source / dest (implicit) | RHI | -| `CopyTexture` source / dest | copy source / dest (implicit) | RHI | -| `ResolveTexture` source / dest | copy source / dest (implicit) | RHI | -| `SetVertexBuffer` / `SetIndexBuffer` | vertex / index buffer (implicit) | RHI | -| `PushResourceTable` CBV slot | constant buffer (implicit) | RHI | -| `Draw*Indirect` / `Dispatch*Indirect` argument buffer | indirect argument (implicit) | RHI | -| `Dispatch` reading a UAV that an earlier dispatch wrote | UAV barrier (implicit; user may also call `MemoryBarrier()`) | RHI | -| `SwapChain.Present` backbuffer | present (implicit) | RHI | - -The validation layer enforces correctness on `PushResourceTable`: passing a UAV resource in `ShaderResource` or vice versa is a loud error. - -### Cross-Queue Ownership - -Vulkan requires explicit queue family ownership transfer for resources used on multiple queues with `VK_SHARING_MODE_EXCLUSIVE`. To keep the RHI simple: - -- All resources are created with concurrent / shared semantics (Vulkan: `VK_SHARING_MODE_CONCURRENT` across the three queues; DX12 / Metal need no equivalent). -- Cross-queue synchronization is therefore expressed entirely through `CommandSubmission` waits. The RHI does not require ownership-transfer barriers in user code. +Subresource triplet: -Concurrent sharing has a small driver-side cost on Vulkan but eliminates an entire class of barrier bugs, which matches the framework's "easy to use first" stance. +| Type | Shape | Used by | Backend mapping | +|---|---|---|---| +| `TextureSubresource` | one mip × one layer | RTV / DSV / Resolve | DX12 plane+mip+slice singleton; VK `aspect+mip+layer` singleton; Metal `level+slice` | +| `TextureSubresourceLayers` | one mip × contiguous layer range | Copy / Upload | `VkImageSubresourceLayers`; DX12 issues one `CopyTextureRegion` per layer; Metal `blitEncoder` per layer | +| `TextureSubresourceRange` | contiguous mip range × contiguous layer range | View / Transition | `VkImageSubresourceRange`; DX12 view desc base+count; Metal `MTLTextureView` | ---- +The public surface does **not** carry an aspect field; backends derive aspect (color / depth / stencil) from `Texture.Format`. Cube faces use `ArrayLayer = cubeIndex * 6 + face`, matching VK / DX12 / Metal — no separate face axis. -## Part 4 — Public Resource Surface +## 4. Resource Handles -Three public RHI resource handles. Nothing else. +`Buffer` / `Texture` / `Sampler` no longer implement `IBindableResource` (the interface is removed): ```csharp public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) @@ -442,6 +201,8 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic private BufferDesc desc = desc; public ref readonly BufferDesc Desc => ref desc; + + // Map / Unmap / Upload kept } public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) @@ -470,331 +231,261 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph } ``` -Removed from the public surface: `BufferView`, `BufferViewDesc`, `TextureView`, `TextureViewDesc`, `IBindableResource`, `TextureSlice`, `TextureAspect`, `TextureSubresourceLayers`, `BufferViewType`, `FrameBuffer`, `FrameBufferDesc`, `FrameBufferAttachment`, `RenderPassDesc`, `ClearValue`, `ClearValues`, `ClearFlags`. - -The backend infers texture aspect from `desc.Format` whenever it needs to fill in a `VkImageAspectFlags` / DX12 plane index. The implicit operator from `Texture` to `TextureSubresourceRange` covers the whole resource. - -`TextureType` (1D / 2D / 3D / Cube / CubeArray combined into one enum) stays as it is. Metal merges these the same way (`MTLTextureType` lists `Type1D / Type2D / TypeCube / Type3D / Type1DArray / Type2DArray / TypeCubeArray` in one enum), so the existing shape is canonical, not bespoke. - -### Caching on the Parent - -Each backend resource holds two private dictionaries: - -```csharp -// Conceptual; backend-internal, not exposed. -private Dictionary bufferViewCache; -private Dictionary textureViewCache; -``` - -`T_*` is whatever the backend needs: - -| Backend | Buffer cache value | Texture cache value | -|---|---|---| -| DX12 | `DXDescriptorToken` per CBV / SRV / UAV variant | `DXDescriptorToken` per SRV / UAV; RTV / DSV come from a separate `TextureSubresource`-keyed pool | -| Vulkan | `VkDescriptorBufferInfo` (no GPU object) | `VkImageView` | -| Metal | `nuint GpuAddress` | `MtlTexture` slice via `MakeTextureView` | - -The cache is populated on first bind and reused for every subsequent bind with an equal range. The default range — produced by the implicit operator from `Texture` / `Buffer` — hits the same slot every time, so the typical "bind whole resource" call path materializes one backend object for the lifetime of the resource. The cache is owned by, and disposed with, the parent. - -### Cache Invalidation +**View caches** (per-backend, on each `Texture` / `Buffer`): -Resources are immutable in shape (you cannot re-`BufferDesc` an existing `Buffer`), so the cache never needs partial invalidation. `Resize` on a swapchain backbuffer disposes and recreates the underlying `Texture`; the new texture starts with an empty cache. +- `Dictionary` — SRV / UAV +- `Dictionary` — RTV / DSV +- `Dictionary` — typed buffer view ---- +Lazy-allocated by value-type key, lifetime tied to the parent. The same `(resource, range)` pair across all call sites shares one backend object. -## Part 5 — Resource Table Binding +**State tracking:** every `Texture` / `Buffer` keeps its current `TransitionState` internally; the initial state is set by the backend default. The first explicit `Transition` synthesizes the from→to barrier from that cached value. -### Stage-Tagged Push Instead of Set Index +**Removed from the framework (these all exist today):** `BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `BufferViewType` / `IBindableResource` / `TextureSlice` (incl. `Face`) / `TextureOffset` / `TextureExtent` / `FrameBuffer` / `FrameBufferDesc` / `FrameBufferAttachment` / `ClearValue` / `ClearValues` (static factory) / `ClearFlags` / `GraphicsContext.CreateBufferView` / `CreateTextureView` / `CreateFrameBuffer`. The `Output` type itself stays — it remains `GraphicsPipelineDesc.Output`; only the `FrameBuffer.Output` use site disappears. -Modelling Vulkan's descriptor sets directly via `SetResourceTable(uint set, ResourceTable table)` maps cleanly to Vulkan but forces DX12 (root signature with per-stage visibility) and Metal (per-stage argument tables) into an artificial set-indexed translation layer. It also does not let the user say "this table is for the fragment stage only", which both DX12 and Metal can express natively. +## 5. CommandQueue / CommandBuffer -The design uses a **stage-tagged push** modelled on Metal's `setVertexBuffer:atIndex:` / `setFragmentBuffer:atIndex:` family, generalised across stages via the existing `ShaderStageFlags` enum (`None / Vertex / Pixel / Compute / Amplification / Mesh`, `[Flags]`). The verb is `Push` rather than `Set` to signal that the call is binding-now / push-style, not a long-lived assignment. - -### `ResourceTable.Write` — Typed Overloads +Each `CommandQueue` owns one monotonic completion timeline. Each `Submit` advances that timeline by one and exposes the new value as `CommandSubmission(queue, value)`. Cross-queue dependencies are expressed by feeding upstream `CommandSubmission`s into a downstream `Submit(waits)`. ```csharp -public abstract class ResourceTable +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) { - public void Write(uint binding, BufferRange range); + private readonly Lock @lock = new(); + private readonly Queue available = []; + private readonly Queue execution = []; - public void Write(uint binding, TextureSubresourceRange range); + private ulong nextValue = 1; + private ulong lastSignaledValue; - public void Write(uint binding, Sampler sampler); + public CommandQueueType Type { get; } = type; - public void Write(uint binding, ReadOnlySpan ranges); + public CommandBuffer CommandBuffer() + { + using Lock.Scope _ = @lock.EnterScope(); - public void Write(uint binding, ReadOnlySpan ranges); + CollectCompleted(); - public void Write(uint binding, ReadOnlySpan samplers); -} -``` + CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); -The implicit operators keep simple call sites a single line: + commandBuffer.Begin(); -```csharp -table.Write(0, vertexBuffer); -table.Write(1, albedoTexture); -table.Write(2, sampler); -``` + return commandBuffer; + } -Sub-ranges use a struct literal: + /// Idempotent. Returns immediately when the queue is already drained. + public void WaitForIdle() + { + Wait(lastSignaledValue); + } -```csharp -table.Write(3, new TextureSubresourceRange -{ - BaseMipLevel = 0, - LevelCount = 1, - BaseArrayLayer = 4, - LayerCount = 1 -}); + internal void Wait(ulong value) + { + using Lock.Scope _ = @lock.EnterScope(); -table.Write(4, new BufferRange -{ - Buffer = bigBuffer, - OffsetInBytes = 256, - SizeInBytes = 1024, - StrideInBytes = 32 -}); -``` + if (GetCompletedValue() >= value) + { + CollectCompleted(); -Validation enforces shape compatibility against the resource-layout slot (UAV slot requires `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`, etc.). There is no marker interface like `IBindableResource`. + return; + } -### Backend Mapping for `PushResourceTable` + WaitCore(value); -| Backend | Push semantics | -|---|---| -| DX12 | look up the root parameter whose visibility includes any of `stages`; bind the table's descriptor handle there | -| Vulkan 1.3 | one descriptor set per call, layout pre-baked; for shader-stage-only visibility, use `VK_KHR_push_descriptor` or a stage-disjoint set layout | -| Metal 4 | call the matching per-stage encoder method (`setVertexBuffer` / `setFragmentBuffer` / `setComputeBuffer`) for each bit set in `stages` | + CollectCompleted(); + } ---- + internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) + { + using Lock.Scope _ = @lock.EnterScope(); -## Part 6 — Command Buffer Operations + CollectCompleted(); -All command-buffer operations take canonical types and consistent naming. Signatures align with Vulkan command names and parameter shapes; DX12 / Metal map directly. No `in` parameters; every multi-element parameter is `ReadOnlySpan`; every byte-denominated parameter carries the `*InBytes` suffix. + commandBuffer.End(); -### Buffer Operations + nextValue++; -```csharp -public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); + SubmitCore(commandBuffer, waits, nextValue); -public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); + lastSignaledValue = nextValue; -public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); + execution.Enqueue(new(commandBuffer, nextValue)); -public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, - Buffer destination, ulong destinationOffsetInBytes, - ulong sizeInBytes); + return new(this, nextValue); + } -public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; -``` + private void CollectCompleted() + { + ulong value = GetCompletedValue(); -### Texture Operations + while (execution.Count > 0 && execution.Peek().Value <= value) + { + CommandBuffer commandBuffer = execution.Dequeue().CommandBuffer; -```csharp -public void CopyTexture(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, - Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, - Extent3D extent); + commandBuffer.Reset(); -public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, - Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, - Extent3D extent); + available.Enqueue(commandBuffer); + } + } -public void CopyTextureToBuffer(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, Extent3D extent, - Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); + protected abstract CommandBuffer CreateCommandBuffer(); -public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, - Texture destination, TextureSubresource destinationSubresource); + protected abstract ulong GetCompletedValue(); -public void Upload(Texture destination, TextureSubresourceRange range, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; -``` + protected abstract void WaitCore(ulong value); -`Copy*` and `Upload(Texture, ...)` paths require `range.LevelCount == 1`; the validation layer rejects multi-mip arguments. `ResolveTexture` always operates on a single subresource per side and therefore takes `TextureSubresource` directly. + protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); -### Resource Tables + private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); +} -```csharp -public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandSubmission Submit(params ReadOnlySpan waits) + { + return queue.Submit(this, waits); + } +} ``` -There is no plural `PushResourceTables`; multiple stage targets are handled by setting the appropriate `ShaderStageFlags` bits in one call. +Key points: -### Transitions +- `internal void Wait(ulong)` is the **single** wait entry point: lock → short-circuit on `GetCompletedValue() >= value` → `WaitCore(value)` → `CollectCompleted()`. `CommandSubmission.Wait()` is just a thin forward. +- `WaitForIdle()` is one line: `Wait(lastSignaledValue)`. The first call has `lastSignaledValue == 0`; `GetCompletedValue() >= 0` short-circuits naturally. +- `GetCompletedValue()` is a **method**, not a property: DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue` are all call-shaped on the backends. +- In `Submit`, `nextValue++` runs first, so the same post-incremented value is submitted, signalled, enqueued, and returned. Reads top-to-bottom as "advance → submit → record". +- Command buffer pooling lives in the queue: `CommandBuffer()` pops from `available` (or calls `CreateCommandBuffer()`); `CollectCompleted()` runs at every `Submit` / `Wait` and recycles buffers whose timeline value has retired. -```csharp -public void Transition(Buffer buffer, TransitionState newState); +### Backend Timeline Mapping -public void Transition(Texture texture, TransitionState newState); +| Backend | Timeline object | API | +|---|---|---| +| DX12 | one `ID3D12Fence` per queue | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | +| Vulkan 1.4 | one timeline `VkSemaphore` per queue (core feature) | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | +| Metal 4 | one `MTLSharedEvent` per queue | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | -public void MemoryBarrier(); -``` +The internal binary `VkFence` is retained for swapchain image acquire only. -### Viewports / Scissors +### Interaction with SwapChain Present -```csharp -public void SetViewports(ReadOnlySpan viewports); +Both DX12 and Vulkan implementations execute `Present` on the GraphicsQueue today, and the new design keeps this behavior. `SwapChain.Present(waits)` submits the present from the GraphicsQueue; cross-queue waits are honored on the GraphicsQueue side via "wait other queue's timeline" instructions (see Section 7 mapping table). The SwapChain implementation pulls the GraphicsQueue from `GraphicsContext`; the queue reference is never exposed on `SwapChain`'s public surface. -public void SetScissors(ReadOnlySpan scissors); -``` +To keep barriers simple, all resources are created with concurrent / shared semantics on Vulkan (`VK_SHARING_MODE_CONCURRENT` across the three queues). Cross-queue synchronization is therefore expressed entirely through `CommandSubmission` waits; user code never writes Vulkan ownership-transfer barriers. -`SetViewports` / `SetScissors` are first-class command-buffer ops (not derived from any render-pass desc). Single-viewport / single-scissor calls pass a one-element span via `[stackalloc]` or a collection expression. The plural form matches DX12 `RSSetViewports` / `RSSetScissorRects` and Vulkan `vkCmdSetViewport` / `vkCmdSetScissor` natively; on Metal the backend asserts `viewports.Length == 1` and `scissors.Length == 1`. +## 6. CommandBuffer Operations ```csharp -public record struct Viewport -{ - public float X; - - public float Y; - - public float Width; - - public float Height; - - public float MinDepth; +// === State === - public float MaxDepth; -} - -public record struct Rect -{ - public int X; +public void Transition(Buffer buffer, TransitionState newState); - public int Y; +public void Transition(Texture texture, TransitionState newState); - public uint Width; +public void MemoryBarrier(); - public uint Height; -} -``` +// === Inline RenderPass === ---- +public void BeginRenderPass(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment); -## Part 7 — Inline RenderPass (replaces `FrameBuffer`) +public void EndRenderPass(); -### Why `FrameBuffer` Is Removed +// === Viewports / Scissors === -`FrameBuffer` predates dynamic rendering. It forces: +public void SetViewports(ReadOnlySpan viewports); -- A long-lived object whose only role is bundling attachment views. -- A separate `SwapChainFrameBuffer` per backend. -- An `Output` description duplicated on `FrameBuffer`, `SwapChain`, and pipelines. +public void SetScissors(ReadOnlySpan scissors); -All three target APIs natively accept inline render-pass descriptors. Removing `FrameBuffer` aligns the RHI with that model and makes attachments first-class participants of the resource transition system. +// === Pipeline / Resource binding === -### Attachment Descriptions +public void SetPipeline(Pipeline pipeline); -```csharp -public enum LoadAction -{ - Load, - Clear, - DontCare -} +public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); -public enum StoreAction -{ - Store, - DontCare, - Resolve -} +// === Vertex / Index === -public record struct ColorAttachmentDesc -{ - public Texture Texture; +public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); - public TextureSubresource Subresource; +public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); - public Texture? ResolveTexture; +public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); - public TextureSubresource ResolveSubresource; +// === Draws / Dispatches === - public LoadAction LoadAction; +public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); - public StoreAction StoreAction; +public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance); - public Vector4 ClearColor; -} +public void DrawIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); -public record struct DepthStencilAttachmentDesc -{ - public Texture Texture; +public void DrawIndexedIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); - public TextureSubresource Subresource; +public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ); - public LoadAction DepthLoadAction; +public void DispatchIndirect(Buffer argsBuffer, ulong offsetInBytes); - public StoreAction DepthStoreAction; +// === Buffer copy / upload === - public LoadAction StencilLoadAction; +public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, + Buffer destination, ulong destinationOffsetInBytes, + ulong sizeInBytes); - public StoreAction StencilStoreAction; +public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; - public float ClearDepth; +// === Texture copy / upload / resolve === - public byte ClearStencil; -} -``` +public void CopyTexture(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, + Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, + Extent3D extent); -Attachment descs reference `Texture` + `TextureSubresource` (a single mip × single layer — what an RTV / DSV always is). The backend resolves the RTV / DSV through the parent resource's `TextureSubresource`-keyed pool, so identical attachments across frames share one descriptor. +public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, + Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, + Extent3D extent); -### `BeginRenderPass` +public void CopyTextureToBuffer(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, Extent3D extent, + Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); -```csharp -public void BeginRenderPass(ReadOnlySpan colorAttachments, - DepthStencilAttachmentDesc? depthStencilAttachment); +public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, + Texture destination, TextureSubresource destinationSubresource); -public void EndRenderPass(); +public void Upload(Texture destination, TextureSubresourceLayers layers, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; ``` -The `depthStencilAttachment` parameter has **no default value**. Callers without a depth-stencil target must pass `null` explicitly. This forces the user to acknowledge the choice at the call site instead of silently dropping the depth attachment because of a missing argument. - -The implementation: - -1. Transitions every color attachment to render-target state and the depth attachment (if any) to depth-stencil state, using the backend's tracked-state machinery from Part 3. The user does **not** transition attachments before the pass. -2. Calls the backend `BeginRenderPassCore(...)`. - -`EndRenderPass()` does **not** auto-revert attachments to a previous state; the texture remains in render-target state until the next user-issued transition (typically to `ShaderResource` for the next pass, or implicitly to present state via `SwapChain.Present`). - -`SetViewports` / `SetScissors` are issued separately by the user inside the pass. - -### Pipeline `Output` - -`GraphicsPipelineDesc.Output` (color formats + depth format + sample count) is unchanged. A pipeline is compatible with any `BeginRenderPass` call whose attachment formats and sample count match its `Output`. This stays the user's responsibility, exactly as today; only the per-frame bundling changes. - -### Layered / Multiview +Notes: -Layered rendering is detected per-attachment from `Texture.Desc.ArrayLayers > 1` combined with the absence of an explicit single-layer `TextureSubresource`. A future extension can add an explicit `LayerCount` argument to `BeginRenderPass` if needed; the current proposal omits it to keep the entry point minimal. +- All byte-denominated parameters carry the `*InBytes` suffix; multi-element parameters are `ReadOnlySpan`; no `in` parameters. +- Copy / upload uses `TextureSubresourceLayers` (one mip + contiguous layer range). The type itself excludes multi-mip, so no runtime validation is needed. +- `ResolveTexture` is single-point → single-point and uses `TextureSubresource`. +- `BeginRenderPass` accepts an attachment span; pass `null` explicitly for no depth. The implementation auto-`Transition`s the attachments and auto-fills `SetViewports` / `SetScissors` from attachment dimensions; the caller may issue `SetViewports` / `SetScissors` afterward to override. +- `PushResourceTable` takes a `ShaderStageFlags stages` argument: DX12 root-parameter visibility and Metal argument tables are stage-scoped. On Vulkan the descriptor set's stage mask is fixed at layout creation time, so `stages` is used to validate the call against the layout. +- `Transition` is only called explicitly between `ShaderResource` and `UnorderedAccess`. Render-target / depth-stencil / copy / vertex / index / CBV / indirect / present states are transitioned implicitly by the corresponding operation. +- `MemoryBarrier()` is a global cross-resource / cross-stage memory barrier. The command buffer also tracks the set of UAVs written by the previous dispatch and inserts a barrier automatically as a fallback when the next dispatch reads the same resource. -### Backend Mapping +### ResourceTable -| Backend | Begin | End | -|---|---|---| -| DX12 | `OMSetRenderTargets` (or `BeginRenderPass` with `RENDER_PASS_RENDER_TARGET_DESC` for tile resources) | `EndRenderPass` if used, otherwise nothing | -| Vulkan 1.3 | `vkCmdBeginRendering` with `VkRenderingInfo` built from the attachment span (dynamic rendering is core in 1.3) | `vkCmdEndRendering` | -| Metal 4 | `MTL4CommandBuffer.RenderCommandEncoder(MTLRenderPassDescriptor)` built from the attachment span | encoder `EndEncoding` | - ---- +`Write` becomes strongly typed, replacing the current `Write(uint, params IBindableResource[])` + runtime type-switch: -## Part 8 — `SwapChain` Without `FrameBuffer` - -### Interaction With Queue Synchronization +```csharp +public abstract class ResourceTable +{ + public void Write(uint binding, BufferRange range); -Every coupling point between swapchain and queues is expressed as a `CommandSubmission`, so no new primitive leaks out. The API surface has no `AcquireNextImage()` and no first-frame helper field. `Present(...)` returns a `CommandSubmission` representing the next backbuffer's readiness, and the very first frame's image-ready submission is the **caller's** responsibility — typically `default(CommandSubmission)`, which `CommandSubmission.Wait()` treats as "no wait needed". + public void Write(uint binding, TextureSubresourceRange range); -Two edges exist in a typical frame: + public void Write(uint binding, Sampler sampler); -1. **Submit → Present**: the present engine must not read the backbuffer until the GPU finishes drawing. `SwapChain.Present(params ReadOnlySpan waits)` accepts the submissions that produced the backbuffer contents and turns them into the appropriate GPU-side wait before scheduling present. -2. **Present → next Submit**: a backbuffer is only safe to write after the present engine releases it. `Present(...)` returns a `CommandSubmission` describing that release point; the next frame's `cmd.Submit(...)` waits on it. The very first frame uses whatever the caller decides — `default(CommandSubmission)` for the simple case, an explicit hand-off submission for advanced flight-frame strategies. + public void Write(uint binding, ReadOnlySpan ranges); -The public API does **not** add a `cmd.Submit(SwapChain)` overload or a `SwapChain.SubmitAndPresent(...)` shortcut. Coupling submit to the swapchain on the API surface would: + public void Write(uint binding, ReadOnlySpan ranges); -- Force every multi-pass frame (e.g. shadow + main + post) to either wrap the whole frame in one submit or pull the swapchain through every layer. -- Conflict with cross-queue scheduling, where the final write to the backbuffer may come from compute, not graphics. -- Be redundant: the existing `CommandSubmission` value already carries the necessary edge through `Present(waits)`. + public void Write(uint binding, ReadOnlySpan samplers); +} +``` -Keeping submit and present orthogonal — and bridging them through `CommandSubmission` — is what makes this RHI directly reusable by an RDG layer that schedules many passes per frame. +- `Buffer` / `Texture` flow into the call through their implicit operators, keeping single-resource binds a single line. +- The interpretation of a buffer (CBV / structured SRV / byte-address SRV / UAV / typed) is fixed by the slot's `ResourceLayout`; there is no public `BufferViewType`. +- Validation: a UAV slot requires `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`; shape / layout mismatches are reported at `Write` time. -### Public Surface +## 7. SwapChain ```csharp public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) @@ -807,51 +498,63 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract uint Height { get; } - /// Current backbuffer index. Internal — backend code only; not part of the public surface. - internal abstract uint CurrentImageIndex { get; } - public abstract Texture CurrentColorTarget { get; } public abstract Texture? CurrentDepthStencilTarget { get; } /// - /// Submits a present operation that waits on on the GPU. - /// Returns a CommandSubmission that signals when the next frame's backbuffer is safe to write. - /// The first frame's image-ready value must be supplied by the caller (typically default(CommandSubmission)). + /// Submits a present operation on the GraphicsQueue, waiting on . + /// Returns a CommandSubmission that signals when the next backbuffer is safe to write. + /// First-frame seed is the caller's responsibility — pass an empty span, or seed an `imageReady` + /// local with `default(CommandSubmission)` and let the backend filter it out of the next `Submit`'s waits. + /// Note: itself does not defend against `default`; only call it + /// on submissions returned by an actual `Submit` / `Present`. /// public abstract CommandSubmission Present(params ReadOnlySpan waits); - public void Resize(uint width, uint height) { /* unchanged */ } + public void Resize(uint width, uint height); - public void Refresh(Surface surface) { /* unchanged */ } + public void Refresh(Surface surface); } ``` -The backbuffer is exposed as a plain `Texture` — no view object. Users build attachment descs from `(swapChain.CurrentColorTarget, new TextureSubresource())`, which the backend resolves through the texture's `TextureSubresource`-keyed RTV pool. `CurrentImageIndex` is `internal` because no public API needs it; backend code that does need it (e.g. building per-frame command structures) reaches it through the same-assembly access. +Design points: + +- Backbuffers are exposed as plain `Texture`s (color + optional depth-stencil). No `FrameBuffer`, no image index. +- `Present(...)` returns a `CommandSubmission` describing when the next backbuffer is writable; it accepts waits from any queue. +- The current backbuffer index lives **only** inside the backend (today: `VKSwapChain.ImageIndex`, `DXSwapChain.BufferIndex`). Public callers never need it. +- The first-frame image-ready submission is supplied by the caller (frames-in-flight strategy is the caller's choice). + +Backend mapping: + +| Backend | `Present(waits)` implementation | "Next-frame ready" signal | +|---|---|---| +| DX12 | for each `wait`, call `graphicsQueue.Wait(otherFence, otherValue)`, then `IDXGISwapChain3::Present` | frame-latency event wrapped as a `CommandSubmission` whose value is taken from the graphics queue timeline | +| Vulkan 1.4 | one bridging `vkQueueSubmit2(graphicsQueue, waits=timeline values, signal=renderFinished[N])` → `vkQueuePresentKHR(graphicsQueue, wait=renderFinished[N])` | the next frame's `vkAcquireNextImageKHR` returns binary `imageAvailable[N]`; another graphics-queue "wait binary, signal timeline" bridging submit wraps it as a `CommandSubmission` | +| Metal 4 | translate each wait into `commandBuffer.EncodeWaitForEvent(...)` → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()` | the next drawable's availability event wrapped as a `CommandSubmission` | -### Frame Loop +## 8. Frame Loop ```csharp -// First-frame seed: caller decides. default(CommandSubmission) is the simple case. CommandSubmission imageReady = default; while (running) { CommandBuffer cmd = ctx.Graphics.CommandBuffer(); - ReadOnlySpan colors = + ReadOnlySpan colors = [ new() { Texture = swapChain.CurrentColorTarget, - Subresource = new(), + Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, LoadAction = LoadAction.Clear, StoreAction = StoreAction.Store, ClearColor = new(0, 0, 0, 1) } ]; - DepthStencilAttachmentDesc? depth = swapChain.CurrentDepthStencilTarget is { } ds + DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds ? new() { Texture = ds, @@ -862,13 +565,8 @@ while (running) } : null; + // Viewports / scissors are auto-filled from attachment dimensions; override afterward if needed. cmd.BeginRenderPass(colors, depth); - - Span viewports = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 }]; - Span scissors = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height }]; - - cmd.SetViewports(viewports); - cmd.SetScissors(scissors); // draws cmd.EndRenderPass(); @@ -878,62 +576,46 @@ while (running) } ``` -Every step in the loop has the same shape: takes a set of `CommandSubmission` waits, returns a `CommandSubmission` for the next consumer. `Submit` and `Present` are symmetric on the API surface. +Every step has the same shape: take a set of `CommandSubmission` waits, return a `CommandSubmission`. `Submit` and `Present` are symmetric on the API surface. + +## 9. Three-Backend RenderPass / Barrier Mapping + +| Concept | DX12 | Vulkan 1.4 | Metal 4 | +|---|---|---|---| +| Begin / End RenderPass | `OMSetRenderTargets`, or `BeginRenderPass(RENDER_PASS_RENDER_TARGET_DESC)` / `EndRenderPass` | `vkCmdBeginRendering(VkRenderingInfo)` / `vkCmdEndRendering` | `commandBuffer.RenderCommandEncoder(MTLRenderPassDescriptor)` / `endEncoding` | +| LoadAction | `BeginningAccessType` (Discard / Preserve / Clear) | `loadOp` (DONT_CARE / LOAD / CLEAR) | `MTLLoadAction` | +| StoreAction | `EndingAccessType` (Discard / Preserve / Resolve) | `storeOp` (DONT_CARE / STORE / NONE) + `resolveMode` | `MTLStoreAction` | +| Resolve | `EndingAccessResolveSubresourceParameters` | `pResolveAttachments` + `resolveMode` | `MTLRenderPassDescriptor.resolveTexture` | +| Explicit Transition | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2(VkImageMemoryBarrier2 / VkBufferMemoryBarrier2)` | `commandEncoder.MemoryBarrier(scope, after, before)` | +| Aspect inference source | format → plane index | `Texture.Format` → `VkImageAspectFlags` | format → automatic | + +## 10. RDG Interface Self-Check -### Backend Mapping +The RDG is built on top of this RHI by the consumer, not by us. This section only answers: "Does the RHI expose every primitive an RDG needs?" -| Backend | `Present` | Returned `CommandSubmission` | +| RDG requirement | Provided by RHI | Entry point | |---|---|---| -| DX12 | `IDXGISwapChain::Present`; `waits` are translated into queue `Wait` calls before present | a value derived from the frame-latency waitable object signalling when the next backbuffer is releasable | -| Vulkan 1.3 | `vkQueuePresentKHR` waits on a per-present binary semaphore that the last `CommandSubmission`'s queue signals as part of its submit | adapter that wraps the next-frame `vkAcquireNextImageKHR` binary semaphore so it appears as a wait edge during the next `Submit` | -| Metal 4 | `drawable.Present()` after waiting on the `waits` timeline values | the next drawable's availability event | - -Vulkan is the only backend where binary semaphores must coexist with timelines. Internally the Vulkan swapchain holds: - -- One binary `VkSemaphore` per in-flight frame (`imageAvailable[N]`) — signaled by `vkAcquireNextImageKHR`, exposed through the value returned by `Present`. -- One binary `VkSemaphore` per in-flight frame (`renderFinished[N]`) — waited by `vkQueuePresentKHR`. -- These binaries are wrapped in a private `CommandSubmission`-compatible adapter so the public API stays uniform across backends. - -This isolates the binary-vs-timeline asymmetry inside the Vulkan swapchain, where it belongs. - ---- - -## Migration Notes - -The redesign is breaking. Affected APIs: - -- `CommandQueue.WaitIdle()` → `CommandQueue.WaitForIdle()`. -- All `*Impl` overrides project-wide → `*Core` (`SubmitImpl→SubmitCore`, `WaitIdleImpl→WaitCore`, `SetImpl→SetCore`, `PreprocessImpl→PreprocessCore`, `GetResultsImpl→GetResultsCore`, `ResizeImpl→ResizeCore`, `RefreshImpl→RefreshCore`, `CopyBufferImpl→CopyBufferCore`, `BeginRenderPassImpl→BeginRenderPassCore`, ...). -- `CommandQueue` exposes no public per-value `Wait(ulong)`. Use `submission.Wait()`. The hooks `CompletedValueCore` (now a property) and `WaitCore(ulong)` are `protected internal` so `CommandSubmission` can call them directly. -- All expression-bodied `=>` members on RHI types are rewritten with `{ ... }` bodies, **except** `ref` / `ref readonly` returning properties (`public ref readonly TDesc Desc => ref desc;`), which keep the `=>` form. -- `CommandBuffer.Submit(bool waitForCompletion)` is removed. Use `Submit()` / `Submit().Wait()`. -- `CommandBuffer.BeginRenderPass(FrameBuffer, ClearValue, params IEnumerable)` is removed. Use `BeginRenderPass(ReadOnlySpan, DepthStencilAttachmentDesc?)`; the depth-stencil parameter has no default value (pass `null` explicitly). -- `FrameBuffer`, `FrameBufferDesc`, `FrameBufferAttachment`, `RenderPassDesc`, `ClearValue`, `ClearValues`, `ClearFlags` are removed. -- `BufferView`, `BufferViewDesc`, `TextureView`, `TextureViewDesc`, `IBindableResource` are removed. -- `TextureSlice`, `TextureAspect`, `TextureSubresourceLayers`, `BufferViewType` are removed. -- `CommandBuffer.Transition(Texture, TextureSubresourceRange, TransitionState)` is **not** part of the public API; the corresponding backend primitive (`TransitionRangeCore`) remains internal for an RDG layer. -- `GraphicsContext.CreateBufferView(...)` / `CreateTextureView(...)` / `CreateFrameBuffer(...)` are removed. -- `ResourceTable.Write(uint, IBindableResource)` and its array / params variants are removed. Use the typed overloads in Part 5. -- `ResourceTable.Preprocess(CommandBuffer)` and the `preprocessResourceTables` parameter on `BeginRenderPass` are removed. -- `CommandBuffer.SetResourceTable(uint set, ResourceTable)` and `SetResourceTables(uint firstSet, ReadOnlySpan)` are removed. Use `PushResourceTable(ShaderStageFlags stages, ResourceTable table)` with the existing `ShaderStageFlags` enum. -- `Copy*` and `Upload(Texture, ...)` signatures change to take `TextureSubresourceRange` + `Offset3D` + `Extent3D`. Copy / upload paths require `LevelCount == 1`. -- All byte-denominated parameters now carry the `*InBytes` suffix (`offsetInBytes`, `sizeInBytes`, `sourceOffsetInBytes`, `destinationOffsetInBytes`, `sourceRowPitchInBytes`, `destinationRowPitchInBytes`). `BufferRange` fields rename to `OffsetInBytes`, `SizeInBytes`, `StrideInBytes`. -- `CommandBuffer.SetViewport(Viewport)` and `SetScissor(Rect)` → `SetViewports(ReadOnlySpan)` and `SetScissors(ReadOnlySpan)`. -- Render-pass attachment descs replace any `TextureView View` field with `Texture Texture` + `TextureSubresource Subresource`. -- The DX12 `DXBuffer.View` / `DXTexture.View` fields are removed; descriptor allocation moves into the per-range cache on the parent resource. -- The Vulkan auto-created `VKTexture.View` is removed; the cache lazily creates one `VkImageView` per unique `TextureSubresourceRange`. -- `SwapChain.FrameBuffer`, `SwapChain.AcquireNextImage()`, `SwapChain.FirstFrameImageReady`, and the public `SwapChain.CurrentImageIndex` are removed; replaced by `CurrentColorTarget` / `CurrentDepthStencilTarget` / `CommandSubmission Present(...)`. `CurrentImageIndex` is now `internal`. -- Cube faces are no longer addressed by a separate `Face` field. Replace `new TextureSlice { ArrayLayer = i, Face = f }` with `ArrayLayer = i * 6 + f`. -- All `in` parameters on the public RHI are removed. -- Every public method that took a managed array (`T[]`, `params T[]`, `IEnumerable`) now takes `ReadOnlySpan` (or `params ReadOnlySpan` for trailing parameters). - -The internal Vulkan `VKFence` (binary fence) is retained for swapchain use only. - ---- - -## RDG and Third-Party Compatibility - -This RHI is sufficient for a future render dependency graph to layer on top, and friendly to third-party adapters: - -- **RDG.** Each pass is a function that records a section of a `CommandBuffer`. Resource access is declared at pass build time as `Buffer` / `Texture` plus `BufferRange` / `TextureSubresourceRange`. The RDG can compute exact transitions and emit `Transition(...)` / `MemoryBarrier()` calls between passes; the backend coalesces them. For sub-range or per-aspect transitions the RDG calls into the backend's internal primitive (`TransitionRangeCore`) directly; the public surface stays minimal. All range types are `record struct` and therefore equatable, so the RDG can hash them directly when planning aliases. Cross-queue scheduling reuses `CommandSubmission` waits; no new RHI primitive is needed. Identical bindings across passes share one cached backend object on the parent for free. Inline render passes mean the RDG can build attachment arrays per pass without allocating long-lived `FrameBuffer` objects. Aliased / transient resources can be added later by introducing a transient memory allocator beneath `Texture` / `Buffer` without changing the public RHI. -- **Third-party adapters.** Libraries that already speak Vulkan / DX12 conventions (ImGui backends, glTF loaders, profilers, render-graph layers) can adopt the RHI without translation: the subresource shapes have the same names and the same axes as `VkImageSubresource*`. There is no bespoke `Face` axis or default-view object to special-case. +| Explicit resource state transitions | ✅ | `CommandBuffer.Transition(Buffer/Texture, TransitionState)` | +| Global memory barrier | ✅ | `CommandBuffer.MemoryBarrier()` | +| Sub-range addressing (alias / hazard keys) | ✅ | `BufferRange` / `TextureSubresourceRange` (`record struct`, hashable) | +| Cross-queue dependencies | ✅ | `CommandSubmission` waits (symmetric on `Submit` / `Present`) | +| Queue completion query / wait | ✅ | `CommandQueue.WaitForIdle()` / `CommandSubmission.Wait()` | +| Short-lived RenderPass | ✅ | Inline `BeginRenderPass(colorAttachments, depth)` | +| Cross-pass binding sharing | ✅ | `ResourceTable` cache key = (range, layout) | + +Conclusion: an RDG implementation can be built without any further changes to the RHI public surface. + +## 11. ZenithHelper Members Affected + +The following functions in [ZenithHelper](sources/Zenith.NET/ZenithHelper.cs) depend on the now-removed `TextureSlice` (with `Face`) / `TextureViewDesc`, or on the "cube faces multiply implicitly into subresource counts" model. Under the new design they no longer apply and must be removed or rewritten against the new types: + +- `FaceCount(TextureDesc)` — cubes / cube arrays now express their faces directly as 6-layer arrays. `ArrayLayers` already counts faces; there is no separate face axis. +- `FaceIndex(TextureDesc, TextureSlice)` — depends on `TextureSlice.Face`. +- `FlattenArrayLayerCount(TextureDesc)` — the "fold face into array layer" step is no longer needed; callers use `desc.ArrayLayers` directly. +- `FlattenArrayLayerIndex(TextureDesc, TextureSlice)` — same. +- `FlattenArrayLayerRange(TextureViewDesc)` — `TextureViewDesc` is removed. +- `SubresourceCount(TextureDesc)` — new model = `MipLevels * ArrayLayers` (DX12 plane is handled implicitly by format). Inline at the call site. +- `SubresourceIndex(TextureDesc, TextureSlice)` — replace with inline `MipLevel + ArrayLayer * MipLevels` over `TextureSubresource`. +- `SubresourceSizeInBytes(TextureDesc, TextureSlice)` — rewrite to take a `TextureSubresource` (only `MipLevel` is used; the rest reuses the existing `MipDimensions` + `SizeInBytes` helpers). + +Unaffected: pure format / geometry helpers (`MipDimensions`, `SizeInBytes(PixelFormat, ...)`, `ElementFormat` byte-size table, etc.) keep working as-is. diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index d188330d..6e9c279a 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -2,28 +2,44 @@ > 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 -## 设计目标 +## 1. 目标与非目标 -- 公共 API 表面尽量小、调用形态统一(结构体一律 `record struct`,参数无 `in`) -- 帧循环代码短、零托管分配 -- 与 DirectX 12 / Vulkan 1.3 / Metal 4 的真实命令模型 1:1 对应 -- 后续 RDG 层直接复用,无需破坏性变更 -- 第三方适配(ImGui / glTF / 性能分析器)无需翻译层 +**目标** -## 命名与代码风格约定 +- 公共 API 表面小、调用形态统一,与 DirectX 12 / Vulkan 1.4 / Metal 4 命令模型 1:1 对应 +- 帧循环代码短、热路径零托管分配 +- 跨队列同步(Graphics / Compute / Copy)一等公民 +- 显式资源状态转换,作为未来 RDG 的稳定基座 +- 内联 RenderPass,去掉长生命周期 `FrameBuffer` +- 子范围 / 子资源信息在**调用现场**以值类型表达,不再有公共 `View` 对象 -- 所有公共值类型为 `record struct`,公共字段可写;不使用 `ref struct`,不使用 `in` 参数 +**非目标** + +- RHI 内不做完整 hazard tracking(仅维护"当前状态"用于计算 from→to) +- 一次 submit 一个同步对象 +- 把 `WaitForIdle` 当作主同步手段 +- 本轮不实现 RDG、bindless / descriptor buffer +- 不重构 `ResourceTable` / `ResourceLayout` / `Sampler` / `Pipeline` / shader IO 之外的部分 +- 不在公共面暴露 per-aspect (color/depth/stencil) 寻址、子范围 transition、首帧 image-ready 等待 + +## 2. 命名与代码风格约定 + +- 所有公共值类型:`record struct`,公共字段可写;不使用 `ref struct`、不使用 `in` 参数 - 多元素入参一律 `ReadOnlySpan` / `params ReadOnlySpan`,不使用 `T[]` / `IEnumerable` -- 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性(如 `public ref readonly TDesc Desc => ref desc;`)保留 `=>` 形式 -- 所有以字节为单位的字段与参数加 `*InBytes` 后缀 -- `slot` / `firstSlot` / `binding` 等图形领域标准术语保留无后缀 -- 后端覆写钩子统一以 `*Core` 结尾(项目内现有 `*Impl` 全部改名为 `*Core`,`SetImpl` / `PreprocessImpl` / `GetResultsImpl` / `ResizeImpl` / `RefreshImpl` / `WaitIdleImpl` / `SubmitImpl` / `CopyBufferImpl` 等等都同步更名) +- 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性保留 `=> ref _field;` +- 字节单位字段与参数加 `*InBytes` 后缀 +- 抽象后端钩子的命名规则:**只有**当存在**同名**的非 `Core` 包装时才用 `*Core` 后缀;否则用自然名 + - `Wait(ulong)` 包装 → `WaitCore(ulong)` + - `Submit(...)` 包装 → `SubmitCore(...)` + - `CreateCommandBuffer()` 无同名包装(公共工厂叫 `CommandBuffer()`)→ 不加 `Core` + - `GetCompletedValue()` 无包装 → 不加 `Core` +- 抽象成员一律 `protected abstract`,同程序集内部调用走 `internal` 包装 - 公共同步结果只有一种:`CommandSubmission` -## 公共类型一览 +## 3. 公共类型一览 ```csharp -// === 子资源 === +// === 子资源(设计原则:view 用 range,copy/upload 用 layers,单点用 single) === public record struct TextureSubresource { @@ -32,6 +48,15 @@ public record struct TextureSubresource public uint ArrayLayer; } +public record struct TextureSubresourceLayers +{ + public uint MipLevel; + + public uint BaseArrayLayer; + + public uint LayerCount; +} + public record struct TextureSubresourceRange { public uint BaseMipLevel; @@ -45,11 +70,11 @@ public record struct TextureSubresourceRange public record struct Offset3D { - public int X; + public uint X; - public int Y; + public uint Y; - public int Z; + public uint Z; } public record struct Extent3D @@ -85,7 +110,7 @@ public record struct BufferRange } } -// === 状态转换 === +// === 资源状态(公共面) === public enum TransitionState { @@ -93,7 +118,7 @@ public enum TransitionState UnorderedAccess } -// === 渲染附件 === +// === RenderPass 附件 === public enum LoadAction { @@ -109,7 +134,7 @@ public enum StoreAction Resolve } -public record struct ColorAttachmentDesc +public record struct ColorAttachment { public Texture Texture; @@ -126,7 +151,7 @@ public record struct ColorAttachmentDesc public Vector4 ClearColor; } -public record struct DepthStencilAttachmentDesc +public record struct DepthStencilAttachment { public Texture Texture; @@ -145,57 +170,41 @@ public record struct DepthStencilAttachmentDesc public byte ClearStencil; } -// === 视口 / 裁剪 === - -public record struct Viewport -{ - public float X; - - public float Y; - - public float Width; - - public float Height; - - public float MinDepth; - - public float MaxDepth; -} - -public record struct Rect -{ - public int X; - - public int Y; - - public uint Width; - - public uint Height; -} - // === 队列同步结果 === public readonly struct CommandSubmission(CommandQueue queue, ulong value) { public void Wait() { - if (queue is null || queue.CompletedValueCore >= value) - { - return; - } - - queue.WaitCore(value); + queue.Wait(value); } } ``` -注:`ShaderStageFlags` 沿用项目现有定义(`None / Vertex / Pixel / Compute / Amplification / Mesh`),本设计不重新声明。 +子资源三件套对照: + +| 类型 | 表达 | 用途 | 后端对照 | +|---|---|---|---| +| `TextureSubresource` | 单 mip × 单 layer | RTV / DSV / Resolve | DX12 plane+mip+slice 单点;VK `aspect+mip+layer` 单点;Metal `level+slice` | +| `TextureSubresourceLayers` | 单 mip × 连续 layer 段 | Copy / Upload | `VkImageSubresourceLayers`;DX12 按 layer 循环 `CopyTextureRegion`;Metal `blitEncoder` 逐层 | +| `TextureSubresourceRange` | 连续 mip 段 × 连续 layer 段 | View / Transition | `VkImageSubresourceRange`;DX12 view desc 的 base+count;Metal `MTLTextureView` | -## 公共资源句柄 +公共面**不**暴露 aspect 字段;后端从 `Texture.Format` 推断 `VkImageAspectFlags` / DX12 plane index。Cube 面以 `ArrayLayer = cubeIndex * 6 + face` 表达,与 VK / DX12 / Metal 一致,无独立 face 轴。 -只有三个:`Buffer`、`Texture`、`Sampler`。 +## 4. 资源句柄 + +`Buffer` / `Texture` / `Sampler` 不再实现 `IBindableResource`(接口删除): ```csharp +public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) +{ + private BufferDesc desc = desc; + + public ref readonly BufferDesc Desc => ref desc; + + // Map / Unmap / Upload 保留 +} + public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) { private TextureDesc desc = desc; @@ -213,15 +222,30 @@ public abstract class Texture(GraphicsContext context, TextureDesc desc) : Graph }; } } + +public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) +{ + private SamplerDesc desc = desc; + + public ref readonly SamplerDesc Desc => ref desc; +} ``` -`Buffer` / `Sampler` 同样以 `public ref readonly XxxDesc Desc => ref desc;` 暴露描述。 +**View 缓存**(每个后端 `Texture` / `Buffer` 内部): + +- `Dictionary` —— SRV / UAV +- `Dictionary` —— RTV / DSV +- `Dictionary` —— typed buffer view -**移除**:`BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `IBindableResource` / `TextureSlice` / `TextureAspect` / `TextureSubresourceLayers` / `BufferViewType` / `FrameBuffer` 系列 / `RenderPassDesc` / `ClearValue` 系列。 +按值类型 key 懒加载,与父资源同生命周期;同一 (resource, range) 组合在所有调用方共享一个后端对象。 -**view 缓存**:每个后端 `Texture` / `Buffer` 内部维护 `Dictionary` / `Dictionary`,按 range 值类型为键,懒加载,与父资源同生命周期。同范围多次绑定共用一个后端对象。 +**资源状态跟踪**:每个 `Texture` / `Buffer` 内部跟踪当前 `TransitionState`,初值由后端默认写入;首次显式 `Transition` 时根据当前值生成 from→to barrier。 -## CommandQueue / CommandBuffer +**确认从框架删除(这些当前真实存在)**:`BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `BufferViewType` / `IBindableResource` / `TextureSlice`(含 `Face`)/ `TextureOffset` / `TextureExtent` / `FrameBuffer` / `FrameBufferDesc` / `FrameBufferAttachment` / `ClearValue` / `ClearValues` 静态类 / `ClearFlags` 枚举 / `GraphicsContext.CreateBufferView` / `CreateTextureView` / `CreateFrameBuffer`。`Output` 类型保留并继续作为 `GraphicsPipelineDesc.Output`,仅 `FrameBuffer.Output` 一处使用消失。 + +## 5. CommandQueue / CommandBuffer + +每个 `CommandQueue` 持有一条单调递增的完成时间线;一次 `Submit` 在时间线上推进一个值,并以 `CommandSubmission(queue, value)` 公开该点。跨队列依赖通过把上游 `CommandSubmission` 传入下游 `Submit(waits)` 表达。 ```csharp public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) @@ -235,30 +259,38 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ public CommandQueueType Type { get; } = type; - /// 从池中借出一个已 Begin 的命令缓冲。 public CommandBuffer CommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); CollectCompleted(); - CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBufferCore() : available.Dequeue(); + CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); commandBuffer.Begin(); return commandBuffer; } - /// 等待此 queue 上所有已提交的命令完成。 + /// 等待此 queue 上所有已提交的命令完成。幂等。 public void WaitForIdle() + { + Wait(lastSignaledValue); + } + + internal void Wait(ulong value) { using Lock.Scope _ = @lock.EnterScope(); - if (lastSignaledValue is not 0) + if (GetCompletedValue() >= value) { - WaitCore(lastSignaledValue); + CollectCompleted(); + + return; } + WaitCore(value); + CollectCompleted(); } @@ -270,40 +302,38 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ commandBuffer.End(); - ulong value = nextValue++; + nextValue++; - SubmitCore(commandBuffer, waits, value); + SubmitCore(commandBuffer, waits, nextValue); - lastSignaledValue = value; + lastSignaledValue = nextValue; - execution.Enqueue(new(commandBuffer, value)); + execution.Enqueue(new(commandBuffer, nextValue)); - return new(this, value); + return new(this, nextValue); } private void CollectCompleted() { - ulong completed = CompletedValueCore; + ulong value = GetCompletedValue(); - while (execution.Count > 0 && execution.Peek().Value <= completed) + while (execution.Count > 0 && execution.Peek().Value <= value) { - CommandBuffer cmd = execution.Dequeue().CommandBuffer; + CommandBuffer commandBuffer = execution.Dequeue().CommandBuffer; - cmd.Reset(); + commandBuffer.Reset(); - available.Enqueue(cmd); + available.Enqueue(commandBuffer); } } - /// 当前已完成的时间线值。同程序集内(如 )可直接访问,子类必须实现。 - protected internal abstract ulong CompletedValueCore { get; } + protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract CommandBuffer CreateCommandBufferCore(); + protected abstract ulong GetCompletedValue(); - protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + protected abstract void WaitCore(ulong value); - /// 等待此 queue 时间线达到给定值。同程序集内(如 )可直接访问,子类必须实现。 - protected internal abstract void WaitCore(ulong value); + protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); } @@ -318,130 +348,142 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) ``` 要点: -- `CommandQueue` 不再公开 `Wait(ulong)`。`CommandSubmission.Wait()` 通过 `protected internal` 直接调用 `CompletedValueCore` 与 `WaitCore`,省掉一层包装方法 -- `*Core` 后缀替代旧的 `*Impl`,覆盖项目内所有抽象覆写点 -- 后端时间线:DX12 `ID3D12Fence` / Vulkan timeline `VkSemaphore`(核心于 1.2,VK 1.3 标配) / Metal `MTLSharedEvent`。Vulkan 二进制 `VkFence` 仅在 swapchain 内部使用 -## 资源状态转换 +- `internal void Wait(ulong)` 是唯一的 wait 入口:加锁 → `GetCompletedValue() >= value` 短路 → `WaitCore(value)` → `CollectCompleted()`。`CommandSubmission.Wait()` 只是它的薄转发。 +- `WaitForIdle()` 一行:`Wait(lastSignaledValue)`;首次调用 `lastSignaledValue == 0` 时 `GetCompletedValue() >= 0` 自然短路。 +- `GetCompletedValue()` 是**方法**而非属性:DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue` 都是调用语义。 +- `Submit` 中 `nextValue++` 在使用前完成,读起来按"递增 → 提交 → 记录"顺序。 +- `CommandBuffer` 池化由 queue 自管:`CommandBuffer()` 从 `available` 出队(空则 `CreateCommandBuffer()`),`CollectCompleted()` 在每次 `Submit` / `Wait` 时回收已完成的并重置入池。 + +### 后端时间线对照 + +| 后端 | 时间线对象 | API | +|---|---|---| +| DX12 | 每 queue 一个 `ID3D12Fence` | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | +| Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore`(核心特性) | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | +| Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | + +二进制 `VkFence` 仅在 swapchain 内部使用。 + +### 与 SwapChain Present 的关系 + +DX12 / Vulkan 当前实现都在 GraphicsQueue 上 Present;新模型不变这条事实。`SwapChain.Present(waits)` 内部仍由 GraphicsQueue 提交 present,跨队列 wait 通过 GraphicsQueue 端的"等待对方时间线"指令落地(详见第 7 节后端映射表)。SwapChain 实现从 `GraphicsContext` 取 GraphicsQueue 引用即可,无需对外暴露。 + +## 6. CommandBuffer 操作 ```csharp +// === 状态 === + public void Transition(Buffer buffer, TransitionState newState); public void Transition(Texture texture, TransitionState newState); public void MemoryBarrier(); -``` -- 没有子范围 transition 重载(RDG 用例,公共 API 不暴露;后端内部仍提供给 RDG 直调) -- `MemoryBarrier()` 用于跨资源、跨阶段的全局内存同步;CommandBuffer 同时维护"上一次 dispatch 写过的 UAV 集合",下一次 dispatch 若读到同一资源会自动插入 barrier 作为 fallback -- 渲染目标 / 深度 / copy / 顶点 / 索引 / CBV / indirect / present 状态都由对应操作隐式转换 -- 用户只在 `ShaderResource` ↔ `UnorderedAccess` 之间显式转换 +// === RenderPass(内联) === -每个 `Texture` / `Buffer` 跟踪当前 `TransitionState`,初始化时由后端默认设置,第一次显式 `Transition` 时根据当前值生成 from→to barrier。 +public void BeginRenderPass(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment); -## ResourceTable - -```csharp -public abstract class ResourceTable -{ - public void Write(uint binding, BufferRange range); +public void EndRenderPass(); - public void Write(uint binding, TextureSubresourceRange range); +// === 视口 / 裁剪 === - public void Write(uint binding, Sampler sampler); +public void SetViewports(ReadOnlySpan viewports); - public void Write(uint binding, ReadOnlySpan ranges); +public void SetScissors(ReadOnlySpan scissors); - public void Write(uint binding, ReadOnlySpan ranges); +// === Pipeline / 资源绑定 === - public void Write(uint binding, ReadOnlySpan samplers); -} -``` +public void SetPipeline(Pipeline pipeline); -- `Buffer` / `Texture` 的隐式转换让单资源绑定一行代码 -- 没有 `BufferViewType` 参数,buffer 解释方式由对应槽位的 `ResourceLayout` 决定 -- 没有 `IBindableResource` 标记接口 +public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); -## CommandBuffer 操作 +// === 顶点 / 索引 === -```csharp -// 顶点 / 索引 public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); -// Buffer 拷贝 / 上传 +// === 绘制 / 调度 === + +public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); + +public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance); + +public void DrawIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); + +public void DrawIndexedIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); + +public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ); + +public void DispatchIndirect(Buffer argsBuffer, ulong offsetInBytes); + +// === Buffer 拷贝 / 上传 === + public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, Buffer destination, ulong destinationOffsetInBytes, ulong sizeInBytes); public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; -// Texture 拷贝 / 上传 / Resolve -public void CopyTexture(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, - Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, +// === Texture 拷贝 / 上传 / Resolve === + +public void CopyTexture(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, + Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, Extent3D extent); public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, - Texture destination, TextureSubresourceRange destinationRange, Offset3D destinationOrigin, + Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, Extent3D extent); -public void CopyTextureToBuffer(Texture source, TextureSubresourceRange sourceRange, Offset3D sourceOrigin, Extent3D extent, +public void CopyTextureToBuffer(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, Extent3D extent, Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, Texture destination, TextureSubresource destinationSubresource); -public void Upload(Texture destination, TextureSubresourceRange range, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; - -// 状态转换 -public void Transition(Buffer buffer, TransitionState newState); +public void Upload(Texture destination, TextureSubresourceLayers layers, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; +``` -public void Transition(Texture texture, TransitionState newState); +要点: -public void MemoryBarrier(); +- 字节相关参数统一 `*InBytes` 后缀;多元素入参一律 `ReadOnlySpan`;无 `in` 参数。 +- copy / upload 统一用 `TextureSubresourceLayers`(单 mip + 连续 layer 段),结构本身排除多 mip,无需运行时校验。 +- `ResolveTexture` 是"单点 → 单点",用 `TextureSubresource`。 +- `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;内部按 attachment 自动 `Transition` + 自动填 `SetViewports` / `SetScissors`,调用方可在其后再调一次 `SetViewports` / `SetScissors` 覆盖。 +- `PushResourceTable` 加 `ShaderStageFlags stages`:DX12 root parameter visibility / Metal argument table 都按 stage 区分;VK 的 set 在 layout 里编译期固定,stages 用于校验匹配。 +- `Transition` 只在 `ShaderResource` ↔ `UnorderedAccess` 之间显式调用;render target / 深度模板 / copy / 顶点 / 索引 / CBV / indirect / present 状态由对应操作隐式转换。 +- `MemoryBarrier()`:跨资源 / 跨阶段全局内存同步。CommandBuffer 同时维护"上一次 dispatch 写过的 UAV 集合",下一次 dispatch 若读到同一资源则自动插一个 barrier 作为 fallback。 -// 资源绑定(按 stage 推送) -public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); +### ResourceTable -// 渲染 pass -public void BeginRenderPass(ReadOnlySpan colorAttachments, - DepthStencilAttachmentDesc? depthStencilAttachment); +`Write` 强类型重载,取代当前 `Write(uint, params IBindableResource[])` + 运行时 type-switch: -public void EndRenderPass(); +```csharp +public abstract class ResourceTable +{ + public void Write(uint binding, BufferRange range); -// 视口 / 裁剪(复数 + span) -public void SetViewports(ReadOnlySpan viewports); + public void Write(uint binding, TextureSubresourceRange range); -public void SetScissors(ReadOnlySpan scissors); + public void Write(uint binding, Sampler sampler); -// 绘制 / 调度 -public void Draw(...); + public void Write(uint binding, ReadOnlySpan ranges); -public void DrawIndexed(...); + public void Write(uint binding, ReadOnlySpan ranges); -public void Dispatch(uint groupsX, uint groupsY, uint groupsZ); + public void Write(uint binding, ReadOnlySpan samplers); +} ``` -要点: -- 全部参数无 `in` -- 字节相关参数统一 `*InBytes` 后缀 -- copy / upload 用 `TextureSubresourceRange`(拷贝时校验 `LevelCount == 1`),不再有 `TextureSubresourceLayers` -- `Aspect` 字段消失,aspect 由 `Texture.Format` 推断 -- `BeginRenderPass` 直接接收 attachment span,没有 `RenderPassDesc` 类型;无深度时显式传 `null` -- `PushResourceTable` 接受 `ShaderStageFlags`,不再有 set 索引;后端按 stage 与资源 layout 直接绑定 - -### `PushResourceTable` 的设计取舍 +- `Buffer` / `Texture` 经隐式转换让单资源绑定保持一行。 +- buffer 解释(CBV / SRV-structured / SRV-byteaddress / UAV / typed)由槽位的 `ResourceLayout` 决定,公共面无 `BufferViewType`。 +- 校验:UAV 槽位要求 `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`,shape 与 layout 不匹配在 `Write` 时报错。 -- `SetResourceTable(uint set, ResourceTable)` 假设有 set 概念,仅 Vulkan 描述符集天然契合,DX12 / Metal 均需要做翻译层 -- 借鉴 Metal 的 `setVertexBuffer:atIndex:` / `setFragmentBuffer:atIndex:` 风格:调用方告诉 RHI"把这张表绑给这些 stage" -- DX12:根据 stages 选择对应 root parameter(VS/PS/CS visibility) -- Vulkan:layout 编译期固定,按 stage 走 push descriptor 或独立 set -- Metal:对应 stage 的 argument table - -## SwapChain +## 7. SwapChain ```csharp public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) @@ -454,17 +496,15 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract uint Height { get; } - /// 当前 backbuffer 索引。仅供后端与同程序集内部使用。 - internal abstract uint CurrentImageIndex { get; } - public abstract Texture CurrentColorTarget { get; } public abstract Texture? CurrentDepthStencilTarget { get; } /// - /// 提交 present,等待 完成;返回的 submission 表示下一张 backbuffer 已就绪。 - /// 首帧没有上一帧 present 可等待时,外部应自行准备首张就绪信号 - /// (典型做法:传 default(CommandSubmission),由后端识别为"无需等待")。 + /// 提交 present 到 GraphicsQueue,等待 ; + /// 返回的 submission 表示下一张 backbuffer 已就绪。 + /// 首帧外部传空 span,或用 default(CommandSubmission) 占位 — 后端从 waits 中过滤掉 queue is null 的项。 + /// 注意: 本身不防御 default,只能在 Submit / Present 返回的实例上调用。 /// public abstract CommandSubmission Present(params ReadOnlySpan waits); @@ -475,31 +515,30 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G ``` 设计要点: -- `Acquire` 完全消失。SwapChain 内部在 `Present` 时同步前进 backbuffer 索引并准备下一张图像的就绪信号 -- `Present` 返回 `CommandSubmission`,与 `CommandBuffer.Submit` 形态完全一致 -- 不提供首帧便利字段;首帧的"零号 submission"由用户自行准备 -- backbuffer 直接是 `Texture`,无 view 类型 -- `CurrentImageIndex` 不公开,仅后端跨文件协作时使用 + +- backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板);不公开 `FrameBuffer`,不公开 image index。 +- `Present(...)` 返回 `CommandSubmission`,与 `CommandBuffer.Submit(...)` 形态完全一致;接收任意队列的 wait。 +- 当前 image index 在后端内部维护(`VKSwapChain.ImageIndex` / `DXSwapChain.BufferIndex`),公共抽象类不感知。 +- 首帧 image-ready 由调用方 seed(飞行帧策略由调用方决定)。 后端映射: -| 后端 | `Present` | 下一帧就绪信号 | +| 后端 | `Present(waits)` 实现 | "下一帧就绪" 信号 | |---|---|---| -| DX12 | `IDXGISwapChain::Present`,`waits` 转为 queue Wait | 内部用 frame latency event 转成 `CommandSubmission` | -| Vulkan 1.3 | `vkQueuePresentKHR` 等待 `renderFinished[N]` 二进制 semaphore | `vkAcquireNextImageKHR` 在 Present 调用末尾执行,结果包装成时间线对齐的 `CommandSubmission` | -| Metal 4 | `drawable.Present()` 后等下一个 drawable | 下一帧 drawable 的可用事件 | +| DX12 | 对每个 wait 调 `graphicsQueue.Wait(otherFence, otherValue)` → `IDXGISwapChain3::Present` | frame latency event 包装为 `CommandSubmission`(值取自 graphics queue 时间线) | +| Vulkan 1.4 | 一次桥接 submit `vkQueueSubmit2(graphicsQueue, waits=timeline values, signal=renderFinished[N])` → `vkQueuePresentKHR(graphicsQueue, wait=renderFinished[N])` | 下一帧 `vkAcquireNextImageKHR` 返回的 binary `imageAvailable[N]`,再用一次 graphics queue 的 "wait binary, signal timeline" 桥接 submit 包装为 `CommandSubmission` | +| Metal 4 | `commandBuffer.EncodeWaitForEvent(...)` 转译每个 wait → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()` | 下一个 drawable 的可用事件包装为 `CommandSubmission` | -## 帧循环 +## 8. 帧循环 ```csharp -// 用户决定首帧 image-ready 的来源;最简单的方式是 default。 CommandSubmission imageReady = default; while (running) { CommandBuffer cmd = ctx.Graphics.CommandBuffer(); - ReadOnlySpan colors = + ReadOnlySpan colors = [ new() { @@ -511,7 +550,7 @@ while (running) } ]; - DepthStencilAttachmentDesc? depth = swapChain.CurrentDepthStencilTarget is { } ds + DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds ? new() { Texture = ds, @@ -522,13 +561,8 @@ while (running) } : null; + // viewport / scissor 已按 attachment 自动填充;如需自定义可后续覆盖 cmd.BeginRenderPass(colors, depth); - - Span viewports = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height, MinDepth = 0, MaxDepth = 1 }]; - Span scissors = [new() { X = 0, Y = 0, Width = swapChain.Width, Height = swapChain.Height }]; - - cmd.SetViewports(viewports); - cmd.SetScissors(scissors); // draws cmd.EndRenderPass(); @@ -538,12 +572,46 @@ while (running) } ``` -形态:每一步都是"接收一组 `CommandSubmission`,返回一个 `CommandSubmission`"。 +每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。 + +## 9. 三端 RenderPass / 状态映射 -## RDG 兼容性 +| 概念 | DX12 | Vulkan 1.4 | Metal 4 | +|---|---|---|---| +| Begin / End RenderPass | `OMSetRenderTargets` 或 `BeginRenderPass(RENDER_PASS_RENDER_TARGET_DESC)` / `EndRenderPass` | `vkCmdBeginRendering(VkRenderingInfo)` / `vkCmdEndRendering` | `commandBuffer.RenderCommandEncoder(MTLRenderPassDescriptor)` / `endEncoding` | +| LoadAction | `BeginningAccessType` (Discard / Preserve / Clear) | `loadOp` (DONT_CARE / LOAD / CLEAR) | `MTLLoadAction` | +| StoreAction | `EndingAccessType` (Discard / Preserve / Resolve) | `storeOp` (DONT_CARE / STORE / NONE) + `resolveMode` | `MTLStoreAction` | +| Resolve | `EndingAccessResolveSubresourceParameters` | `pResolveAttachments` + `resolveMode` | `MTLRenderPassDescriptor.resolveTexture` | +| 显式 Transition | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2(VkImageMemoryBarrier2 / VkBufferMemoryBarrier2)` | `commandEncoder.MemoryBarrier(scope, after, before)` | +| Aspect 推断源 | format → plane index | `Texture.Format` → `VkImageAspectFlags` | format → 自动 | -虽然简化版砍掉了子范围 transition,未来 RDG 层可以: -- 在 RDG 自身的命令缓冲适配器内调用后端私有方法 `TransitionRangeCore`,跳过公共 API -- 用自己的 hazard 跟踪覆盖隐式 UAV barrier;显式 `MemoryBarrier()` 仍可被 RDG 复用 -- 复用 `CommandSubmission` 处理跨队列依赖 -- 复用 `record struct` 子资源类型作为 hash key +## 10. RDG 接口自检 + +RDG 由 RHI 用户自行封装,本节只回答:「本 RHI 是否暴露了 RDG 所需的所有原语?」 + +| RDG 需求 | 本 RHI 是否提供 | 对应入口 | +|---|---|---| +| 显式资源状态转换 | ✅ | `CommandBuffer.Transition(Buffer/Texture, TransitionState)` | +| 全局内存屏障 | ✅ | `CommandBuffer.MemoryBarrier()` | +| 子范围寻址(用作 alias / hazard key) | ✅ | `BufferRange` / `TextureSubresourceRange`(`record struct`,可哈希) | +| 跨队列依赖 | ✅ | `CommandSubmission` waits(`Submit` / `Present` 同形态) | +| 队列完成查询 / 等待 | ✅ | `CommandQueue.WaitForIdle()` / `CommandSubmission.Wait()` | +| 短生命周期 RenderPass | ✅ | 内联 `BeginRenderPass(colorAttachments, depth)` | +| Pass 间共享绑定 | ✅ | `ResourceTable` 缓存键 = (range, layout) | + +结论:RDG 实现方可在不修改 RHI 公共面的前提下完成封装。 + +## 11. ZenithHelper 受影响成员 + +[ZenithHelper](sources/Zenith.NET/ZenithHelper.cs) 中以下函数依赖被删除的 `TextureSlice`(含 `Face`) / `TextureViewDesc`,或依赖 "cube 面隐式乘进子资源计数" 模型,新设计下不再适用,需删除或以新型重写: + +- `FaceCount(TextureDesc)` —— cube / cubeArray 以 6-layer array 表达,`ArrayLayers` 含 face 总层数,face 不再是独立轴。 +- `FaceIndex(TextureDesc, TextureSlice)` —— 依赖 `TextureSlice.Face`。 +- `FlattenArrayLayerCount(TextureDesc)` —— "把 face 乘进 array layer" 的折叠不再需要,直接 `desc.ArrayLayers`。 +- `FlattenArrayLayerIndex(TextureDesc, TextureSlice)` —— 同上。 +- `FlattenArrayLayerRange(TextureViewDesc)` —— `TextureViewDesc` 删除。 +- `SubresourceCount(TextureDesc)` —— 新模型 = `MipLevels * ArrayLayers`,使用侧可内联。 +- `SubresourceIndex(TextureDesc, TextureSlice)` —— 新模型直接 `MipLevel + ArrayLayer * MipLevels`,使用侧内联。 +- `SubresourceSizeInBytes(TextureDesc, TextureSlice)` —— 改为 `TextureSubresource` 入参重写(仅需 `MipLevel`,其余复用现有 `MipDimensions` + `SizeInBytes`)。 + +不受影响:纯格式 / 几何计算 helper(`MipDimensions`、`SizeInBytes(PixelFormat,...)`、`ElementFormat` 字节表等)保持不变。 From 89386781da33aa449cc2d8e63a5566c9fcc08389 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 22 Apr 2026 16:07:37 +0800 Subject: [PATCH 028/466] Refactor RHI documentation: update interop/native handles section and remove obsolete ZenithHelper references --- rhi-redesign.draft.md | 197 ++++++++++++++++++++++++++++++++++++++- rhi-redesign.zh.draft.md | 196 +++++++++++++++++++++++++++++++++++++- 2 files changed, 391 insertions(+), 2 deletions(-) diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index d4fd41aa..d49e05aa 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -605,7 +605,202 @@ The RDG is built on top of this RHI by the consumer, not by us. This section onl Conclusion: an RDG implementation can be built without any further changes to the RHI public surface. -## 11. ZenithHelper Members Affected +## 11. Interop / Native Handles + +For third-party libraries that need backend-native handles: Skia / DLSS / FSR / RenderDoc / tooling. Design principles: + +- The public surface exposes only `nint` plus an enum; no backend types and no platform-conditional properties. +- The RHI does not perform work on behalf of the external library; the caller follows the documented protocol for state synchronization. +- The interop API is a side-effect of state the RHI already maintains internally — zero extra runtime cost. + +### 11.1 NativeObjectType + +```csharp +public enum NativeObjectType +{ + // DirectX 12 + DxgiFactory, + DxgiAdapter, + D3D12Device, + D3D12CommandQueue, + D3D12GraphicsCommandList, + D3D12Resource, + D3D12CpuDescriptorHandleSampler, + D3D12CpuDescriptorHandleRtv, + D3D12CpuDescriptorHandleDsv, + D3D12CpuDescriptorHandleSrv, + D3D12CpuDescriptorHandleUav, + + // Metal + MtlDevice, + Mtl4CommandQueue, + Mtl4CommandBuffer, + MtlTexture, + MtlBuffer, + MtlSamplerState, + + // Vulkan + VkInstance, + VkPhysicalDevice, + VkDevice, + VkQueue, + VkQueueFamilyIndex, + VkCommandBuffer, + VkImage, + VkImageView, + VkBuffer, + VkSampler +} +``` + +Prefixes follow the original native APIs: DX12 splits into `Dxgi` / `D3D12`; Metal 4 keeps `Mtl4` distinct from the older `Mtl`; Vulkan uses `Vk`. `D3D12CpuDescriptorHandle*` is split per view type to avoid ambiguity at a single entry point. + +Non-handle scalar information (e.g. `VkQueueFamilyIndex`) flows through the same entry point, carrying a `uint` inside an `nint`. The public surface never grows a platform-conditional property. + +### 11.2 INativeObject Interface + +```csharp +public interface INativeObject +{ + /// Returns 0 when the enum does not match this object or the current backend. + nint GetNativeObject(NativeObjectType type); +} + +public abstract class GraphicsContext : INativeObject +{ + public abstract nint GetNativeObject(NativeObjectType type); +} + +public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject +{ + public GraphicsContext Context { get; } = context; + + public abstract nint GetNativeObject(NativeObjectType type); +} +``` + +Every `GraphicsResource` subclass (`Buffer` / `Texture` / `Sampler` / `CommandQueue` / `CommandBuffer` / `SwapChain` / `Pipeline` / `Shader` / `ResourceTable` / `ResourceLayout`) implements the interface. Backends use a `switch`; subclasses with nothing to expose return 0. Third-party code can program against the interface without distinguishing context from resource. + +Backend implementation examples: + +```csharp +// VKTexture +public override nint GetNativeObject(NativeObjectType type) => type switch +{ + NativeObjectType.VkImage => (nint)Image.Handle, + NativeObjectType.VkImageView => (nint)View.Handle, + _ => 0 +}; + +// DXBuffer +public override nint GetNativeObject(NativeObjectType type) => type switch +{ + NativeObjectType.D3D12Resource => (nint)Resource.Handle, + _ => 0 +}; + +// Subclass that does not currently expose anything +public override nint GetNativeObject(NativeObjectType type) => 0; +``` + +### 11.3 CommandBuffer Interop Verbs + +```csharp +public abstract class CommandBuffer +{ + /// + /// Enter the interop scope: end the current RenderPass / Encoder (if any), clear the + /// RHI's internal binding caches (PSO / descriptors / vertex+index buffers / viewports / scissors). + /// Does not write any command into the cmd (Vulkan exception: one vkCmdEndRendering call). + /// After entering, use GetNativeObject(...) to hand native handles to the external library. + /// Begin / End must be paired; nesting is forbidden. + /// + public abstract void BeginExternalCommands(); + + /// + /// Leave the interop scope: emit one global MemoryBarrier to isolate the external library's + /// writes from subsequent RHI commands. Does not re-open any RenderPass / Encoder; the caller + /// reissues BeginRenderPass / SetPipeline as needed. + /// + public abstract void EndExternalCommands(); + + /// + /// Synchronize the RHI's cached resource state to newState. Writes neither cmd nor barrier. + /// The next Transition uses this value to compute from → to. Callable inside or outside the External scope. + /// + public abstract void SetState(Texture texture, TransitionState newState); + + public abstract void SetState(Buffer buffer, TransitionState newState); +} +``` + +Backend implementation notes: + +- `BeginExternalCommands`: + - Common: clear `cachedPipeline` / `cachedRootSignature` (DX12) / `cachedDescriptorSets/Heaps` / `cachedVertexBuffers` / `cachedIndexBuffer` / `cachedViewports` / `cachedScissors`. + - VK: if currently inside a dynamic rendering scope, call `vkCmdEndRendering` once. + - DX12 / Metal: only clear fields; do not write cmd (DX12 must NOT call `ID3D12GraphicsCommandList::ClearState`, which would actually emit a command). + - Set `inExternalScope = true`; throw if already true. +- `EndExternalCommands`: + - Call the internal `MemoryBarrier()` once (VK = `vkCmdPipelineBarrier2(ALL → ALL, MEMORY_READ|WRITE → MEMORY_READ|WRITE)`; DX12 = `ResourceBarrier(UAV, nullptr)` or `D3D12_GLOBAL_BARRIER`; Metal = `MemoryBarrier(scope=AllResources, after=AllStages, before=AllStages)`). + - Set `inExternalScope = false`; throw if already false. + - Do not re-open RenderPass / Encoder. +- `SetState`: write `texture.CurrentState = newState` / `buffer.CurrentState = newState` directly; never touches cmd. + +### 11.4 Caller Templates + +```csharp +// === DLSS (DX12) — shared cmd, state preserved === +cmd.Transition(colorIn, TransitionState.ShaderResource); +cmd.Transition(colorOut, TransitionState.UnorderedAccess); + +cmd.BeginExternalCommands(); +DLSS.Evaluate( + cmd.GetNativeObject(NativeObjectType.D3D12GraphicsCommandList), + colorIn.GetNativeObject(NativeObjectType.D3D12Resource), + colorOut.GetNativeObject(NativeObjectType.D3D12Resource)); +cmd.EndExternalCommands(); // auto barrier isolates DLSS writes from subsequent RHI commands + +cmd.BeginRenderPass(...); + +// === FSR/FFX (Vulkan) — shared cmd, external library mutates state === +cmd.Transition(colorIn, TransitionState.ShaderResource); +cmd.Transition(colorOut, TransitionState.UnorderedAccess); + +cmd.BeginExternalCommands(); +Ffx.Fsr2Dispatch( + cmd.GetNativeObject(NativeObjectType.VkCommandBuffer), + colorIn.GetNativeObject(NativeObjectType.VkImage), + colorOut.GetNativeObject(NativeObjectType.VkImage)); +cmd.SetState(colorOut, TransitionState.ShaderResource); +cmd.EndExternalCommands(); + +cmd.BeginRenderPass(...); + +// === Skia (Vulkan) — not sharing cmd, library submits on its own === +nint instance = ctx.GetNativeObject(NativeObjectType.VkInstance); +nint device = ctx.GetNativeObject(NativeObjectType.VkDevice); +nint queue = ctx.Graphics.GetNativeObject(NativeObjectType.VkQueue); +uint family = (uint)ctx.Graphics.GetNativeObject(NativeObjectType.VkQueueFamilyIndex); +var grContext = GRContext.MakeVulkan(instance, device, queue, family, /*...*/); +// Libraries that do not share cmd need no Begin/End; just SetState before the next RHI access. + +// === RenderDoc — device handle only === +nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); +RenderDocApi.StartFrameCapture(device, IntPtr.Zero); +// ... render one frame ... +RenderDocApi.EndFrameCapture(device, IntPtr.Zero); +``` + +### 11.5 Out of Scope + +- No backend types or platform-conditional properties on the public surface. +- No "import external native resource" entry point in this iteration; if needed later, add `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` with the same shape. +- `BeginExternalCommands` is non-reentrant. +- `EndExternalCommands` does not re-open any RenderPass / Encoder; the caller does that. +- The caller guarantees that the external library is invoked outside any RenderPass (typical interop is compute-only, so this holds naturally). + +## 12. ZenithHelper Members Affected The following functions in [ZenithHelper](sources/Zenith.NET/ZenithHelper.cs) depend on the now-removed `TextureSlice` (with `Face`) / `TextureViewDesc`, or on the "cube faces multiply implicitly into subresource counts" model. Under the new design they no longer apply and must be removed or rewritten against the new types: diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index 6e9c279a..7cfecc2f 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -601,7 +601,201 @@ RDG 由 RHI 用户自行封装,本节只回答:「本 RHI 是否暴露了 RD 结论:RDG 实现方可在不修改 RHI 公共面的前提下完成封装。 -## 11. ZenithHelper 受影响成员 +## 11. 互操作 / 原生句柄 + +面向 Skia / DLSS / FSR / RenderDoc / 工具链等需要拿后端原生句柄的场景。设计原则: + +- 公共面只暴露 `nint` + 一组枚举;不出现任何后端类型 / 平台条件属性 +- RHI 不替外部库做事;调用方按既有模板自管 state 协议 +- 互操作 API 是 RHI 内部本就维护的状态的副产品,零额外运行时成本 + +### 11.1 NativeObjectType 枚举 + +```csharp +public enum NativeObjectType +{ + // DirectX 12 + DxgiFactory, + DxgiAdapter, + D3D12Device, + D3D12CommandQueue, + D3D12GraphicsCommandList, + D3D12Resource, + D3D12CpuDescriptorHandleSampler, + D3D12CpuDescriptorHandleRtv, + D3D12CpuDescriptorHandleDsv, + D3D12CpuDescriptorHandleSrv, + D3D12CpuDescriptorHandleUav, + + // Metal + MtlDevice, + Mtl4CommandQueue, + Mtl4CommandBuffer, + MtlTexture, + MtlBuffer, + MtlSamplerState, + + // Vulkan + VkInstance, + VkPhysicalDevice, + VkDevice, + VkQueue, + VkQueueFamilyIndex, + VkCommandBuffer, + VkImage, + VkImageView, + VkBuffer, + VkSampler +} +``` + +命名沿用各原生 API 既有前缀:DX12 体系按 `Dxgi` / `D3D12` 区分;Metal 4 与旧 Metal 共存时用 `Mtl` / `Mtl4` 区分;Vulkan 一律 `Vk`。`D3D12CpuDescriptorHandle*` 按 view 类型拆开,避免单入口语义模糊。 + +非句柄的标量信息(如 `VkQueueFamilyIndex`)也走同一入口,以 `nint` 承载 `uint`,公共面不挂任何平台条件属性。 + +### 11.2 INativeObject 接口 + +```csharp +public interface INativeObject +{ + /// 枚举不匹配本对象 / 当前后端时返回 0。 + nint GetNativeObject(NativeObjectType type); +} + +public abstract class GraphicsContext : INativeObject +{ + public abstract nint GetNativeObject(NativeObjectType type); +} + +public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject +{ + public GraphicsContext Context { get; } = context; + + public abstract nint GetNativeObject(NativeObjectType type); +} +``` + +所有 `GraphicsResource` 子类(`Buffer` / `Texture` / `Sampler` / `CommandQueue` / `CommandBuffer` / `SwapChain` / `Pipeline` / `Shader` / `ResourceTable` / `ResourceLayout`)一律自动实现该接口;后端按 `switch` 实现,不感兴趣的子类返回 0。第三方代码可走接口编程,不必区分 context / resource。 + +后端实现示例: + +```csharp +// VKTexture +public override nint GetNativeObject(NativeObjectType type) => type switch +{ + NativeObjectType.VkImage => (nint)Image.Handle, + NativeObjectType.VkImageView => (nint)View.Handle, + _ => 0 +}; + +// DXBuffer +public override nint GetNativeObject(NativeObjectType type) => type switch +{ + NativeObjectType.D3D12Resource => (nint)Resource.Handle, + _ => 0 +}; + +// 暂不暴露的子类 +public override nint GetNativeObject(NativeObjectType type) => 0; +``` + +### 11.3 CommandBuffer 互操作动词 + +```csharp +public abstract class CommandBuffer +{ + /// + /// 进入互操作段:结束当前 RenderPass / Encoder(如有),清空 RHI 内部绑定缓存 + /// (PSO / 描述符 / 顶点索引 / viewport / scissor)。 + /// 不向 cmd 写入命令(VK 例外:会调一次 vkCmdEndRendering)。 + /// 进入后请通过 GetNativeObject(...) 拿原生句柄交给外部库。 + /// Begin / End 必须成对调用,禁止嵌套。 + /// + public abstract void BeginExternalCommands(); + + /// + /// 结束互操作段:插入一个全局 MemoryBarrier 以隔离外部库的写入与后续 RHI 命令。 + /// 不重新打开 RenderPass / Encoder;调用方按需 BeginRenderPass / SetPipeline。 + /// + public abstract void EndExternalCommands(); + + /// + /// 同步 RHI 资源状态缓存为 newState,不写 cmd、不发 barrier。 + /// 下一次 Transition 据此算 from→to。允许在 External scope 内或外调用。 + /// + public abstract void SetState(Texture texture, TransitionState newState); + + public abstract void SetState(Buffer buffer, TransitionState newState); +} +``` + +后端实现要点: + +- `BeginExternalCommands`: + - 公共部分:清 `cachedPipeline` / `cachedRootSignature`(DX12) / `cachedDescriptorSets/Heaps` / `cachedVertexBuffers` / `cachedIndexBuffer` / `cachedViewports` / `cachedScissors` + - VK:若当前在 dynamic rendering scope,调一次 `vkCmdEndRendering` + - DX12 / Metal:仅清字段,不写 cmd(DX12 不调 `ID3D12GraphicsCommandList::ClearState`,那会真往 cmd 写命令) + - 置内部 `inExternalScope = true`;若已为 true → 抛异常 +- `EndExternalCommands`: + - 调用一次内部 `MemoryBarrier()`(VK = `vkCmdPipelineBarrier2(ALL → ALL, MEMORY_READ|WRITE → MEMORY_READ|WRITE)`;DX12 = `ResourceBarrier(UAV, nullptr)` 或 `D3D12_GLOBAL_BARRIER`;Metal = `MemoryBarrier(scope=AllResources, after=AllStages, before=AllStages)`) + - 置 `inExternalScope = false`;若已为 false → 抛异常 + - 不重开 RenderPass / Encoder +- `SetState`:直接写 `texture.CurrentState = newState` / `buffer.CurrentState = newState`,零写 cmd。 + +### 11.4 调用方模板 + +```csharp +// === DLSS (DX12) — 共享 cmd,state 不变 === +cmd.Transition(colorIn, TransitionState.ShaderResource); +cmd.Transition(colorOut, TransitionState.UnorderedAccess); + +cmd.BeginExternalCommands(); +DLSS.Evaluate( + cmd.GetNativeObject(NativeObjectType.D3D12GraphicsCommandList), + colorIn.GetNativeObject(NativeObjectType.D3D12Resource), + colorOut.GetNativeObject(NativeObjectType.D3D12Resource)); +cmd.EndExternalCommands(); // 自动插入 barrier,隔离 DLSS 写入与后续 RHI 命令 + +cmd.BeginRenderPass(...); + +// === FSR/FFX (Vulkan) — 共享 cmd,state 由外部库改 === +cmd.Transition(colorIn, TransitionState.ShaderResource); +cmd.Transition(colorOut, TransitionState.UnorderedAccess); + +cmd.BeginExternalCommands(); +Ffx.Fsr2Dispatch( + cmd.GetNativeObject(NativeObjectType.VkCommandBuffer), + colorIn.GetNativeObject(NativeObjectType.VkImage), + colorOut.GetNativeObject(NativeObjectType.VkImage)); +cmd.SetState(colorOut, TransitionState.ShaderResource); +cmd.EndExternalCommands(); + +cmd.BeginRenderPass(...); + +// === Skia (Vulkan) — 不共享 cmd,自带提交 === +nint instance = ctx.GetNativeObject(NativeObjectType.VkInstance); +nint device = ctx.GetNativeObject(NativeObjectType.VkDevice); +nint queue = ctx.Graphics.GetNativeObject(NativeObjectType.VkQueue); +uint family = (uint)ctx.Graphics.GetNativeObject(NativeObjectType.VkQueueFamilyIndex); +var grContext = GRContext.MakeVulkan(instance, device, queue, family, /*...*/); +// 不共享 cmd 的库不需要 Begin/End;只需在下一次用到资源前 SetState 同步 layout + +// === RenderDoc — 仅 device 句柄 === +nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); +RenderDocApi.StartFrameCapture(device, IntPtr.Zero); +// ... 渲染一帧 ... +RenderDocApi.EndFrameCapture(device, IntPtr.Zero); +``` + +### 11.5 不做的事 + +- 不暴露任何后端类型 / 平台条件属性 +- 不提供「导入外部 native 资源」入口(首版);如未来需要:通过 `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` 加入,签名同形态 +- 不允许 `BeginExternalCommands` 嵌套 +- `EndExternalCommands` 不重开 RenderPass / Encoder(调用方按需重开) +- 调用方自己保证调用外部库时不在 RenderPass 内(典型互操作都是 compute,天然如此) + +## 12. ZenithHelper 受影响成员 [ZenithHelper](sources/Zenith.NET/ZenithHelper.cs) 中以下函数依赖被删除的 `TextureSlice`(含 `Face`) / `TextureViewDesc`,或依赖 "cube 面隐式乘进子资源计数" 模型,新设计下不再适用,需删除或以新型重写: From 401739d88801e69b51024539c746b4f69fff8710 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 22 Apr 2026 19:03:49 +0800 Subject: [PATCH 029/466] Refactor RHI redesign documents: improve clarity and consistency in API-surface conventions and naming rules --- rhi-redesign.draft.md | 66 ++++++++++++++-------------------- rhi-redesign.zh.draft.md | 78 +++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 81 deletions(-) diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index d49e05aa..78d1bb79 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -24,22 +24,18 @@ ## 2. API-Surface Conventions -- All public value types: `record struct` with public mutable fields. No `ref struct`. No `in` parameters. +- All public value types: `record struct` with public mutable fields. - Multi-element parameters: `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, no `IEnumerable`. - All method bodies use `{ ... }`. **Exception:** `ref` / `ref readonly` returning properties keep `=> ref _field;`. - Every byte-denominated parameter or field carries an `*InBytes` suffix. -- Backend-hook naming rule: the `*Core` suffix exists **only** to disambiguate from a **same-named** non-`Core` wrapper. - - `Wait(ulong)` wrapper → `WaitCore(ulong)` - - `Submit(...)` wrapper → `SubmitCore(...)` - - `CreateCommandBuffer()` has no same-named wrapper (the public factory is `CommandBuffer()`) → no `Core` suffix. - - `GetCompletedValue()` has no wrapper → no `Core` suffix. +- Backend-hook naming: the `*Core` suffix exists **only** to disambiguate from a same-named non-`Core` wrapper (e.g. `Wait` / `WaitCore`, `Submit` / `SubmitCore`). Hooks without a wrapper use their natural name. - Every abstract member is plain `protected abstract`; same-assembly callers go through an `internal` wrapper. - The only public synchronization result type is `CommandSubmission`. ## 3. Public Type Catalog ```csharp -// === Subresources (rule: views use range, copy/upload uses layers, single-point uses single) === +// === Subresources === public record struct TextureSubresource { @@ -172,11 +168,20 @@ public record struct DepthStencilAttachment // === Queue submission result === -public readonly struct CommandSubmission(CommandQueue queue, ulong value) +/// +/// One point on a queue's completion timeline, produced by Submit / Present. +/// default is the well-defined empty value: backends filter it out of any waits, +/// and is a no-op on it. +/// +public readonly struct CommandSubmission(CommandQueue? queue, ulong value) { + public CommandQueue? Queue = queue; + + public ulong Value = value; + public void Wait() { - queue.Wait(value); + Queue?.Wait(Value); } } ``` @@ -254,7 +259,6 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ private readonly Queue available = []; private readonly Queue execution = []; - private ulong nextValue = 1; private ulong lastSignaledValue; public CommandQueueType Type { get; } = type; @@ -302,15 +306,13 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ commandBuffer.End(); - nextValue++; + lastSignaledValue++; - SubmitCore(commandBuffer, waits, nextValue); + SubmitCore(commandBuffer, waits, lastSignaledValue); - lastSignaledValue = nextValue; + execution.Enqueue(new(commandBuffer, lastSignaledValue)); - execution.Enqueue(new(commandBuffer, nextValue)); - - return new(this, nextValue); + return new(this, lastSignaledValue); } private void CollectCompleted() @@ -347,13 +349,10 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) } ``` -Key points: +Notes: -- `internal void Wait(ulong)` is the **single** wait entry point: lock → short-circuit on `GetCompletedValue() >= value` → `WaitCore(value)` → `CollectCompleted()`. `CommandSubmission.Wait()` is just a thin forward. -- `WaitForIdle()` is one line: `Wait(lastSignaledValue)`. The first call has `lastSignaledValue == 0`; `GetCompletedValue() >= 0` short-circuits naturally. -- `GetCompletedValue()` is a **method**, not a property: DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue` are all call-shaped on the backends. -- In `Submit`, `nextValue++` runs first, so the same post-incremented value is submitted, signalled, enqueued, and returned. Reads top-to-bottom as "advance → submit → record". -- Command buffer pooling lives in the queue: `CommandBuffer()` pops from `available` (or calls `CreateCommandBuffer()`); `CollectCompleted()` runs at every `Submit` / `Wait` and recycles buffers whose timeline value has retired. +- `GetCompletedValue()` is a **method**, not a property: all three backends (DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`) are call-shaped. +- Command buffer pooling lives in the queue: `CollectCompleted()` runs at every `Submit` / `Wait` and recycles buffers whose timeline value has retired. ### Backend Timeline Mapping @@ -367,7 +366,7 @@ The internal binary `VkFence` is retained for swapchain image acquire only. ### Interaction with SwapChain Present -Both DX12 and Vulkan implementations execute `Present` on the GraphicsQueue today, and the new design keeps this behavior. `SwapChain.Present(waits)` submits the present from the GraphicsQueue; cross-queue waits are honored on the GraphicsQueue side via "wait other queue's timeline" instructions (see Section 7 mapping table). The SwapChain implementation pulls the GraphicsQueue from `GraphicsContext`; the queue reference is never exposed on `SwapChain`'s public surface. +`Present` runs on the GraphicsQueue. `SwapChain.Present(waits)` submits the present from the GraphicsQueue; cross-queue waits are honored on the GraphicsQueue side via "wait other queue's timeline" instructions (see Section 7 mapping table). The SwapChain implementation pulls the GraphicsQueue from `GraphicsContext`; the queue reference is never exposed on `SwapChain`'s public surface. To keep barriers simple, all resources are created with concurrent / shared semantics on Vulkan (`VK_SHARING_MODE_CONCURRENT` across the three queues). Cross-queue synchronization is therefore expressed entirely through `CommandSubmission` waits; user code never writes Vulkan ownership-transfer barriers. @@ -452,13 +451,10 @@ public void Upload(Texture destination, TextureSubresourceLayers layers, Offs Notes: -- All byte-denominated parameters carry the `*InBytes` suffix; multi-element parameters are `ReadOnlySpan`; no `in` parameters. -- Copy / upload uses `TextureSubresourceLayers` (one mip + contiguous layer range). The type itself excludes multi-mip, so no runtime validation is needed. -- `ResolveTexture` is single-point → single-point and uses `TextureSubresource`. - `BeginRenderPass` accepts an attachment span; pass `null` explicitly for no depth. The implementation auto-`Transition`s the attachments and auto-fills `SetViewports` / `SetScissors` from attachment dimensions; the caller may issue `SetViewports` / `SetScissors` afterward to override. - `PushResourceTable` takes a `ShaderStageFlags stages` argument: DX12 root-parameter visibility and Metal argument tables are stage-scoped. On Vulkan the descriptor set's stage mask is fixed at layout creation time, so `stages` is used to validate the call against the layout. - `Transition` is only called explicitly between `ShaderResource` and `UnorderedAccess`. Render-target / depth-stencil / copy / vertex / index / CBV / indirect / present states are transitioned implicitly by the corresponding operation. -- `MemoryBarrier()` is a global cross-resource / cross-stage memory barrier. The command buffer also tracks the set of UAVs written by the previous dispatch and inserts a barrier automatically as a fallback when the next dispatch reads the same resource. +- `MemoryBarrier()` is a global cross-resource / cross-stage memory barrier; the caller decides when to issue it. ### ResourceTable @@ -503,12 +499,8 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture? CurrentDepthStencilTarget { get; } /// - /// Submits a present operation on the GraphicsQueue, waiting on . - /// Returns a CommandSubmission that signals when the next backbuffer is safe to write. - /// First-frame seed is the caller's responsibility — pass an empty span, or seed an `imageReady` - /// local with `default(CommandSubmission)` and let the backend filter it out of the next `Submit`'s waits. - /// Note: itself does not defend against `default`; only call it - /// on submissions returned by an actual `Submit` / `Present`. + /// Submits a present on the GraphicsQueue, waiting on ; + /// returns a that signals when the next backbuffer is writable. /// public abstract CommandSubmission Present(params ReadOnlySpan waits); @@ -523,7 +515,6 @@ Design points: - Backbuffers are exposed as plain `Texture`s (color + optional depth-stencil). No `FrameBuffer`, no image index. - `Present(...)` returns a `CommandSubmission` describing when the next backbuffer is writable; it accepts waits from any queue. - The current backbuffer index lives **only** inside the backend (today: `VKSwapChain.ImageIndex`, `DXSwapChain.BufferIndex`). Public callers never need it. -- The first-frame image-ready submission is supplied by the caller (frames-in-flight strategy is the caller's choice). Backend mapping: @@ -601,7 +592,6 @@ The RDG is built on top of this RHI by the consumer, not by us. This section onl | Cross-queue dependencies | ✅ | `CommandSubmission` waits (symmetric on `Submit` / `Present`) | | Queue completion query / wait | ✅ | `CommandQueue.WaitForIdle()` / `CommandSubmission.Wait()` | | Short-lived RenderPass | ✅ | Inline `BeginRenderPass(colorAttachments, depth)` | -| Cross-pass binding sharing | ✅ | `ResourceTable` cache key = (range, layout) | Conclusion: an RDG implementation can be built without any further changes to the RHI public surface. @@ -783,7 +773,8 @@ nint device = ctx.GetNativeObject(NativeObjectType.VkDevice); nint queue = ctx.Graphics.GetNativeObject(NativeObjectType.VkQueue); uint family = (uint)ctx.Graphics.GetNativeObject(NativeObjectType.VkQueueFamilyIndex); var grContext = GRContext.MakeVulkan(instance, device, queue, family, /*...*/); -// Libraries that do not share cmd need no Begin/End; just SetState before the next RHI access. +// Skia owns its own command buffers; for any resource shared with the RHI, call SetState on the +// matching RHI handle before the next RHI use to keep the cached state in sync. // === RenderDoc — device handle only === nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); @@ -794,10 +785,7 @@ RenderDocApi.EndFrameCapture(device, IntPtr.Zero); ### 11.5 Out of Scope -- No backend types or platform-conditional properties on the public surface. - No "import external native resource" entry point in this iteration; if needed later, add `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` with the same shape. -- `BeginExternalCommands` is non-reentrant. -- `EndExternalCommands` does not re-open any RenderPass / Encoder; the caller does that. - The caller guarantees that the external library is invoked outside any RenderPass (typical interop is compute-only, so this holds naturally). ## 12. ZenithHelper Members Affected diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index 7cfecc2f..790cd63b 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -7,7 +7,7 @@ **目标** - 公共 API 表面小、调用形态统一,与 DirectX 12 / Vulkan 1.4 / Metal 4 命令模型 1:1 对应 -- 帧循环代码短、热路径零托管分配 +- 帧循环代码短,热路径零托管分配 - 跨队列同步(Graphics / Compute / Copy)一等公民 - 显式资源状态转换,作为未来 RDG 的稳定基座 - 内联 RenderPass,去掉长生命周期 `FrameBuffer` @@ -15,31 +15,27 @@ **非目标** -- RHI 内不做完整 hazard tracking(仅维护"当前状态"用于计算 from→to) +- RHI 内不做完整 hazard tracking(仅缓存"当前状态"用于计算显式 transition 的 from 端) - 一次 submit 一个同步对象 - 把 `WaitForIdle` 当作主同步手段 -- 本轮不实现 RDG、bindless / descriptor buffer +- 本轮不实现 RDG、bindless、descriptor buffer - 不重构 `ResourceTable` / `ResourceLayout` / `Sampler` / `Pipeline` / shader IO 之外的部分 - 不在公共面暴露 per-aspect (color/depth/stencil) 寻址、子范围 transition、首帧 image-ready 等待 ## 2. 命名与代码风格约定 -- 所有公共值类型:`record struct`,公共字段可写;不使用 `ref struct`、不使用 `in` 参数 +- 所有公共值类型:`record struct`,公共字段可写 - 多元素入参一律 `ReadOnlySpan` / `params ReadOnlySpan`,不使用 `T[]` / `IEnumerable` - 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性保留 `=> ref _field;` - 字节单位字段与参数加 `*InBytes` 后缀 -- 抽象后端钩子的命名规则:**只有**当存在**同名**的非 `Core` 包装时才用 `*Core` 后缀;否则用自然名 - - `Wait(ulong)` 包装 → `WaitCore(ulong)` - - `Submit(...)` 包装 → `SubmitCore(...)` - - `CreateCommandBuffer()` 无同名包装(公共工厂叫 `CommandBuffer()`)→ 不加 `Core` - - `GetCompletedValue()` 无包装 → 不加 `Core` +- 抽象后端钩子命名:**只有**与同名非 `Core` 包装共存时才用 `*Core` 后缀(如 `Wait` / `WaitCore`、`Submit` / `SubmitCore`);其余一律用自然名 - 抽象成员一律 `protected abstract`,同程序集内部调用走 `internal` 包装 - 公共同步结果只有一种:`CommandSubmission` ## 3. 公共类型一览 ```csharp -// === 子资源(设计原则:view 用 range,copy/upload 用 layers,单点用 single) === +// === 子资源 === public record struct TextureSubresource { @@ -172,11 +168,19 @@ public record struct DepthStencilAttachment // === 队列同步结果 === -public readonly struct CommandSubmission(CommandQueue queue, ulong value) +/// +/// 一次 Submit / Present 产生的一个时间线点。 +/// default 是合法的"空"值,后端会从 waits 中过滤掉;其上 为空操作。 +/// +public readonly struct CommandSubmission(CommandQueue? queue, ulong value) { + public CommandQueue? Queue = queue; + + public ulong Value = value; + public void Wait() { - queue.Wait(value); + Queue?.Wait(Value); } } ``` @@ -254,7 +258,6 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ private readonly Queue available = []; private readonly Queue execution = []; - private ulong nextValue = 1; private ulong lastSignaledValue; public CommandQueueType Type { get; } = type; @@ -302,15 +305,13 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ commandBuffer.End(); - nextValue++; + lastSignaledValue++; - SubmitCore(commandBuffer, waits, nextValue); + SubmitCore(commandBuffer, waits, lastSignaledValue); - lastSignaledValue = nextValue; + execution.Enqueue(new(commandBuffer, lastSignaledValue)); - execution.Enqueue(new(commandBuffer, nextValue)); - - return new(this, nextValue); + return new(this, lastSignaledValue); } private void CollectCompleted() @@ -349,11 +350,8 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) 要点: -- `internal void Wait(ulong)` 是唯一的 wait 入口:加锁 → `GetCompletedValue() >= value` 短路 → `WaitCore(value)` → `CollectCompleted()`。`CommandSubmission.Wait()` 只是它的薄转发。 -- `WaitForIdle()` 一行:`Wait(lastSignaledValue)`;首次调用 `lastSignaledValue == 0` 时 `GetCompletedValue() >= 0` 自然短路。 -- `GetCompletedValue()` 是**方法**而非属性:DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue` 都是调用语义。 -- `Submit` 中 `nextValue++` 在使用前完成,读起来按"递增 → 提交 → 记录"顺序。 -- `CommandBuffer` 池化由 queue 自管:`CommandBuffer()` 从 `available` 出队(空则 `CreateCommandBuffer()`),`CollectCompleted()` 在每次 `Submit` / `Wait` 时回收已完成的并重置入池。 +- `GetCompletedValue()` 是**方法**而非属性:三端后端(DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`)都是调用语义。 +- CommandBuffer 池化由 queue 自管:`CollectCompleted()` 在每次 `Submit` / `Wait` 时根据时间线值回收实例,重置后入 `available` 复用。 ### 后端时间线对照 @@ -363,11 +361,13 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) | Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore`(核心特性) | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | | Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | -二进制 `VkFence` 仅在 swapchain 内部使用。 +二进制 `VkFence` 仅在 swapchain image acquire 内部使用。 ### 与 SwapChain Present 的关系 -DX12 / Vulkan 当前实现都在 GraphicsQueue 上 Present;新模型不变这条事实。`SwapChain.Present(waits)` 内部仍由 GraphicsQueue 提交 present,跨队列 wait 通过 GraphicsQueue 端的"等待对方时间线"指令落地(详见第 7 节后端映射表)。SwapChain 实现从 `GraphicsContext` 取 GraphicsQueue 引用即可,无需对外暴露。 +`Present` 始终在 GraphicsQueue 上执行。`SwapChain.Present(waits)` 内部由 GraphicsQueue 提交 present,跨队列 wait 通过 GraphicsQueue 端的"等待对方时间线"指令落地(详见第 7 节后端映射表)。SwapChain 实现从 `GraphicsContext` 取 GraphicsQueue 引用即可,无需对外暴露。 + +为简化 barrier,Vulkan 后端所有资源以 concurrent / shared 语义创建(三队列 `VK_SHARING_MODE_CONCURRENT`);跨队列同步完全通过 `CommandSubmission` waits 表达,用户代码从不写 ownership-transfer barrier。 ## 6. CommandBuffer 操作 @@ -450,13 +450,10 @@ public void Upload(Texture destination, TextureSubresourceLayers layers, Offs 要点: -- 字节相关参数统一 `*InBytes` 后缀;多元素入参一律 `ReadOnlySpan`;无 `in` 参数。 -- copy / upload 统一用 `TextureSubresourceLayers`(单 mip + 连续 layer 段),结构本身排除多 mip,无需运行时校验。 -- `ResolveTexture` 是"单点 → 单点",用 `TextureSubresource`。 - `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;内部按 attachment 自动 `Transition` + 自动填 `SetViewports` / `SetScissors`,调用方可在其后再调一次 `SetViewports` / `SetScissors` 覆盖。 - `PushResourceTable` 加 `ShaderStageFlags stages`:DX12 root parameter visibility / Metal argument table 都按 stage 区分;VK 的 set 在 layout 里编译期固定,stages 用于校验匹配。 - `Transition` 只在 `ShaderResource` ↔ `UnorderedAccess` 之间显式调用;render target / 深度模板 / copy / 顶点 / 索引 / CBV / indirect / present 状态由对应操作隐式转换。 -- `MemoryBarrier()`:跨资源 / 跨阶段全局内存同步。CommandBuffer 同时维护"上一次 dispatch 写过的 UAV 集合",下一次 dispatch 若读到同一资源则自动插一个 barrier 作为 fallback。 +- `MemoryBarrier()`:跨资源 / 跨阶段全局内存屏障,由调用方自行决定何时发出。 ### ResourceTable @@ -501,10 +498,8 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture? CurrentDepthStencilTarget { get; } /// - /// 提交 present 到 GraphicsQueue,等待 ; - /// 返回的 submission 表示下一张 backbuffer 已就绪。 - /// 首帧外部传空 span,或用 default(CommandSubmission) 占位 — 后端从 waits 中过滤掉 queue is null 的项。 - /// 注意: 本身不防御 default,只能在 Submit / Present 返回的实例上调用。 + /// 在 GraphicsQueue 上提交 present,等待 ; + /// 返回的 表示下一张 backbuffer 可写。 /// public abstract CommandSubmission Present(params ReadOnlySpan waits); @@ -519,7 +514,6 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G - backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板);不公开 `FrameBuffer`,不公开 image index。 - `Present(...)` 返回 `CommandSubmission`,与 `CommandBuffer.Submit(...)` 形态完全一致;接收任意队列的 wait。 - 当前 image index 在后端内部维护(`VKSwapChain.ImageIndex` / `DXSwapChain.BufferIndex`),公共抽象类不感知。 -- 首帧 image-ready 由调用方 seed(飞行帧策略由调用方决定)。 后端映射: @@ -572,7 +566,7 @@ while (running) } ``` -每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。 +每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面完全对称。 ## 9. 三端 RenderPass / 状态映射 @@ -597,7 +591,6 @@ RDG 由 RHI 用户自行封装,本节只回答:「本 RHI 是否暴露了 RD | 跨队列依赖 | ✅ | `CommandSubmission` waits(`Submit` / `Present` 同形态) | | 队列完成查询 / 等待 | ✅ | `CommandQueue.WaitForIdle()` / `CommandSubmission.Wait()` | | 短生命周期 RenderPass | ✅ | 内联 `BeginRenderPass(colorAttachments, depth)` | -| Pass 间共享绑定 | ✅ | `ResourceTable` 缓存键 = (range, layout) | 结论:RDG 实现方可在不修改 RHI 公共面的前提下完成封装。 @@ -675,7 +668,7 @@ public abstract class GraphicsResource(GraphicsContext context) : DisposableObje } ``` -所有 `GraphicsResource` 子类(`Buffer` / `Texture` / `Sampler` / `CommandQueue` / `CommandBuffer` / `SwapChain` / `Pipeline` / `Shader` / `ResourceTable` / `ResourceLayout`)一律自动实现该接口;后端按 `switch` 实现,不感兴趣的子类返回 0。第三方代码可走接口编程,不必区分 context / resource。 +所有 `GraphicsResource` 子类(`Buffer` / `Texture` / `Sampler` / `CommandQueue` / `CommandBuffer` / `SwapChain` / `Pipeline` / `Shader` / `ResourceTable` / `ResourceLayout`)一律实现该接口;后端按 `switch` 实现,不感兴趣的子类返回 0。第三方代码可走接口编程,不必区分 context / resource。 后端实现示例: @@ -778,7 +771,7 @@ nint device = ctx.GetNativeObject(NativeObjectType.VkDevice); nint queue = ctx.Graphics.GetNativeObject(NativeObjectType.VkQueue); uint family = (uint)ctx.Graphics.GetNativeObject(NativeObjectType.VkQueueFamilyIndex); var grContext = GRContext.MakeVulkan(instance, device, queue, family, /*...*/); -// 不共享 cmd 的库不需要 Begin/End;只需在下一次用到资源前 SetState 同步 layout +// Skia 自管 cmd;与 RHI 共享的资源在下次 RHI 使用前对相应 RHI 句柄调 SetState 同步缓存状态即可。 // === RenderDoc — 仅 device 句柄 === nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); @@ -789,10 +782,7 @@ RenderDocApi.EndFrameCapture(device, IntPtr.Zero); ### 11.5 不做的事 -- 不暴露任何后端类型 / 平台条件属性 - 不提供「导入外部 native 资源」入口(首版);如未来需要:通过 `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` 加入,签名同形态 -- 不允许 `BeginExternalCommands` 嵌套 -- `EndExternalCommands` 不重开 RenderPass / Encoder(调用方按需重开) - 调用方自己保证调用外部库时不在 RenderPass 内(典型互操作都是 compute,天然如此) ## 12. ZenithHelper 受影响成员 @@ -809,3 +799,7 @@ RenderDocApi.EndFrameCapture(device, IntPtr.Zero); - `SubresourceSizeInBytes(TextureDesc, TextureSlice)` —— 改为 `TextureSubresource` 入参重写(仅需 `MipLevel`,其余复用现有 `MipDimensions` + `SizeInBytes`)。 不受影响:纯格式 / 几何计算 helper(`MipDimensions`、`SizeInBytes(PixelFormat,...)`、`ElementFormat` 字节表等)保持不变。 +# RHI 重设计草案(中文临时版) + +> 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 + From 687c76d189f991bd1ebc0b3b2505d7b7d6ffde73 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 23 Apr 2026 11:23:15 +0800 Subject: [PATCH 030/466] Refactor ResourceTable methods for clarity and consistency - Renamed `SetImpl` method to `WriteImpl` across all resource table implementations (DX, Metal, Vulkan). - Updated calls to the renamed method in the base `ResourceTable` class to maintain functionality. --- rhi-redesign.draft.md | 265 +++++------------ rhi-redesign.zh.draft.md | 266 +++++------------- .../Zenith.NET.DirectX12/DXResourceTable.cs | 2 +- sources/Zenith.NET.Metal/MTLResourceTable.cs | 2 +- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 2 +- sources/Zenith.NET/ResourceTable.cs | 4 +- 6 files changed, 131 insertions(+), 410 deletions(-) diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index 78d1bb79..0d2bc028 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -2,25 +2,15 @@ > Working document for the redesign discussion. Not for commit. The Chinese mirror lives in `rhi-redesign.zh.draft.md`. -## 1. Goals and Non-Goals - -**Goals** +## 1. Goals - Small, uniform public API surface that maps 1:1 to DirectX 12 / Vulkan 1.4 / Metal 4. - Short frame-loop code with no managed allocations on the hot path. - Cross-queue synchronization (Graphics / Compute / Copy) as a first-class primitive. -- Explicit resource state transitions as a stable foundation for a future RDG. +- Explicit resource state transitions between `ShaderResource` and `UnorderedAccess`; every other transition is implicit from the operation. - Inline render passes; no long-lived `FrameBuffer`. - Subresource / sub-range information expressed at the **call site** as a value type, not as a long-lived `View` object. - -**Non-Goals** - -- Full hazard tracking inside the RHI (only "current state" is cached, used to compute the source side of explicit transitions). -- One synchronization object per submission. -- Using `WaitForIdle` as the primary synchronization mechanism. -- Implementing the RDG, bindless, or descriptor-buffer paths in this iteration. -- Reworking `ResourceTable` / `ResourceLayout` / `Sampler` / `Pipeline` / shader IO outside of what is described here. -- Exposing per-aspect (color/depth/stencil) addressing, sub-range transitions, or "first-frame image ready" waiting on the public surface. +- One `ResourceTable` per pipeline (no secondary descriptor sets), matching the Metal 4 argument-table model and the other two backends' root/descriptor models at 1:1. ## 2. API-Surface Conventions @@ -236,15 +226,9 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph } ``` -**View caches** (per-backend, on each `Texture` / `Buffer`): - -- `Dictionary` — SRV / UAV -- `Dictionary` — RTV / DSV -- `Dictionary` — typed buffer view - -Lazy-allocated by value-type key, lifetime tied to the parent. The same `(resource, range)` pair across all call sites shares one backend object. +**View caches** (per-backend, on each `Texture` / `Buffer`): the design permits backends to lazily cache the native view objects keyed by the value types in §3, but no part of this is on the public surface. -**State tracking:** every `Texture` / `Buffer` keeps its current `TransitionState` internally; the initial state is set by the backend default. The first explicit `Transition` synthesizes the from→to barrier from that cached value. +**State tracking:** every `Texture` / `Buffer` carries its current `TransitionState`; the initial value is set by the backend at creation. The first explicit `Transition` synthesizes the from→to barrier against that cached value. Only `ShaderResource` ↔ `UnorderedAccess` is user-visible; all other transitions (render-target / depth-stencil / copy / vertex / index / CBV / indirect / present) are driven implicitly by the operation issuing them. **Removed from the framework (these all exist today):** `BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `BufferViewType` / `IBindableResource` / `TextureSlice` (incl. `Face`) / `TextureOffset` / `TextureExtent` / `FrameBuffer` / `FrameBufferDesc` / `FrameBufferAttachment` / `ClearValue` / `ClearValues` (static factory) / `ClearFlags` / `GraphicsContext.CreateBufferView` / `CreateTextureView` / `CreateFrameBuffer`. The `Output` type itself stays — it remains `GraphicsPipelineDesc.Output`; only the `FrameBuffer.Output` use site disappears. @@ -255,81 +239,13 @@ Each `CommandQueue` owns one monotonic completion timeline. Each `Submit` advanc ```csharp public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) { - private readonly Lock @lock = new(); - private readonly Queue available = []; - private readonly Queue execution = []; - - private ulong lastSignaledValue; - public CommandQueueType Type { get; } = type; - public CommandBuffer CommandBuffer() - { - using Lock.Scope _ = @lock.EnterScope(); - - CollectCompleted(); - - CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); - - commandBuffer.Begin(); - - return commandBuffer; - } - - /// Idempotent. Returns immediately when the queue is already drained. - public void WaitForIdle() - { - Wait(lastSignaledValue); - } - - internal void Wait(ulong value) - { - using Lock.Scope _ = @lock.EnterScope(); - - if (GetCompletedValue() >= value) - { - CollectCompleted(); - - return; - } - - WaitCore(value); - - CollectCompleted(); - } - - internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) - { - using Lock.Scope _ = @lock.EnterScope(); - - CollectCompleted(); - - commandBuffer.End(); - - lastSignaledValue++; - - SubmitCore(commandBuffer, waits, lastSignaledValue); - - execution.Enqueue(new(commandBuffer, lastSignaledValue)); - - return new(this, lastSignaledValue); - } - - private void CollectCompleted() - { - ulong value = GetCompletedValue(); - - while (execution.Count > 0 && execution.Peek().Value <= value) - { - CommandBuffer commandBuffer = execution.Dequeue().CommandBuffer; - - commandBuffer.Reset(); + /// Acquires a recording-ready CommandBuffer, recycling a retired one when available. + public CommandBuffer CommandBuffer(); - available.Enqueue(commandBuffer); - } - } - - protected abstract CommandBuffer CreateCommandBuffer(); + /// Waits until every submission issued on this queue has completed. Idempotent. + public void WaitForIdle(); protected abstract ulong GetCompletedValue(); @@ -337,22 +253,19 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); - private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); + protected abstract CommandBuffer CreateCommandBuffer(); } public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { - public CommandSubmission Submit(params ReadOnlySpan waits) - { - return queue.Submit(this, waits); - } + /// Closes recording and enqueues this buffer on its owning queue. + public CommandSubmission Submit(params ReadOnlySpan waits); } ``` -Notes: - - `GetCompletedValue()` is a **method**, not a property: all three backends (DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`) are call-shaped. -- Command buffer pooling lives in the queue: `CollectCompleted()` runs at every `Submit` / `Wait` and recycles buffers whose timeline value has retired. +- `CommandBuffer` pooling is entirely a queue-internal detail; the public surface only offers `CommandBuffer()` and `Submit(...)`. +- Three queues are exposed on `GraphicsContext`: `Graphics` / `Compute` / `Copy`. `Present` always runs on `Graphics`; `SwapChain` pulls that queue reference from the context and never exposes it on its own surface. ### Backend Timeline Mapping @@ -362,13 +275,7 @@ Notes: | Vulkan 1.4 | one timeline `VkSemaphore` per queue (core feature) | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | | Metal 4 | one `MTLSharedEvent` per queue | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | -The internal binary `VkFence` is retained for swapchain image acquire only. - -### Interaction with SwapChain Present - -`Present` runs on the GraphicsQueue. `SwapChain.Present(waits)` submits the present from the GraphicsQueue; cross-queue waits are honored on the GraphicsQueue side via "wait other queue's timeline" instructions (see Section 7 mapping table). The SwapChain implementation pulls the GraphicsQueue from `GraphicsContext`; the queue reference is never exposed on `SwapChain`'s public surface. - -To keep barriers simple, all resources are created with concurrent / shared semantics on Vulkan (`VK_SHARING_MODE_CONCURRENT` across the three queues). Cross-queue synchronization is therefore expressed entirely through `CommandSubmission` waits; user code never writes Vulkan ownership-transfer barriers. +All three backends support this timeline model natively; the table is a feasibility record, not a prescription. ## 6. CommandBuffer Operations @@ -398,7 +305,7 @@ public void SetScissors(ReadOnlySpan scissors); public void SetPipeline(Pipeline pipeline); -public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); +public void PushResourceTable(ResourceTable table); // === Vertex / Index === @@ -452,7 +359,8 @@ public void Upload(Texture destination, TextureSubresourceLayers layers, Offs Notes: - `BeginRenderPass` accepts an attachment span; pass `null` explicitly for no depth. The implementation auto-`Transition`s the attachments and auto-fills `SetViewports` / `SetScissors` from attachment dimensions; the caller may issue `SetViewports` / `SetScissors` afterward to override. -- `PushResourceTable` takes a `ShaderStageFlags stages` argument: DX12 root-parameter visibility and Metal argument tables are stage-scoped. On Vulkan the descriptor set's stage mask is fixed at layout creation time, so `stages` is used to validate the call against the layout. +- `PushResourceTable` takes no `stages` argument: stage information is carried per-binding in `ResourceTable.Layout` (DX12 root-parameter visibility and Metal argument-table placement are derived from it; the VK set's stage mask is fixed at layout creation time). Only one table per pipeline is supported; there is no `setIndex`. +- **Push-snapshot semantics**: `PushResourceTable` snapshots the current contents of `table` into the cmd buffer at the call site; subsequent `Write`s to that `table` do not affect already-pushed bindings. All three backends provide this natively (DX12 descriptor copy on bind, VK `vkCmdPushDescriptorSet`, Metal 4 `setArgumentTable:`), so the same `ResourceTable` can be repeatedly `Write` + `Push`ed within a frame. - `Transition` is only called explicitly between `ShaderResource` and `UnorderedAccess`. Render-target / depth-stencil / copy / vertex / index / CBV / indirect / present states are transitioned implicitly by the corresponding operation. - `MemoryBarrier()` is a global cross-resource / cross-stage memory barrier; the caller decides when to issue it. @@ -513,16 +421,10 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G Design points: - Backbuffers are exposed as plain `Texture`s (color + optional depth-stencil). No `FrameBuffer`, no image index. -- `Present(...)` returns a `CommandSubmission` describing when the next backbuffer is writable; it accepts waits from any queue. -- The current backbuffer index lives **only** inside the backend (today: `VKSwapChain.ImageIndex`, `DXSwapChain.BufferIndex`). Public callers never need it. +- `Present(...)` returns a `CommandSubmission` describing when the next backbuffer is writable; it accepts waits from any queue, symmetrically with `CommandBuffer.Submit(...)`. +- The current backbuffer index is entirely a backend concern and is never on the public surface. -Backend mapping: - -| Backend | `Present(waits)` implementation | "Next-frame ready" signal | -|---|---|---| -| DX12 | for each `wait`, call `graphicsQueue.Wait(otherFence, otherValue)`, then `IDXGISwapChain3::Present` | frame-latency event wrapped as a `CommandSubmission` whose value is taken from the graphics queue timeline | -| Vulkan 1.4 | one bridging `vkQueueSubmit2(graphicsQueue, waits=timeline values, signal=renderFinished[N])` → `vkQueuePresentKHR(graphicsQueue, wait=renderFinished[N])` | the next frame's `vkAcquireNextImageKHR` returns binary `imageAvailable[N]`; another graphics-queue "wait binary, signal timeline" bridging submit wraps it as a `CommandSubmission` | -| Metal 4 | translate each wait into `commandBuffer.EncodeWaitForEvent(...)` → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()` | the next drawable's availability event wrapped as a `CommandSubmission` | +All three backends support the shape `Present(waits) → CommandSubmission` natively: DX12 `IDXGISwapChain3::Present` with `graphicsQueue.Wait(fence, value)` for each wait; Vulkan 1.4 a bridging `vkQueueSubmit2` that translates timeline waits into a binary `renderFinished` semaphore consumed by `vkQueuePresentKHR`; Metal 4 `commandBuffer.EncodeWaitForEvent(...)` → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()`. ## 8. Frame Loop @@ -569,15 +471,17 @@ while (running) Every step has the same shape: take a set of `CommandSubmission` waits, return a `CommandSubmission`. `Submit` and `Present` are symmetric on the API surface. -## 9. Three-Backend RenderPass / Barrier Mapping +## 9. Three-Backend Feasibility Matrix + +Every public primitive in §3–§7 has a native equivalent on all three backends: | Concept | DX12 | Vulkan 1.4 | Metal 4 | |---|---|---|---| -| Begin / End RenderPass | `OMSetRenderTargets`, or `BeginRenderPass(RENDER_PASS_RENDER_TARGET_DESC)` / `EndRenderPass` | `vkCmdBeginRendering(VkRenderingInfo)` / `vkCmdEndRendering` | `commandBuffer.RenderCommandEncoder(MTLRenderPassDescriptor)` / `endEncoding` | -| LoadAction | `BeginningAccessType` (Discard / Preserve / Clear) | `loadOp` (DONT_CARE / LOAD / CLEAR) | `MTLLoadAction` | -| StoreAction | `EndingAccessType` (Discard / Preserve / Resolve) | `storeOp` (DONT_CARE / STORE / NONE) + `resolveMode` | `MTLStoreAction` | -| Resolve | `EndingAccessResolveSubresourceParameters` | `pResolveAttachments` + `resolveMode` | `MTLRenderPassDescriptor.resolveTexture` | -| Explicit Transition | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2(VkImageMemoryBarrier2 / VkBufferMemoryBarrier2)` | `commandEncoder.MemoryBarrier(scope, after, before)` | +| Inline RenderPass | `ID3D12GraphicsCommandList4::BeginRenderPass` / `EndRenderPass` | `vkCmdBeginRendering` / `vkCmdEndRendering` | `MTL4CommandBuffer::MakeRenderCommandEncoder` / `endEncoding` | +| LoadAction | `RenderPassBeginningAccessType` | `VkAttachmentLoadOp` | `MTLLoadAction` | +| StoreAction (incl. Resolve) | `RenderPassEndingAccessType` | `VkAttachmentStoreOp` + `pResolveAttachments` + `resolveMode` | `MTLStoreAction` + `resolveTexture` | +| Explicit Transition (SRV ↔ UAV) | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2` (`VkImageMemoryBarrier2` / `VkBufferMemoryBarrier2`) | `MTL4ComputeCommandEncoder.BarrierAfterEncoderStages` | +| Global `MemoryBarrier()` | `ResourceBarrier(UAV)` global | `vkCmdPipelineBarrier2` with `VK_ACCESS_2_MEMORY_READ/WRITE_BIT` | `BarrierAfterEncoderStages` with all stages | | Aspect inference source | format → plane index | `Texture.Format` → `VkImageAspectFlags` | format → automatic | ## 10. RDG Interface Self-Check @@ -600,9 +504,21 @@ Conclusion: an RDG implementation can be built without any further changes to th For third-party libraries that need backend-native handles: Skia / DLSS / FSR / RenderDoc / tooling. Design principles: - The public surface exposes only `nint` plus an enum; no backend types and no platform-conditional properties. -- The RHI does not perform work on behalf of the external library; the caller follows the documented protocol for state synchronization. +- The RHI does not perform work on behalf of the external library, and provides no escape hatch for external code to mutate the RHI's cached resource state. - The interop API is a side-effect of state the RHI already maintains internally — zero extra runtime cost. +**State-preservation contract**: any resource exposed to an external library through `GetNativeObject(...)` must be returned in the underlying state matching its current `TransitionState`: + +- DX12: `ShaderResource` → `D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE`; `UnorderedAccess` → `D3D12_RESOURCE_STATE_UNORDERED_ACCESS`. +- Vulkan: `ShaderResource` → `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`; `UnorderedAccess` → `VK_IMAGE_LAYOUT_GENERAL`. +- Metal 4: state is driver-managed; no caller cooperation required. + +Compute post-processing libraries (DLSS / XeSS / FFX-FSR / NRD) honour this contract by design (state at entry == state at exit). Libraries that actively rewrite layout (Skia and similar) are not suitable for the shared-cmd path; instead use: + +1. **Texture copy** (recommended): let the external library render onto its own private resource, then copy the output into an RHI texture (a future import entry point may avoid the copy; see § 11.5). +2. **Dedicated swapchain**: pure-UI applications can hand the entire swapchain to the external library; the RHI does not participate in that swapchain's rendering. +3. **Configure the library to restore layout**: e.g. Skia's `GrBackendTexture::setVkImageLayout` can request the layout to be restored to the RHI's expected value after every flush. + ### 11.1 NativeObjectType ```csharp @@ -693,88 +609,48 @@ public override nint GetNativeObject(NativeObjectType type) => type switch public override nint GetNativeObject(NativeObjectType type) => 0; ``` -### 11.3 CommandBuffer Interop Verbs +### 11.3 Interop Calling Convention -```csharp -public abstract class CommandBuffer -{ - /// - /// Enter the interop scope: end the current RenderPass / Encoder (if any), clear the - /// RHI's internal binding caches (PSO / descriptors / vertex+index buffers / viewports / scissors). - /// Does not write any command into the cmd (Vulkan exception: one vkCmdEndRendering call). - /// After entering, use GetNativeObject(...) to hand native handles to the external library. - /// Begin / End must be paired; nesting is forbidden. - /// - public abstract void BeginExternalCommands(); - - /// - /// Leave the interop scope: emit one global MemoryBarrier to isolate the external library's - /// writes from subsequent RHI commands. Does not re-open any RenderPass / Encoder; the caller - /// reissues BeginRenderPass / SetPipeline as needed. - /// - public abstract void EndExternalCommands(); +No dedicated `BeginExternalCommands` / `EndExternalCommands` verbs. Call ordering and RHI cache interaction are concerns the caller resolves at development time, not at the runtime API boundary. The caller guarantees: - /// - /// Synchronize the RHI's cached resource state to newState. Writes neither cmd nor barrier. - /// The next Transition uses this value to compute from → to. Callable inside or outside the External scope. - /// - public abstract void SetState(Texture texture, TransitionState newState); - - public abstract void SetState(Buffer buffer, TransitionState newState); -} -``` - -Backend implementation notes: - -- `BeginExternalCommands`: - - Common: clear `cachedPipeline` / `cachedRootSignature` (DX12) / `cachedDescriptorSets/Heaps` / `cachedVertexBuffers` / `cachedIndexBuffer` / `cachedViewports` / `cachedScissors`. - - VK: if currently inside a dynamic rendering scope, call `vkCmdEndRendering` once. - - DX12 / Metal: only clear fields; do not write cmd (DX12 must NOT call `ID3D12GraphicsCommandList::ClearState`, which would actually emit a command). - - Set `inExternalScope = true`; throw if already true. -- `EndExternalCommands`: - - Call the internal `MemoryBarrier()` once (VK = `vkCmdPipelineBarrier2(ALL → ALL, MEMORY_READ|WRITE → MEMORY_READ|WRITE)`; DX12 = `ResourceBarrier(UAV, nullptr)` or `D3D12_GLOBAL_BARRIER`; Metal = `MemoryBarrier(scope=AllResources, after=AllStages, before=AllStages)`). - - Set `inExternalScope = false`; throw if already false. - - Do not re-open RenderPass / Encoder. -- `SetState`: write `texture.CurrentState = newState` / `buffer.CurrentState = newState` directly; never touches cmd. +1. The external library is invoked outside any `BeginRenderPass` / `EndRenderPass` (typical interop is compute, so this holds naturally). +2. If the call ordering would let the RHI's cached PSO / descriptors / vertex+index buffers / viewports / scissors collide with the external library, the caller re-issues `SetPipeline` / `PushResourceTable` etc. after the external library returns. +3. **Issue one `cmd.MemoryBarrier()`** (see § 6) after the external library returns to isolate its writes from subsequent RHI commands. +Combined with the state-preservation contract from § 11, interop relies only on `GetNativeObject` and `MemoryBarrier`; no dedicated API is required. ### 11.4 Caller Templates ```csharp -// === DLSS (DX12) — shared cmd, state preserved === +// === DLSS (DX12) — shared cmd, follows the state-preservation contract === cmd.Transition(colorIn, TransitionState.ShaderResource); cmd.Transition(colorOut, TransitionState.UnorderedAccess); -cmd.BeginExternalCommands(); DLSS.Evaluate( cmd.GetNativeObject(NativeObjectType.D3D12GraphicsCommandList), colorIn.GetNativeObject(NativeObjectType.D3D12Resource), colorOut.GetNativeObject(NativeObjectType.D3D12Resource)); -cmd.EndExternalCommands(); // auto barrier isolates DLSS writes from subsequent RHI commands +// On return: colorIn stays NON_PIXEL_SHADER_RESOURCE, colorOut stays UNORDERED_ACCESS. +cmd.MemoryBarrier(); // isolate DLSS writes from later RHI commands +cmd.Transition(colorOut, TransitionState.ShaderResource); -cmd.BeginRenderPass(...); - -// === FSR/FFX (Vulkan) — shared cmd, external library mutates state === +// === FSR/FFX (Vulkan) — shared cmd, same contract === cmd.Transition(colorIn, TransitionState.ShaderResource); cmd.Transition(colorOut, TransitionState.UnorderedAccess); -cmd.BeginExternalCommands(); Ffx.Fsr2Dispatch( cmd.GetNativeObject(NativeObjectType.VkCommandBuffer), colorIn.GetNativeObject(NativeObjectType.VkImage), colorOut.GetNativeObject(NativeObjectType.VkImage)); -cmd.SetState(colorOut, TransitionState.ShaderResource); -cmd.EndExternalCommands(); - -cmd.BeginRenderPass(...); +cmd.MemoryBarrier(); +cmd.Transition(colorOut, TransitionState.ShaderResource); -// === Skia (Vulkan) — not sharing cmd, library submits on its own === -nint instance = ctx.GetNativeObject(NativeObjectType.VkInstance); -nint device = ctx.GetNativeObject(NativeObjectType.VkDevice); -nint queue = ctx.Graphics.GetNativeObject(NativeObjectType.VkQueue); -uint family = (uint)ctx.Graphics.GetNativeObject(NativeObjectType.VkQueueFamilyIndex); -var grContext = GRContext.MakeVulkan(instance, device, queue, family, /*...*/); -// Skia owns its own command buffers; for any resource shared with the RHI, call SetState on the -// matching RHI handle before the next RHI use to keep the cached state in sync. +// === Skia (Vulkan) — texture-copy path === +// Skia actively rewrites layout, so it does not fit the shared-cmd contract. +// Let Skia render onto its own private VkImage, then copy into an RHI-owned +// Texture (TransferDst | Sampled) from inside Skia's own command buffer; have +// Skia signal a timeline semaphore on flush and feed it into the next RHI +// Submit as a CommandSubmission wait. The exact SkiaSharp surface for sharing +// a VkImage and the flush-signal is volatile, so the integration stays caller-side. // === RenderDoc — device handle only === nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); @@ -786,19 +662,4 @@ RenderDocApi.EndFrameCapture(device, IntPtr.Zero); ### 11.5 Out of Scope - No "import external native resource" entry point in this iteration; if needed later, add `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` with the same shape. -- The caller guarantees that the external library is invoked outside any RenderPass (typical interop is compute-only, so this holds naturally). - -## 12. ZenithHelper Members Affected - -The following functions in [ZenithHelper](sources/Zenith.NET/ZenithHelper.cs) depend on the now-removed `TextureSlice` (with `Face`) / `TextureViewDesc`, or on the "cube faces multiply implicitly into subresource counts" model. Under the new design they no longer apply and must be removed or rewritten against the new types: - -- `FaceCount(TextureDesc)` — cubes / cube arrays now express their faces directly as 6-layer arrays. `ArrayLayers` already counts faces; there is no separate face axis. -- `FaceIndex(TextureDesc, TextureSlice)` — depends on `TextureSlice.Face`. -- `FlattenArrayLayerCount(TextureDesc)` — the "fold face into array layer" step is no longer needed; callers use `desc.ArrayLayers` directly. -- `FlattenArrayLayerIndex(TextureDesc, TextureSlice)` — same. -- `FlattenArrayLayerRange(TextureViewDesc)` — `TextureViewDesc` is removed. -- `SubresourceCount(TextureDesc)` — new model = `MipLevels * ArrayLayers` (DX12 plane is handled implicitly by format). Inline at the call site. -- `SubresourceIndex(TextureDesc, TextureSlice)` — replace with inline `MipLevel + ArrayLayer * MipLevels` over `TextureSubresource`. -- `SubresourceSizeInBytes(TextureDesc, TextureSlice)` — rewrite to take a `TextureSubresource` (only `MipLevel` is used; the rest reuses the existing `MipDimensions` + `SizeInBytes` helpers). - -Unaffected: pure format / geometry helpers (`MipDimensions`, `SizeInBytes(PixelFormat, ...)`, `ElementFormat` byte-size table, etc.) keep working as-is. +- No dedicated interop scope verbs (`BeginExternalCommands` / `EndExternalCommands`); call ordering and cache interaction are caller-side concerns, with a single `MemoryBarrier()` at runtime (see § 11.3). diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index 790cd63b..fcda140c 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -2,25 +2,15 @@ > 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 -## 1. 目标与非目标 - -**目标** +## 1. 目标 - 公共 API 表面小、调用形态统一,与 DirectX 12 / Vulkan 1.4 / Metal 4 命令模型 1:1 对应 - 帧循环代码短,热路径零托管分配 - 跨队列同步(Graphics / Compute / Copy)一等公民 -- 显式资源状态转换,作为未来 RDG 的稳定基座 +- 显式资源状态转换仅限 `ShaderResource` ↔ `UnorderedAccess`,其余转换均由对应操作隐式完成 - 内联 RenderPass,去掉长生命周期 `FrameBuffer` - 子范围 / 子资源信息在**调用现场**以值类型表达,不再有公共 `View` 对象 - -**非目标** - -- RHI 内不做完整 hazard tracking(仅缓存"当前状态"用于计算显式 transition 的 from 端) -- 一次 submit 一个同步对象 -- 把 `WaitForIdle` 当作主同步手段 -- 本轮不实现 RDG、bindless、descriptor buffer -- 不重构 `ResourceTable` / `ResourceLayout` / `Sampler` / `Pipeline` / shader IO 之外的部分 -- 不在公共面暴露 per-aspect (color/depth/stencil) 寻址、子范围 transition、首帧 image-ready 等待 +- 每个 pipeline 一张 `ResourceTable`(不设次级 descriptor set),对齐 Metal 4 argument-table 模型与另两端的 root/descriptor 模型 ## 2. 命名与代码风格约定 @@ -235,15 +225,9 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph } ``` -**View 缓存**(每个后端 `Texture` / `Buffer` 内部): +**View 缓存**(每个后端 `Texture` / `Buffer` 内部):设计允许后端按 §3 的值类型做 key 懒加载缓存原生 view 对象,但此缓存完全属于后端内部,不在公共面出现。 -- `Dictionary` —— SRV / UAV -- `Dictionary` —— RTV / DSV -- `Dictionary` —— typed buffer view - -按值类型 key 懒加载,与父资源同生命周期;同一 (resource, range) 组合在所有调用方共享一个后端对象。 - -**资源状态跟踪**:每个 `Texture` / `Buffer` 内部跟踪当前 `TransitionState`,初值由后端默认写入;首次显式 `Transition` 时根据当前值生成 from→to barrier。 +**资源状态跟踪**:每个 `Texture` / `Buffer` 持有当前 `TransitionState`,初值由后端在创建时写入。首次显式 `Transition` 根据该缓存值生成 from→to barrier。仅 `ShaderResource` ↔ `UnorderedAccess` 向用户可见;render target / 深度模板 / copy / 顶点 / 索引 / CBV / indirect / present 等状态由对应操作隐式转换。 **确认从框架删除(这些当前真实存在)**:`BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `BufferViewType` / `IBindableResource` / `TextureSlice`(含 `Face`)/ `TextureOffset` / `TextureExtent` / `FrameBuffer` / `FrameBufferDesc` / `FrameBufferAttachment` / `ClearValue` / `ClearValues` 静态类 / `ClearFlags` 枚举 / `GraphicsContext.CreateBufferView` / `CreateTextureView` / `CreateFrameBuffer`。`Output` 类型保留并继续作为 `GraphicsPipelineDesc.Output`,仅 `FrameBuffer.Output` 一处使用消失。 @@ -254,81 +238,13 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph ```csharp public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) { - private readonly Lock @lock = new(); - private readonly Queue available = []; - private readonly Queue execution = []; - - private ulong lastSignaledValue; - public CommandQueueType Type { get; } = type; - public CommandBuffer CommandBuffer() - { - using Lock.Scope _ = @lock.EnterScope(); - - CollectCompleted(); - - CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); - - commandBuffer.Begin(); + /// 获取一个可录制的 CommandBuffer,必要时复用已退役实例。 + public CommandBuffer CommandBuffer(); - return commandBuffer; - } - - /// 等待此 queue 上所有已提交的命令完成。幂等。 - public void WaitForIdle() - { - Wait(lastSignaledValue); - } - - internal void Wait(ulong value) - { - using Lock.Scope _ = @lock.EnterScope(); - - if (GetCompletedValue() >= value) - { - CollectCompleted(); - - return; - } - - WaitCore(value); - - CollectCompleted(); - } - - internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) - { - using Lock.Scope _ = @lock.EnterScope(); - - CollectCompleted(); - - commandBuffer.End(); - - lastSignaledValue++; - - SubmitCore(commandBuffer, waits, lastSignaledValue); - - execution.Enqueue(new(commandBuffer, lastSignaledValue)); - - return new(this, lastSignaledValue); - } - - private void CollectCompleted() - { - ulong value = GetCompletedValue(); - - while (execution.Count > 0 && execution.Peek().Value <= value) - { - CommandBuffer commandBuffer = execution.Dequeue().CommandBuffer; - - commandBuffer.Reset(); - - available.Enqueue(commandBuffer); - } - } - - protected abstract CommandBuffer CreateCommandBuffer(); + /// 等待此 queue 上所有已提交命令完成。幂等。 + public void WaitForIdle(); protected abstract ulong GetCompletedValue(); @@ -336,22 +252,19 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); - private readonly record struct InFlightCommandBuffer(CommandBuffer CommandBuffer, ulong Value); + protected abstract CommandBuffer CreateCommandBuffer(); } public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { - public CommandSubmission Submit(params ReadOnlySpan waits) - { - return queue.Submit(this, waits); - } + /// 结束录制并在其所属 queue 上入队。 + public CommandSubmission Submit(params ReadOnlySpan waits); } ``` -要点: - - `GetCompletedValue()` 是**方法**而非属性:三端后端(DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`)都是调用语义。 -- CommandBuffer 池化由 queue 自管:`CollectCompleted()` 在每次 `Submit` / `Wait` 时根据时间线值回收实例,重置后入 `available` 复用。 +- CommandBuffer 池化完全是 queue 的内部实现细节;公共面只暴露 `CommandBuffer()` 与 `Submit(...)`。 +- `GraphicsContext` 暴露三条队列:`Graphics` / `Compute` / `Copy`。`Present` 始终在 `Graphics` 上执行;`SwapChain` 从 context 拿到该队列引用,自身公共面不暴露队列。 ### 后端时间线对照 @@ -361,13 +274,7 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) | Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore`(核心特性) | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | | Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | -二进制 `VkFence` 仅在 swapchain image acquire 内部使用。 - -### 与 SwapChain Present 的关系 - -`Present` 始终在 GraphicsQueue 上执行。`SwapChain.Present(waits)` 内部由 GraphicsQueue 提交 present,跨队列 wait 通过 GraphicsQueue 端的"等待对方时间线"指令落地(详见第 7 节后端映射表)。SwapChain 实现从 `GraphicsContext` 取 GraphicsQueue 引用即可,无需对外暴露。 - -为简化 barrier,Vulkan 后端所有资源以 concurrent / shared 语义创建(三队列 `VK_SHARING_MODE_CONCURRENT`);跨队列同步完全通过 `CommandSubmission` waits 表达,用户代码从不写 ownership-transfer barrier。 +三端均原生支持上述 timeline 模型;本表仅用于可行性存档,并非实现规定。 ## 6. CommandBuffer 操作 @@ -397,7 +304,7 @@ public void SetScissors(ReadOnlySpan scissors); public void SetPipeline(Pipeline pipeline); -public void PushResourceTable(ShaderStageFlags stages, ResourceTable table); +public void PushResourceTable(ResourceTable table); // === 顶点 / 索引 === @@ -451,7 +358,8 @@ public void Upload(Texture destination, TextureSubresourceLayers layers, Offs 要点: - `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;内部按 attachment 自动 `Transition` + 自动填 `SetViewports` / `SetScissors`,调用方可在其后再调一次 `SetViewports` / `SetScissors` 覆盖。 -- `PushResourceTable` 加 `ShaderStageFlags stages`:DX12 root parameter visibility / Metal argument table 都按 stage 区分;VK 的 set 在 layout 里编译期固定,stages 用于校验匹配。 +- `PushResourceTable` 不接受 stages 参数:stages 信息由 `ResourceTable.Layout` 的每个 binding 自带(DX12 root parameter visibility / Metal argument table 写入位置据此推导;VK 的 set 在 layout 里编译期固定)。每个 pipeline 仅支持一张 table,无 setIndex。 +- **Push-snapshot 语义**:`PushResourceTable` 在调用现场把 `table` 的当前内容快照进 cmd buffer;之后对该 `table` 的 `Write` 不影响已 push 的绑定。三端均原生支持此语义(DX12 绑定时拷贝 descriptor、VK `vkCmdPushDescriptorSet`、Metal 4 `setArgumentTable:`),因此同一个 `ResourceTable` 可以在帧内反复 `Write` + `Push`。 - `Transition` 只在 `ShaderResource` ↔ `UnorderedAccess` 之间显式调用;render target / 深度模板 / copy / 顶点 / 索引 / CBV / indirect / present 状态由对应操作隐式转换。 - `MemoryBarrier()`:跨资源 / 跨阶段全局内存屏障,由调用方自行决定何时发出。 @@ -512,16 +420,10 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G 设计要点: - backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板);不公开 `FrameBuffer`,不公开 image index。 -- `Present(...)` 返回 `CommandSubmission`,与 `CommandBuffer.Submit(...)` 形态完全一致;接收任意队列的 wait。 -- 当前 image index 在后端内部维护(`VKSwapChain.ImageIndex` / `DXSwapChain.BufferIndex`),公共抽象类不感知。 +- `Present(...)` 返回 `CommandSubmission`,与 `CommandBuffer.Submit(...)` 形态对称;接收任意队列的 wait。 +- 当前 image index 完全是后端事务,不在公共面出现。 -后端映射: - -| 后端 | `Present(waits)` 实现 | "下一帧就绪" 信号 | -|---|---|---| -| DX12 | 对每个 wait 调 `graphicsQueue.Wait(otherFence, otherValue)` → `IDXGISwapChain3::Present` | frame latency event 包装为 `CommandSubmission`(值取自 graphics queue 时间线) | -| Vulkan 1.4 | 一次桥接 submit `vkQueueSubmit2(graphicsQueue, waits=timeline values, signal=renderFinished[N])` → `vkQueuePresentKHR(graphicsQueue, wait=renderFinished[N])` | 下一帧 `vkAcquireNextImageKHR` 返回的 binary `imageAvailable[N]`,再用一次 graphics queue 的 "wait binary, signal timeline" 桥接 submit 包装为 `CommandSubmission` | -| Metal 4 | `commandBuffer.EncodeWaitForEvent(...)` 转译每个 wait → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()` | 下一个 drawable 的可用事件包装为 `CommandSubmission` | +三端均原生支持 `Present(waits) → CommandSubmission` 的形态:DX12 以 `IDXGISwapChain3::Present` 搭配每个 wait 的 `graphicsQueue.Wait(fence, value)`;Vulkan 1.4 用一次桥接 `vkQueueSubmit2`,把 timeline waits 翻译成供 `vkQueuePresentKHR` 消费的 binary `renderFinished` semaphore;Metal 4 为 `commandBuffer.EncodeWaitForEvent(...)` → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()`。 ## 8. 帧循环 @@ -568,15 +470,17 @@ while (running) 每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面完全对称。 -## 9. 三端 RenderPass / 状态映射 +## 9. 三端可行性对照 + +§3–§7 的每个公共原语,三端均有原生对应: | 概念 | DX12 | Vulkan 1.4 | Metal 4 | |---|---|---|---| -| Begin / End RenderPass | `OMSetRenderTargets` 或 `BeginRenderPass(RENDER_PASS_RENDER_TARGET_DESC)` / `EndRenderPass` | `vkCmdBeginRendering(VkRenderingInfo)` / `vkCmdEndRendering` | `commandBuffer.RenderCommandEncoder(MTLRenderPassDescriptor)` / `endEncoding` | -| LoadAction | `BeginningAccessType` (Discard / Preserve / Clear) | `loadOp` (DONT_CARE / LOAD / CLEAR) | `MTLLoadAction` | -| StoreAction | `EndingAccessType` (Discard / Preserve / Resolve) | `storeOp` (DONT_CARE / STORE / NONE) + `resolveMode` | `MTLStoreAction` | -| Resolve | `EndingAccessResolveSubresourceParameters` | `pResolveAttachments` + `resolveMode` | `MTLRenderPassDescriptor.resolveTexture` | -| 显式 Transition | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2(VkImageMemoryBarrier2 / VkBufferMemoryBarrier2)` | `commandEncoder.MemoryBarrier(scope, after, before)` | +| 内联 RenderPass | `ID3D12GraphicsCommandList4::BeginRenderPass` / `EndRenderPass` | `vkCmdBeginRendering` / `vkCmdEndRendering` | `MTL4CommandBuffer::MakeRenderCommandEncoder` / `endEncoding` | +| LoadAction | `RenderPassBeginningAccessType` | `VkAttachmentLoadOp` | `MTLLoadAction` | +| StoreAction(含 Resolve) | `RenderPassEndingAccessType` | `VkAttachmentStoreOp` + `pResolveAttachments` + `resolveMode` | `MTLStoreAction` + `resolveTexture` | +| 显式 Transition(SRV ↔ UAV) | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2`(`VkImageMemoryBarrier2` / `VkBufferMemoryBarrier2`) | `MTL4ComputeCommandEncoder.BarrierAfterEncoderStages` | +| 全局 `MemoryBarrier()` | `ResourceBarrier(UAV)` 全局 | `vkCmdPipelineBarrier2` 配 `VK_ACCESS_2_MEMORY_READ/WRITE_BIT` | `BarrierAfterEncoderStages` 覆盖全阶段 | | Aspect 推断源 | format → plane index | `Texture.Format` → `VkImageAspectFlags` | format → 自动 | ## 10. RDG 接口自检 @@ -599,9 +503,21 @@ RDG 由 RHI 用户自行封装,本节只回答:「本 RHI 是否暴露了 RD 面向 Skia / DLSS / FSR / RenderDoc / 工具链等需要拿后端原生句柄的场景。设计原则: - 公共面只暴露 `nint` + 一组枚举;不出现任何后端类型 / 平台条件属性 -- RHI 不替外部库做事;调用方按既有模板自管 state 协议 +- RHI 不替外部库做事,也不提供任何 escape hatch 让外部代码修改 RHI 的资源状态缓存 - 互操作 API 是 RHI 内部本就维护的状态的副产品,零额外运行时成本 +**状态不变契约**:通过 `GetNativeObject(...)` 暴露给外部库的资源,外部库返回后必须将其留在与当前 `TransitionState` 一致的底层状态: + +- DX12:`ShaderResource` → `D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE`;`UnorderedAccess` → `D3D12_RESOURCE_STATE_UNORDERED_ACCESS` +- Vulkan:`ShaderResource` → `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`;`UnorderedAccess` → `VK_IMAGE_LAYOUT_GENERAL` +- Metal 4:状态由 driver 自管,无需配合 + +DLSS / XeSS / FFX-FSR / NRD 这类 compute 后处理库本就遵守该契约(入口前后状态一致)。主动修改 layout 的库(典型如 Skia)不适合共享 cmd,应采用: + +1. **纹理拷贝**(推荐):让外部库在其私有资源上渲染,结束后以 `Texture` 包装其输出(未来 import 接口,见 § 11.5)或直接以原生拷贝 API 搬进 RHI 资源。 +2. **獨占 swapchain**:纯 UI 应用可将整个 swapchain 让渡给外部库,RHI 不参与该 swapchain 的渲染。 +3. **配置外部库还原 layout**:Skia 可通过 `GrBackendTexture::setVkImageLayout` 要求每次 flush 后还原到 RHI 期望的 layout。 + ### 11.1 NativeObjectType 枚举 ```csharp @@ -692,86 +608,49 @@ public override nint GetNativeObject(NativeObjectType type) => type switch public override nint GetNativeObject(NativeObjectType type) => 0; ``` -### 11.3 CommandBuffer 互操作动词 - -```csharp -public abstract class CommandBuffer -{ - /// - /// 进入互操作段:结束当前 RenderPass / Encoder(如有),清空 RHI 内部绑定缓存 - /// (PSO / 描述符 / 顶点索引 / viewport / scissor)。 - /// 不向 cmd 写入命令(VK 例外:会调一次 vkCmdEndRendering)。 - /// 进入后请通过 GetNativeObject(...) 拿原生句柄交给外部库。 - /// Begin / End 必须成对调用,禁止嵌套。 - /// - public abstract void BeginExternalCommands(); - - /// - /// 结束互操作段:插入一个全局 MemoryBarrier 以隔离外部库的写入与后续 RHI 命令。 - /// 不重新打开 RenderPass / Encoder;调用方按需 BeginRenderPass / SetPipeline。 - /// - public abstract void EndExternalCommands(); - - /// - /// 同步 RHI 资源状态缓存为 newState,不写 cmd、不发 barrier。 - /// 下一次 Transition 据此算 from→to。允许在 External scope 内或外调用。 - /// - public abstract void SetState(Texture texture, TransitionState newState); +### 11.3 互操作调用约定 - public abstract void SetState(Buffer buffer, TransitionState newState); -} -``` +互操作不引入专用的 `BeginExternalCommands` / `EndExternalCommands` 动词。这类调用顺序 / RHI 缓存联动问题属于开发期能够一次性保证的事项,RHI 不以运行时 API 兜底。调用方需自行满足: -后端实现要点: +1. 调用外部库时不在 `BeginRenderPass` / `EndRenderPass` 之间(典型互操作都是 compute,天然如此)。 +2. 若调用顺序让 RHI 缓存的 PSO / descriptor / VB / IB / viewport / scissor 与外部库可能冲突,由调用方在外部库返回后重新 `SetPipeline` / `PushResourceTable` 等。 +3. **外部库返回后调一次 `cmd.MemoryBarrier()`**(见 § 6)隔离外部写入与后续 RHI 命令。 -- `BeginExternalCommands`: - - 公共部分:清 `cachedPipeline` / `cachedRootSignature`(DX12) / `cachedDescriptorSets/Heaps` / `cachedVertexBuffers` / `cachedIndexBuffer` / `cachedViewports` / `cachedScissors` - - VK:若当前在 dynamic rendering scope,调一次 `vkCmdEndRendering` - - DX12 / Metal:仅清字段,不写 cmd(DX12 不调 `ID3D12GraphicsCommandList::ClearState`,那会真往 cmd 写命令) - - 置内部 `inExternalScope = true`;若已为 true → 抛异常 -- `EndExternalCommands`: - - 调用一次内部 `MemoryBarrier()`(VK = `vkCmdPipelineBarrier2(ALL → ALL, MEMORY_READ|WRITE → MEMORY_READ|WRITE)`;DX12 = `ResourceBarrier(UAV, nullptr)` 或 `D3D12_GLOBAL_BARRIER`;Metal = `MemoryBarrier(scope=AllResources, after=AllStages, before=AllStages)`) - - 置 `inExternalScope = false`;若已为 false → 抛异常 - - 不重开 RenderPass / Encoder -- `SetState`:直接写 `texture.CurrentState = newState` / `buffer.CurrentState = newState`,零写 cmd。 +加上 § 11 引言的"状态不变契约",互操作场景仅依赖 `GetNativeObject` + `MemoryBarrier`,无需专用 API。 ### 11.4 调用方模板 ```csharp -// === DLSS (DX12) — 共享 cmd,state 不变 === +// === DLSS (DX12) — 共享 cmd,遵守状态不变契约 === cmd.Transition(colorIn, TransitionState.ShaderResource); cmd.Transition(colorOut, TransitionState.UnorderedAccess); -cmd.BeginExternalCommands(); DLSS.Evaluate( cmd.GetNativeObject(NativeObjectType.D3D12GraphicsCommandList), colorIn.GetNativeObject(NativeObjectType.D3D12Resource), colorOut.GetNativeObject(NativeObjectType.D3D12Resource)); -cmd.EndExternalCommands(); // 自动插入 barrier,隔离 DLSS 写入与后续 RHI 命令 +// 返回时:colorIn 仍为 NON_PIXEL_SHADER_RESOURCE,colorOut 仍为 UNORDERED_ACCESS +cmd.MemoryBarrier(); // 隔离 DLSS 写入与后续 RHI 命令 +cmd.Transition(colorOut, TransitionState.ShaderResource); -cmd.BeginRenderPass(...); - -// === FSR/FFX (Vulkan) — 共享 cmd,state 由外部库改 === +// === FSR/FFX (Vulkan) — 共享 cmd,同契约 === cmd.Transition(colorIn, TransitionState.ShaderResource); cmd.Transition(colorOut, TransitionState.UnorderedAccess); -cmd.BeginExternalCommands(); Ffx.Fsr2Dispatch( cmd.GetNativeObject(NativeObjectType.VkCommandBuffer), colorIn.GetNativeObject(NativeObjectType.VkImage), colorOut.GetNativeObject(NativeObjectType.VkImage)); -cmd.SetState(colorOut, TransitionState.ShaderResource); -cmd.EndExternalCommands(); - -cmd.BeginRenderPass(...); +cmd.MemoryBarrier(); +cmd.Transition(colorOut, TransitionState.ShaderResource); -// === Skia (Vulkan) — 不共享 cmd,自带提交 === -nint instance = ctx.GetNativeObject(NativeObjectType.VkInstance); -nint device = ctx.GetNativeObject(NativeObjectType.VkDevice); -nint queue = ctx.Graphics.GetNativeObject(NativeObjectType.VkQueue); -uint family = (uint)ctx.Graphics.GetNativeObject(NativeObjectType.VkQueueFamilyIndex); -var grContext = GRContext.MakeVulkan(instance, device, queue, family, /*...*/); -// Skia 自管 cmd;与 RHI 共享的资源在下次 RHI 使用前对相应 RHI 句柄调 SetState 同步缓存状态即可。 +// === Skia (Vulkan) — 纹理拷贝路径 === +// Skia 主动修改 layout,不适合共享 cmd 契约。 +// 让 Skia 在其私有 VkImage 上完成绘制,然后在 Skia 自己的 cmd buffer 里 +// 把结果拷贝到一张 RHI 拥有的 Texture(TransferDst | Sampled);Skia 在 +// flush 时 signal 一个 timeline semaphore,由调用方包装为 CommandSubmission +// 传入下一次 RHI Submit 的 wait。由于 SkiaSharp 共享 VkImage / flush 信号 +// 的 API 易变,该集成始终留在调用方侧。 // === RenderDoc — 仅 device 句柄 === nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); @@ -783,23 +662,4 @@ RenderDocApi.EndFrameCapture(device, IntPtr.Zero); ### 11.5 不做的事 - 不提供「导入外部 native 资源」入口(首版);如未来需要:通过 `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` 加入,签名同形态 -- 调用方自己保证调用外部库时不在 RenderPass 内(典型互操作都是 compute,天然如此) - -## 12. ZenithHelper 受影响成员 - -[ZenithHelper](sources/Zenith.NET/ZenithHelper.cs) 中以下函数依赖被删除的 `TextureSlice`(含 `Face`) / `TextureViewDesc`,或依赖 "cube 面隐式乘进子资源计数" 模型,新设计下不再适用,需删除或以新型重写: - -- `FaceCount(TextureDesc)` —— cube / cubeArray 以 6-layer array 表达,`ArrayLayers` 含 face 总层数,face 不再是独立轴。 -- `FaceIndex(TextureDesc, TextureSlice)` —— 依赖 `TextureSlice.Face`。 -- `FlattenArrayLayerCount(TextureDesc)` —— "把 face 乘进 array layer" 的折叠不再需要,直接 `desc.ArrayLayers`。 -- `FlattenArrayLayerIndex(TextureDesc, TextureSlice)` —— 同上。 -- `FlattenArrayLayerRange(TextureViewDesc)` —— `TextureViewDesc` 删除。 -- `SubresourceCount(TextureDesc)` —— 新模型 = `MipLevels * ArrayLayers`,使用侧可内联。 -- `SubresourceIndex(TextureDesc, TextureSlice)` —— 新模型直接 `MipLevel + ArrayLayer * MipLevels`,使用侧内联。 -- `SubresourceSizeInBytes(TextureDesc, TextureSlice)` —— 改为 `TextureSubresource` 入参重写(仅需 `MipLevel`,其余复用现有 `MipDimensions` + `SizeInBytes`)。 - -不受影响:纯格式 / 几何计算 helper(`MipDimensions`、`SizeInBytes(PixelFormat,...)`、`ElementFormat` 字节表等)保持不变。 -# RHI 重设计草案(中文临时版) - -> 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 - +- 不提供互操作 scope 动词(`BeginExternalCommands` / `EndExternalCommands`);调用顺序与缓存联动由开发期保证,运行时仅需一次 `MemoryBarrier()`(见 § 11.3) diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index b0755511..df1c0e41 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -64,7 +64,7 @@ public void Bind(DXCommandBuffer commandBuffer, DXDescriptorTable cbvSrvUavTable } } - protected override void SetImpl(uint binding, IBindableResource[] resources) + protected override void WriteImpl(uint binding, IBindableResource[] resources) { switch (Desc.Bindings[binding].Type) { diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs index dded90a3..bddf976b 100644 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ b/sources/Zenith.NET.Metal/MTLResourceTable.cs @@ -45,7 +45,7 @@ public void Bind(MTL4ArgumentTable argumentTable, uint argumentCount) } } - protected override void SetImpl(uint binding, IBindableResource[] resources) + protected override void WriteImpl(uint binding, IBindableResource[] resources) { switch (Desc.Bindings[binding].Type) { diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index 7d085a37..3db410b3 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -64,7 +64,7 @@ public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base public VKTextureView?[] UavTextureViews { get; } - protected override void SetImpl(uint binding, IBindableResource[] resources) + protected override void WriteImpl(uint binding, IBindableResource[] resources) { ref WriteDescriptorSet set = ref Sets[binding]; diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs index dd351259..5b89d401 100644 --- a/sources/Zenith.NET/ResourceTable.cs +++ b/sources/Zenith.NET/ResourceTable.cs @@ -67,7 +67,7 @@ public void Write(uint binding, params IBindableResource[] resources) break; } - SetImpl(binding, resources); + WriteImpl(binding, resources); } internal void Preprocess(CommandBuffer commandBuffer) @@ -75,7 +75,7 @@ internal void Preprocess(CommandBuffer commandBuffer) PreprocessImpl(commandBuffer); } - protected abstract void SetImpl(uint binding, IBindableResource[] resources); + protected abstract void WriteImpl(uint binding, IBindableResource[] resources); protected abstract void PreprocessImpl(CommandBuffer commandBuffer); } From 5ff5ad91bc6598e9d5a83144d8febaeec3cb6286 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 23 Apr 2026 15:48:35 +0800 Subject: [PATCH 031/466] Refactor RHI API specification and enhance synchronization model - Updated the API surface to align with Metal 4, Vulkan 1.4, and DirectX 12. - Introduced new types for texture views and component mapping, enhancing flexibility in texture handling. - Replaced abstract resource state transitions with a more explicit barrier model, detailing stages and access types. - Removed deprecated interfaces and types, streamlining the resource management approach. - Improved command submission semantics to facilitate cross-queue dependencies. - Enhanced documentation for clarity on resource handling and synchronization. Co-authored-by: Copilot --- rhi-redesign.draft.md | 358 ++++++++++++++++++-------------------- rhi-redesign.zh.draft.md | 359 ++++++++++++++++++--------------------- 2 files changed, 326 insertions(+), 391 deletions(-) diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index 0d2bc028..124bd59a 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -1,26 +1,28 @@ -# RHI Redesign +# RHI Redesign — API Specification -> Working document for the redesign discussion. Not for commit. The Chinese mirror lives in `rhi-redesign.zh.draft.md`. +> Working spec for the redesigned public surface. Not for commit. Mirrors `rhi-redesign.zh.draft.md`. +> The same surface is laid out as a C# skeleton under `sources/Zenith.NET.New/`. ## 1. Goals -- Small, uniform public API surface that maps 1:1 to DirectX 12 / Vulkan 1.4 / Metal 4. -- Short frame-loop code with no managed allocations on the hot path. -- Cross-queue synchronization (Graphics / Compute / Copy) as a first-class primitive. -- Explicit resource state transitions between `ShaderResource` and `UnorderedAccess`; every other transition is implicit from the operation. +- Public API maps 1:1 to Metal 4 / Vulkan 1.4 / DirectX 12 (Enhanced Barriers). +- Hot path is allocation-free; multi-element parameters use `ReadOnlySpan`. +- Cross-queue synchronization via a single value type (`CommandSubmission`). +- Synchronization primitive is `(stage, access)` pairs, modelled after Metal 4 `barrierAfterStages:beforeStages:visibilityOptions:`. - Inline render passes; no long-lived `FrameBuffer`. -- Subresource / sub-range information expressed at the **call site** as a value type, not as a long-lived `View` object. -- One `ResourceTable` per pipeline (no secondary descriptor sets), matching the Metal 4 argument-table model and the other two backends' root/descriptor models at 1:1. +- Subresource / sub-range information is a **call-site value type**, not a long-lived `View` object. +- One `ResourceTable` per pipeline, aligned with the Metal 4 argument-table model. -## 2. API-Surface Conventions +## 2. Conventions - All public value types: `record struct` with public mutable fields. - Multi-element parameters: `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, no `IEnumerable`. - All method bodies use `{ ... }`. **Exception:** `ref` / `ref readonly` returning properties keep `=> ref _field;`. -- Every byte-denominated parameter or field carries an `*InBytes` suffix. -- Backend-hook naming: the `*Core` suffix exists **only** to disambiguate from a same-named non-`Core` wrapper (e.g. `Wait` / `WaitCore`, `Submit` / `SubmitCore`). Hooks without a wrapper use their natural name. +- Every byte-denominated parameter or field carries the `*InBytes` suffix. +- Backend hooks use the `*Core` suffix only when they share a name with a non-`Core` wrapper (e.g. `Wait` / `WaitCore`); otherwise they keep their natural name. - Every abstract member is plain `protected abstract`; same-assembly callers go through an `internal` wrapper. - The only public synchronization result type is `CommandSubmission`. +- There are **no global barriers** and **no residency primitives** on the public surface. ## 3. Public Type Catalog @@ -96,12 +98,119 @@ public record struct BufferRange } } -// === Resource state (public surface) === +// === Texture view (call-site value) === -public enum TransitionState +public enum TextureViewType { - ShaderResource, - UnorderedAccess + Texture1D, + Texture2D, + Texture3D, + TextureCube, + Texture1DArray, + Texture2DArray, + TextureCubeArray +} + +public enum ComponentSwizzle +{ + Identity, + Zero, + One, + R, + G, + B, + A +} + +public record struct ComponentMapping +{ + public ComponentSwizzle R; + + public ComponentSwizzle G; + + public ComponentSwizzle B; + + public ComponentSwizzle A; + + public static ComponentMapping Identity => new() + { + R = ComponentSwizzle.Identity, + G = ComponentSwizzle.Identity, + B = ComponentSwizzle.Identity, + A = ComponentSwizzle.Identity + }; +} + +public record struct TextureView +{ + public Texture Texture; + + public TextureSubresourceRange Range; + + /// null derives from Texture.Desc + Range. + public TextureViewType? ViewType; + + /// null uses Texture.Desc.Format; otherwise reinterprets within the same family. + public PixelFormat? Format; + + public ComponentMapping Swizzle; + + public static implicit operator TextureView(Texture texture) + { + return new() + { + Texture = texture, + Range = texture, + ViewType = null, + Format = null, + Swizzle = ComponentMapping.Identity + }; + } +} + +// === Synchronization (stage + access) === + +[Flags] +public enum BarrierStage : uint +{ + None = 0, + All = ~0u, + Draw = 1u << 0, + VertexInput = 1u << 1, + VertexShader = 1u << 2, + PixelShader = 1u << 3, + EarlyDepthStencil = 1u << 4, + LateDepthStencil = 1u << 5, + RenderTarget = 1u << 6, + ComputeShader = 1u << 7, + RayTracing = 1u << 8, + Copy = 1u << 9, + Resolve = 1u << 10, + IndirectArgument = 1u << 11, + AccelerationStructureBuild = 1u << 12, +} + +[Flags] +public enum BarrierAccess : uint +{ + None = 0, + VertexBuffer = 1u << 0, + IndexBuffer = 1u << 1, + ConstantBuffer = 1u << 2, + ShaderRead = 1u << 3, + UnorderedAccessRead = 1u << 4, + UnorderedAccessWrite = 1u << 5, + RenderTarget = 1u << 6, + DepthStencilRead = 1u << 7, + DepthStencilWrite = 1u << 8, + CopySource = 1u << 9, + CopyDestination = 1u << 10, + ResolveSource = 1u << 11, + ResolveDestination = 1u << 12, + IndirectArgument = 1u << 13, + Present = 1u << 14, + AccelerationStructureRead = 1u << 15, + AccelerationStructureWrite = 1u << 16, } // === RenderPass attachments === @@ -178,17 +287,19 @@ public readonly struct CommandSubmission(CommandQueue? queue, ulong value) Subresource triplet: -| Type | Shape | Used by | Backend mapping | -|---|---|---|---| -| `TextureSubresource` | one mip × one layer | RTV / DSV / Resolve | DX12 plane+mip+slice singleton; VK `aspect+mip+layer` singleton; Metal `level+slice` | -| `TextureSubresourceLayers` | one mip × contiguous layer range | Copy / Upload | `VkImageSubresourceLayers`; DX12 issues one `CopyTextureRegion` per layer; Metal `blitEncoder` per layer | -| `TextureSubresourceRange` | contiguous mip range × contiguous layer range | View / Transition | `VkImageSubresourceRange`; DX12 view desc base+count; Metal `MTLTextureView` | +| Type | Shape | Used by | +|---|---|---| +| `TextureSubresource` | one mip × one layer | RTV / DSV / Resolve | +| `TextureSubresourceLayers` | one mip × contiguous layer range | Copy / Upload | +| `TextureSubresourceRange` | contiguous mip range × contiguous layer range | Barrier / `TextureView.Range` | -The public surface does **not** carry an aspect field; backends derive aspect (color / depth / stencil) from `Texture.Format`. Cube faces use `ArrayLayer = cubeIndex * 6 + face`, matching VK / DX12 / Metal — no separate face axis. +The public surface does not carry an aspect field; backends derive aspect (color / depth / stencil) from `Texture.Format`. Cube faces are addressed as `ArrayLayer = cubeIndex * 6 + face`; there is no separate face axis. -## 4. Resource Handles +`TextureView` mirrors VK `VkImageViewCreateInfo` and Metal `newTextureViewWithPixelFormat:textureType:levels:slices:swizzle:`. The native view object is created lazily and cached per backend, keyed by `(Texture, Range, ViewType, Format, Swizzle)`. DX12 has no first-class swizzle; the backend rejects non-Identity swizzles unless the slot's layout opts in to a shader-side swizzle workaround. -`Buffer` / `Texture` / `Sampler` no longer implement `IBindableResource` (the interface is removed): +`BarrierStage` describes **when** in the pipeline an access happens; `BarrierAccess` describes **what kind** of access it is. They mirror Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`, VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`, and DX12 Enhanced Barriers `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS`. + +## 4. Resource Handles ```csharp public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) @@ -196,8 +307,6 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic private BufferDesc desc = desc; public ref readonly BufferDesc Desc => ref desc; - - // Map / Unmap / Upload kept } public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) @@ -226,11 +335,8 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph } ``` -**View caches** (per-backend, on each `Texture` / `Buffer`): the design permits backends to lazily cache the native view objects keyed by the value types in §3, but no part of this is on the public surface. - -**State tracking:** every `Texture` / `Buffer` carries its current `TransitionState`; the initial value is set by the backend at creation. The first explicit `Transition` synthesizes the from→to barrier against that cached value. Only `ShaderResource` ↔ `UnorderedAccess` is user-visible; all other transitions (render-target / depth-stencil / copy / vertex / index / CBV / indirect / present) are driven implicitly by the operation issuing them. - -**Removed from the framework (these all exist today):** `BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `BufferViewType` / `IBindableResource` / `TextureSlice` (incl. `Face`) / `TextureOffset` / `TextureExtent` / `FrameBuffer` / `FrameBufferDesc` / `FrameBufferAttachment` / `ClearValue` / `ClearValues` (static factory) / `ClearFlags` / `GraphicsContext.CreateBufferView` / `CreateTextureView` / `CreateFrameBuffer`. The `Output` type itself stays — it remains `GraphicsPipelineDesc.Output`; only the `FrameBuffer.Output` use site disappears. +- View objects are not on the public surface. Backends may lazily cache native views keyed by the §3 value types; that cache is a backend implementation detail. +- Texture image-layout (DX12 / VK) is fully internal to the backend; it is computed from the access flags supplied to barriers and from RenderPass attachment metadata. ## 5. CommandQueue / CommandBuffer @@ -263,30 +369,33 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) } ``` -- `GetCompletedValue()` is a **method**, not a property: all three backends (DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`) are call-shaped. +- `GetCompletedValue()` is a method, not a property: all three backends (DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`) are call-shaped. - `CommandBuffer` pooling is entirely a queue-internal detail; the public surface only offers `CommandBuffer()` and `Submit(...)`. -- Three queues are exposed on `GraphicsContext`: `Graphics` / `Compute` / `Copy`. `Present` always runs on `Graphics`; `SwapChain` pulls that queue reference from the context and never exposes it on its own surface. +- `GraphicsContext` exposes three queues: `Graphics` / `Compute` / `Copy`. `Present` always runs on `Graphics`; `SwapChain` pulls that queue reference from the context. ### Backend Timeline Mapping | Backend | Timeline object | API | |---|---|---| -| DX12 | one `ID3D12Fence` per queue | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | -| Vulkan 1.4 | one timeline `VkSemaphore` per queue (core feature) | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | | Metal 4 | one `MTLSharedEvent` per queue | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | - -All three backends support this timeline model natively; the table is a feasibility record, not a prescription. +| Vulkan 1.4 | one timeline `VkSemaphore` per queue | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | +| DX12 | one `ID3D12Fence` per queue | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | ## 6. CommandBuffer Operations ```csharp -// === State === +// === Synchronization === -public void Transition(Buffer buffer, TransitionState newState); +public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); -public void Transition(Texture texture, TransitionState newState); +public void BufferBarrier(BufferRange range, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); -public void MemoryBarrier(); +public void TextureBarrier(TextureView view, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); // === Inline RenderPass === @@ -356,38 +465,33 @@ public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, public void Upload(Texture destination, TextureSubresourceLayers layers, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; ``` -Notes: - -- `BeginRenderPass` accepts an attachment span; pass `null` explicitly for no depth. The implementation auto-`Transition`s the attachments and auto-fills `SetViewports` / `SetScissors` from attachment dimensions; the caller may issue `SetViewports` / `SetScissors` afterward to override. -- `PushResourceTable` takes no `stages` argument: stage information is carried per-binding in `ResourceTable.Layout` (DX12 root-parameter visibility and Metal argument-table placement are derived from it; the VK set's stage mask is fixed at layout creation time). Only one table per pipeline is supported; there is no `setIndex`. -- **Push-snapshot semantics**: `PushResourceTable` snapshots the current contents of `table` into the cmd buffer at the call site; subsequent `Write`s to that `table` do not affect already-pushed bindings. All three backends provide this natively (DX12 descriptor copy on bind, VK `vkCmdPushDescriptorSet`, Metal 4 `setArgumentTable:`), so the same `ResourceTable` can be repeatedly `Write` + `Push`ed within a frame. -- `Transition` is only called explicitly between `ShaderResource` and `UnorderedAccess`. Render-target / depth-stencil / copy / vertex / index / CBV / indirect / present states are transitioned implicitly by the corresponding operation. -- `MemoryBarrier()` is a global cross-resource / cross-stage memory barrier; the caller decides when to issue it. +- `BeginRenderPass` accepts an attachment span; pass `null` explicitly for no depth. The implementation auto-emits the attachment-side barriers and auto-fills `SetViewports` / `SetScissors` from attachment dimensions; the caller may override afterward. +- `PushResourceTable` takes no `stages` argument — stage information is carried per-binding in the table's layout. Only one table per pipeline is supported. +- **Push-snapshot semantics**: `PushResourceTable` snapshots `table` into the cmd buffer at the call site; subsequent `Write`s do not affect already-pushed bindings, so the same `ResourceTable` can be repeatedly `Write` + `Push`ed within a frame. +- `Barrier` is the global form (no resource argument). `BufferBarrier` / `TextureBarrier` scope the barrier to a `BufferRange` / `TextureView`. All three carry the same `(afterStages, afterAccess) → (beforeStages, beforeAccess)` shape. ### ResourceTable -`Write` becomes strongly typed, replacing the current `Write(uint, params IBindableResource[])` + runtime type-switch: - ```csharp public abstract class ResourceTable { public void Write(uint binding, BufferRange range); - public void Write(uint binding, TextureSubresourceRange range); + public void Write(uint binding, TextureView view); public void Write(uint binding, Sampler sampler); public void Write(uint binding, ReadOnlySpan ranges); - public void Write(uint binding, ReadOnlySpan ranges); + public void Write(uint binding, ReadOnlySpan views); public void Write(uint binding, ReadOnlySpan samplers); } ``` - `Buffer` / `Texture` flow into the call through their implicit operators, keeping single-resource binds a single line. -- The interpretation of a buffer (CBV / structured SRV / byte-address SRV / UAV / typed) is fixed by the slot's `ResourceLayout`; there is no public `BufferViewType`. -- Validation: a UAV slot requires `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`; shape / layout mismatches are reported at `Write` time. +- The interpretation of a buffer (CBV / structured SRV / byte-address SRV / UAV / typed) is fixed by the slot's `ResourceLayout`. +- A UAV slot requires `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`; shape / layout / format-family mismatches are reported at `Write` time. ## 7. SwapChain @@ -418,13 +522,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G } ``` -Design points: - -- Backbuffers are exposed as plain `Texture`s (color + optional depth-stencil). No `FrameBuffer`, no image index. +- Backbuffers are exposed as plain `Texture`s (color + optional depth-stencil). - `Present(...)` returns a `CommandSubmission` describing when the next backbuffer is writable; it accepts waits from any queue, symmetrically with `CommandBuffer.Submit(...)`. -- The current backbuffer index is entirely a backend concern and is never on the public surface. - -All three backends support the shape `Present(waits) → CommandSubmission` natively: DX12 `IDXGISwapChain3::Present` with `graphicsQueue.Wait(fence, value)` for each wait; Vulkan 1.4 a bridging `vkQueueSubmit2` that translates timeline waits into a binary `renderFinished` semaphore consumed by `vkQueuePresentKHR`; Metal 4 `commandBuffer.EncodeWaitForEvent(...)` → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()`. +- The current backbuffer index is entirely a backend concern. ## 8. Frame Loop @@ -458,7 +558,6 @@ while (running) } : null; - // Viewports / scissors are auto-filled from attachment dimensions; override afterward if needed. cmd.BeginRenderPass(colors, depth); // draws cmd.EndRenderPass(); @@ -471,55 +570,7 @@ while (running) Every step has the same shape: take a set of `CommandSubmission` waits, return a `CommandSubmission`. `Submit` and `Present` are symmetric on the API surface. -## 9. Three-Backend Feasibility Matrix - -Every public primitive in §3–§7 has a native equivalent on all three backends: - -| Concept | DX12 | Vulkan 1.4 | Metal 4 | -|---|---|---|---| -| Inline RenderPass | `ID3D12GraphicsCommandList4::BeginRenderPass` / `EndRenderPass` | `vkCmdBeginRendering` / `vkCmdEndRendering` | `MTL4CommandBuffer::MakeRenderCommandEncoder` / `endEncoding` | -| LoadAction | `RenderPassBeginningAccessType` | `VkAttachmentLoadOp` | `MTLLoadAction` | -| StoreAction (incl. Resolve) | `RenderPassEndingAccessType` | `VkAttachmentStoreOp` + `pResolveAttachments` + `resolveMode` | `MTLStoreAction` + `resolveTexture` | -| Explicit Transition (SRV ↔ UAV) | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2` (`VkImageMemoryBarrier2` / `VkBufferMemoryBarrier2`) | `MTL4ComputeCommandEncoder.BarrierAfterEncoderStages` | -| Global `MemoryBarrier()` | `ResourceBarrier(UAV)` global | `vkCmdPipelineBarrier2` with `VK_ACCESS_2_MEMORY_READ/WRITE_BIT` | `BarrierAfterEncoderStages` with all stages | -| Aspect inference source | format → plane index | `Texture.Format` → `VkImageAspectFlags` | format → automatic | - -## 10. RDG Interface Self-Check - -The RDG is built on top of this RHI by the consumer, not by us. This section only answers: "Does the RHI expose every primitive an RDG needs?" - -| RDG requirement | Provided by RHI | Entry point | -|---|---|---| -| Explicit resource state transitions | ✅ | `CommandBuffer.Transition(Buffer/Texture, TransitionState)` | -| Global memory barrier | ✅ | `CommandBuffer.MemoryBarrier()` | -| Sub-range addressing (alias / hazard keys) | ✅ | `BufferRange` / `TextureSubresourceRange` (`record struct`, hashable) | -| Cross-queue dependencies | ✅ | `CommandSubmission` waits (symmetric on `Submit` / `Present`) | -| Queue completion query / wait | ✅ | `CommandQueue.WaitForIdle()` / `CommandSubmission.Wait()` | -| Short-lived RenderPass | ✅ | Inline `BeginRenderPass(colorAttachments, depth)` | - -Conclusion: an RDG implementation can be built without any further changes to the RHI public surface. - -## 11. Interop / Native Handles - -For third-party libraries that need backend-native handles: Skia / DLSS / FSR / RenderDoc / tooling. Design principles: - -- The public surface exposes only `nint` plus an enum; no backend types and no platform-conditional properties. -- The RHI does not perform work on behalf of the external library, and provides no escape hatch for external code to mutate the RHI's cached resource state. -- The interop API is a side-effect of state the RHI already maintains internally — zero extra runtime cost. - -**State-preservation contract**: any resource exposed to an external library through `GetNativeObject(...)` must be returned in the underlying state matching its current `TransitionState`: - -- DX12: `ShaderResource` → `D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE`; `UnorderedAccess` → `D3D12_RESOURCE_STATE_UNORDERED_ACCESS`. -- Vulkan: `ShaderResource` → `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`; `UnorderedAccess` → `VK_IMAGE_LAYOUT_GENERAL`. -- Metal 4: state is driver-managed; no caller cooperation required. - -Compute post-processing libraries (DLSS / XeSS / FFX-FSR / NRD) honour this contract by design (state at entry == state at exit). Libraries that actively rewrite layout (Skia and similar) are not suitable for the shared-cmd path; instead use: - -1. **Texture copy** (recommended): let the external library render onto its own private resource, then copy the output into an RHI texture (a future import entry point may avoid the copy; see § 11.5). -2. **Dedicated swapchain**: pure-UI applications can hand the entire swapchain to the external library; the RHI does not participate in that swapchain's rendering. -3. **Configure the library to restore layout**: e.g. Skia's `GrBackendTexture::setVkImageLayout` can request the layout to be restored to the RHI's expected value after every flush. - -### 11.1 NativeObjectType +## 9. Native Handles ```csharp public enum NativeObjectType @@ -557,15 +608,7 @@ public enum NativeObjectType VkBuffer, VkSampler } -``` - -Prefixes follow the original native APIs: DX12 splits into `Dxgi` / `D3D12`; Metal 4 keeps `Mtl4` distinct from the older `Mtl`; Vulkan uses `Vk`. `D3D12CpuDescriptorHandle*` is split per view type to avoid ambiguity at a single entry point. -Non-handle scalar information (e.g. `VkQueueFamilyIndex`) flows through the same entry point, carrying a `uint` inside an `nint`. The public surface never grows a platform-conditional property. - -### 11.2 INativeObject Interface - -```csharp public interface INativeObject { /// Returns 0 when the enum does not match this object or the current backend. @@ -585,81 +628,6 @@ public abstract class GraphicsResource(GraphicsContext context) : DisposableObje } ``` -Every `GraphicsResource` subclass (`Buffer` / `Texture` / `Sampler` / `CommandQueue` / `CommandBuffer` / `SwapChain` / `Pipeline` / `Shader` / `ResourceTable` / `ResourceLayout`) implements the interface. Backends use a `switch`; subclasses with nothing to expose return 0. Third-party code can program against the interface without distinguishing context from resource. - -Backend implementation examples: - -```csharp -// VKTexture -public override nint GetNativeObject(NativeObjectType type) => type switch -{ - NativeObjectType.VkImage => (nint)Image.Handle, - NativeObjectType.VkImageView => (nint)View.Handle, - _ => 0 -}; - -// DXBuffer -public override nint GetNativeObject(NativeObjectType type) => type switch -{ - NativeObjectType.D3D12Resource => (nint)Resource.Handle, - _ => 0 -}; - -// Subclass that does not currently expose anything -public override nint GetNativeObject(NativeObjectType type) => 0; -``` - -### 11.3 Interop Calling Convention - -No dedicated `BeginExternalCommands` / `EndExternalCommands` verbs. Call ordering and RHI cache interaction are concerns the caller resolves at development time, not at the runtime API boundary. The caller guarantees: - -1. The external library is invoked outside any `BeginRenderPass` / `EndRenderPass` (typical interop is compute, so this holds naturally). -2. If the call ordering would let the RHI's cached PSO / descriptors / vertex+index buffers / viewports / scissors collide with the external library, the caller re-issues `SetPipeline` / `PushResourceTable` etc. after the external library returns. -3. **Issue one `cmd.MemoryBarrier()`** (see § 6) after the external library returns to isolate its writes from subsequent RHI commands. - -Combined with the state-preservation contract from § 11, interop relies only on `GetNativeObject` and `MemoryBarrier`; no dedicated API is required. -### 11.4 Caller Templates - -```csharp -// === DLSS (DX12) — shared cmd, follows the state-preservation contract === -cmd.Transition(colorIn, TransitionState.ShaderResource); -cmd.Transition(colorOut, TransitionState.UnorderedAccess); - -DLSS.Evaluate( - cmd.GetNativeObject(NativeObjectType.D3D12GraphicsCommandList), - colorIn.GetNativeObject(NativeObjectType.D3D12Resource), - colorOut.GetNativeObject(NativeObjectType.D3D12Resource)); -// On return: colorIn stays NON_PIXEL_SHADER_RESOURCE, colorOut stays UNORDERED_ACCESS. -cmd.MemoryBarrier(); // isolate DLSS writes from later RHI commands -cmd.Transition(colorOut, TransitionState.ShaderResource); - -// === FSR/FFX (Vulkan) — shared cmd, same contract === -cmd.Transition(colorIn, TransitionState.ShaderResource); -cmd.Transition(colorOut, TransitionState.UnorderedAccess); - -Ffx.Fsr2Dispatch( - cmd.GetNativeObject(NativeObjectType.VkCommandBuffer), - colorIn.GetNativeObject(NativeObjectType.VkImage), - colorOut.GetNativeObject(NativeObjectType.VkImage)); -cmd.MemoryBarrier(); -cmd.Transition(colorOut, TransitionState.ShaderResource); - -// === Skia (Vulkan) — texture-copy path === -// Skia actively rewrites layout, so it does not fit the shared-cmd contract. -// Let Skia render onto its own private VkImage, then copy into an RHI-owned -// Texture (TransferDst | Sampled) from inside Skia's own command buffer; have -// Skia signal a timeline semaphore on flush and feed it into the next RHI -// Submit as a CommandSubmission wait. The exact SkiaSharp surface for sharing -// a VkImage and the flush-signal is volatile, so the integration stays caller-side. - -// === RenderDoc — device handle only === -nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); -RenderDocApi.StartFrameCapture(device, IntPtr.Zero); -// ... render one frame ... -RenderDocApi.EndFrameCapture(device, IntPtr.Zero); -``` - -### 11.5 Out of Scope - -- No "import external native resource" entry point in this iteration; if needed later, add `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` with the same shape. -- No dedicated interop scope verbs (`BeginExternalCommands` / `EndExternalCommands`); call ordering and cache interaction are caller-side concerns, with a single `MemoryBarrier()` at runtime (see § 11.3). +- Prefixes follow each native API: DX12 splits into `Dxgi` / `D3D12`; Metal 4 keeps `Mtl4` distinct from the older `Mtl`; Vulkan uses `Vk`. `D3D12CpuDescriptorHandle*` is split per view type to avoid ambiguity at a single entry point. +- Non-handle scalar information (e.g. `VkQueueFamilyIndex`) flows through the same entry point, carrying a `uint` inside an `nint`. The public surface never grows a platform-conditional property. +- Every `GraphicsResource` subclass implements the interface; subclasses with nothing to expose return 0. diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index fcda140c..5ebc4949 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -1,16 +1,17 @@ -# RHI 重设计草案(中文临时版) +# RHI 重设计草案 — API 规范(中文临时版) > 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 +> 同一份规范以 C# 骨架放在 `sources/Zenith.NET.New/`。 ## 1. 目标 -- 公共 API 表面小、调用形态统一,与 DirectX 12 / Vulkan 1.4 / Metal 4 命令模型 1:1 对应 -- 帧循环代码短,热路径零托管分配 -- 跨队列同步(Graphics / Compute / Copy)一等公民 -- 显式资源状态转换仅限 `ShaderResource` ↔ `UnorderedAccess`,其余转换均由对应操作隐式完成 +- 公共 API 与 Metal 4 / Vulkan 1.4 / DirectX 12(Enhanced Barriers)1:1 对齐 +- 热路径零托管分配;多元素入参一律 `ReadOnlySpan` +- 跨队列同步以单一值类型 `CommandSubmission` 表达 +- 同步原语:`(stage, access)` 二元组,对齐 Metal 4 `barrierAfterStages:beforeStages:visibilityOptions:` - 内联 RenderPass,去掉长生命周期 `FrameBuffer` -- 子范围 / 子资源信息在**调用现场**以值类型表达,不再有公共 `View` 对象 -- 每个 pipeline 一张 `ResourceTable`(不设次级 descriptor set),对齐 Metal 4 argument-table 模型与另两端的 root/descriptor 模型 +- 子范围 / 子资源信息为**调用现场**值类型,不再有公共 `View` 对象 +- 每个 pipeline 一张 `ResourceTable`,对齐 Metal 4 argument-table 模型 ## 2. 命名与代码风格约定 @@ -18,9 +19,10 @@ - 多元素入参一律 `ReadOnlySpan` / `params ReadOnlySpan`,不使用 `T[]` / `IEnumerable` - 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性保留 `=> ref _field;` - 字节单位字段与参数加 `*InBytes` 后缀 -- 抽象后端钩子命名:**只有**与同名非 `Core` 包装共存时才用 `*Core` 后缀(如 `Wait` / `WaitCore`、`Submit` / `SubmitCore`);其余一律用自然名 +- 抽象后端钩子命名:**只有**与同名非 `Core` 包装共存时才用 `*Core` 后缀(如 `Wait` / `WaitCore`);其余一律用自然名 - 抽象成员一律 `protected abstract`,同程序集内部调用走 `internal` 包装 - 公共同步结果只有一种:`CommandSubmission` +- 公共面**无**全局内存屏障、**无**驻留集(residency)相关原语 ## 3. 公共类型一览 @@ -96,12 +98,119 @@ public record struct BufferRange } } -// === 资源状态(公共面) === +// === Texture view(调用现场值) === -public enum TransitionState +public enum TextureViewType { - ShaderResource, - UnorderedAccess + Texture1D, + Texture2D, + Texture3D, + TextureCube, + Texture1DArray, + Texture2DArray, + TextureCubeArray +} + +public enum ComponentSwizzle +{ + Identity, + Zero, + One, + R, + G, + B, + A +} + +public record struct ComponentMapping +{ + public ComponentSwizzle R; + + public ComponentSwizzle G; + + public ComponentSwizzle B; + + public ComponentSwizzle A; + + public static ComponentMapping Identity => new() + { + R = ComponentSwizzle.Identity, + G = ComponentSwizzle.Identity, + B = ComponentSwizzle.Identity, + A = ComponentSwizzle.Identity + }; +} + +public record struct TextureView +{ + public Texture Texture; + + public TextureSubresourceRange Range; + + /// null 表示按 Texture.Desc + Range 推导。 + public TextureViewType? ViewType; + + /// null 表示沿用 Texture.Desc.Format;非 null 时按同一兼容族重解释。 + public PixelFormat? Format; + + public ComponentMapping Swizzle; + + public static implicit operator TextureView(Texture texture) + { + return new() + { + Texture = texture, + Range = texture, + ViewType = null, + Format = null, + Swizzle = ComponentMapping.Identity + }; + } +} + +// === 同步(stage + access) === + +[Flags] +public enum BarrierStage : uint +{ + None = 0, + All = ~0u, + Draw = 1u << 0, + VertexInput = 1u << 1, + VertexShader = 1u << 2, + PixelShader = 1u << 3, + EarlyDepthStencil = 1u << 4, + LateDepthStencil = 1u << 5, + RenderTarget = 1u << 6, + ComputeShader = 1u << 7, + RayTracing = 1u << 8, + Copy = 1u << 9, + Resolve = 1u << 10, + IndirectArgument = 1u << 11, + AccelerationStructureBuild = 1u << 12, +} + +[Flags] +public enum BarrierAccess : uint +{ + None = 0, + VertexBuffer = 1u << 0, + IndexBuffer = 1u << 1, + ConstantBuffer = 1u << 2, + ShaderRead = 1u << 3, + UnorderedAccessRead = 1u << 4, + UnorderedAccessWrite = 1u << 5, + RenderTarget = 1u << 6, + DepthStencilRead = 1u << 7, + DepthStencilWrite = 1u << 8, + CopySource = 1u << 9, + CopyDestination = 1u << 10, + ResolveSource = 1u << 11, + ResolveDestination = 1u << 12, + IndirectArgument = 1u << 13, + Present = 1u << 14, + AccelerationStructureRead = 1u << 15, + AccelerationStructureWrite = 1u << 16, } // === RenderPass 附件 === @@ -175,19 +284,21 @@ public readonly struct CommandSubmission(CommandQueue? queue, ulong value) } ``` -子资源三件套对照: +子资源三件套: -| 类型 | 表达 | 用途 | 后端对照 | -|---|---|---|---| -| `TextureSubresource` | 单 mip × 单 layer | RTV / DSV / Resolve | DX12 plane+mip+slice 单点;VK `aspect+mip+layer` 单点;Metal `level+slice` | -| `TextureSubresourceLayers` | 单 mip × 连续 layer 段 | Copy / Upload | `VkImageSubresourceLayers`;DX12 按 layer 循环 `CopyTextureRegion`;Metal `blitEncoder` 逐层 | -| `TextureSubresourceRange` | 连续 mip 段 × 连续 layer 段 | View / Transition | `VkImageSubresourceRange`;DX12 view desc 的 base+count;Metal `MTLTextureView` | +| 类型 | 表达 | 用途 | +|---|---|---| +| `TextureSubresource` | 单 mip × 单 layer | RTV / DSV / Resolve | +| `TextureSubresourceLayers` | 单 mip × 连续 layer 段 | Copy / Upload | +| `TextureSubresourceRange` | 连续 mip 段 × 连续 layer 段 | Barrier / `TextureView.Range` | -公共面**不**暴露 aspect 字段;后端从 `Texture.Format` 推断 `VkImageAspectFlags` / DX12 plane index。Cube 面以 `ArrayLayer = cubeIndex * 6 + face` 表达,与 VK / DX12 / Metal 一致,无独立 face 轴。 +公共面不暴露 aspect 字段,后端从 `Texture.Format` 推断。Cube 面以 `ArrayLayer = cubeIndex * 6 + face` 表达,无独立 face 轴。 -## 4. 资源句柄 +`TextureView` 对齐 VK `VkImageViewCreateInfo` 与 Metal `newTextureViewWithPixelFormat:textureType:levels:slices:swizzle:` 的参数集;背后的原生 view 由后端按 `(Texture, Range, ViewType, Format, Swizzle)` 为 key 懒加载并缓存,纯实现细节。DX12 没有原生 swizzle,后端在槽位 layout 未声明 shader-side swizzle 兼容时拒绝非 Identity 的 swizzle。 -`Buffer` / `Texture` / `Sampler` 不再实现 `IBindableResource`(接口删除): +`BarrierStage` 描述访问发生在管线**何处**;`BarrierAccess` 描述访问的**性质**。两者对齐 Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`、VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`、DX12 Enhanced Barriers `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS`。 + +## 4. 资源句柄 ```csharp public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) @@ -195,8 +306,6 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic private BufferDesc desc = desc; public ref readonly BufferDesc Desc => ref desc; - - // Map / Unmap / Upload 保留 } public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) @@ -225,15 +334,12 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph } ``` -**View 缓存**(每个后端 `Texture` / `Buffer` 内部):设计允许后端按 §3 的值类型做 key 懒加载缓存原生 view 对象,但此缓存完全属于后端内部,不在公共面出现。 - -**资源状态跟踪**:每个 `Texture` / `Buffer` 持有当前 `TransitionState`,初值由后端在创建时写入。首次显式 `Transition` 根据该缓存值生成 from→to barrier。仅 `ShaderResource` ↔ `UnorderedAccess` 向用户可见;render target / 深度模板 / copy / 顶点 / 索引 / CBV / indirect / present 等状态由对应操作隐式转换。 - -**确认从框架删除(这些当前真实存在)**:`BufferView` / `BufferViewDesc` / `TextureView` / `TextureViewDesc` / `BufferViewType` / `IBindableResource` / `TextureSlice`(含 `Face`)/ `TextureOffset` / `TextureExtent` / `FrameBuffer` / `FrameBufferDesc` / `FrameBufferAttachment` / `ClearValue` / `ClearValues` 静态类 / `ClearFlags` 枚举 / `GraphicsContext.CreateBufferView` / `CreateTextureView` / `CreateFrameBuffer`。`Output` 类型保留并继续作为 `GraphicsPipelineDesc.Output`,仅 `FrameBuffer.Output` 一处使用消失。 +- View 对象不在公共面出现;后端可按 §3 的值类型做 key 懒加载缓存原生 view,纯实现细节。 +- DX12 / VK 的 texture image-layout 完全在后端内部维护,由 barrier 携带的 access flags 与 RenderPass attachment 元数据推导。 ## 5. CommandQueue / CommandBuffer -每个 `CommandQueue` 持有一条单调递增的完成时间线;一次 `Submit` 在时间线上推进一个值,并以 `CommandSubmission(queue, value)` 公开该点。跨队列依赖通过把上游 `CommandSubmission` 传入下游 `Submit(waits)` 表达。 +每个 `CommandQueue` 持有一条单调递增的完成时间线;一次 `Submit` 推进一个值,并以 `CommandSubmission(queue, value)` 公开该点。跨队列依赖通过把上游 `CommandSubmission` 传入下游 `Submit(waits)` 表达。 ```csharp public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) @@ -262,30 +368,33 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) } ``` -- `GetCompletedValue()` 是**方法**而非属性:三端后端(DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`)都是调用语义。 +- `GetCompletedValue()` 是方法而非属性:三端后端(DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`)都是调用语义。 - CommandBuffer 池化完全是 queue 的内部实现细节;公共面只暴露 `CommandBuffer()` 与 `Submit(...)`。 -- `GraphicsContext` 暴露三条队列:`Graphics` / `Compute` / `Copy`。`Present` 始终在 `Graphics` 上执行;`SwapChain` 从 context 拿到该队列引用,自身公共面不暴露队列。 +- `GraphicsContext` 暴露三条队列:`Graphics` / `Compute` / `Copy`。`Present` 始终在 `Graphics` 上执行;`SwapChain` 从 context 取该队列引用。 ### 后端时间线对照 | 后端 | 时间线对象 | API | |---|---|---| -| DX12 | 每 queue 一个 `ID3D12Fence` | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | -| Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore`(核心特性) | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | | Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | - -三端均原生支持上述 timeline 模型;本表仅用于可行性存档,并非实现规定。 +| Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore` | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | +| DX12 | 每 queue 一个 `ID3D12Fence` | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | ## 6. CommandBuffer 操作 ```csharp -// === 状态 === +// === 同步 === -public void Transition(Buffer buffer, TransitionState newState); +public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); -public void Transition(Texture texture, TransitionState newState); +public void BufferBarrier(BufferRange range, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); -public void MemoryBarrier(); +public void TextureBarrier(TextureView view, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); // === RenderPass(内联) === @@ -355,38 +464,33 @@ public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, public void Upload(Texture destination, TextureSubresourceLayers layers, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; ``` -要点: - -- `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;内部按 attachment 自动 `Transition` + 自动填 `SetViewports` / `SetScissors`,调用方可在其后再调一次 `SetViewports` / `SetScissors` 覆盖。 -- `PushResourceTable` 不接受 stages 参数:stages 信息由 `ResourceTable.Layout` 的每个 binding 自带(DX12 root parameter visibility / Metal argument table 写入位置据此推导;VK 的 set 在 layout 里编译期固定)。每个 pipeline 仅支持一张 table,无 setIndex。 -- **Push-snapshot 语义**:`PushResourceTable` 在调用现场把 `table` 的当前内容快照进 cmd buffer;之后对该 `table` 的 `Write` 不影响已 push 的绑定。三端均原生支持此语义(DX12 绑定时拷贝 descriptor、VK `vkCmdPushDescriptorSet`、Metal 4 `setArgumentTable:`),因此同一个 `ResourceTable` 可以在帧内反复 `Write` + `Push`。 -- `Transition` 只在 `ShaderResource` ↔ `UnorderedAccess` 之间显式调用;render target / 深度模板 / copy / 顶点 / 索引 / CBV / indirect / present 状态由对应操作隐式转换。 -- `MemoryBarrier()`:跨资源 / 跨阶段全局内存屏障,由调用方自行决定何时发出。 +- `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;内部按 attachment 自动发出对应的同步 barrier,并按 attachment 尺寸自动填 `SetViewports` / `SetScissors`,调用方可在其后再调一次覆盖。 +- `PushResourceTable` 不接受 stages 参数:stages 信息由 table 的 layout 在每个 binding 上自带。每个 pipeline 仅支持一张 table。 +- **Push-snapshot 语义**:`PushResourceTable` 在调用现场把 `table` 的当前内容快照进 cmd buffer;之后对该 `table` 的 `Write` 不影响已 push 的绑定,因此同一个 `ResourceTable` 可以在帧内反复 `Write` + `Push`。 +- `Barrier` 是不带资源的全局形式;`BufferBarrier` / `TextureBarrier` 把 barrier 限定到 `BufferRange` / `TextureView`。三者形态一致,都是 `(afterStages, afterAccess) → (beforeStages, beforeAccess)`。 ### ResourceTable -`Write` 强类型重载,取代当前 `Write(uint, params IBindableResource[])` + 运行时 type-switch: - ```csharp public abstract class ResourceTable { public void Write(uint binding, BufferRange range); - public void Write(uint binding, TextureSubresourceRange range); + public void Write(uint binding, TextureView view); public void Write(uint binding, Sampler sampler); public void Write(uint binding, ReadOnlySpan ranges); - public void Write(uint binding, ReadOnlySpan ranges); + public void Write(uint binding, ReadOnlySpan views); public void Write(uint binding, ReadOnlySpan samplers); } ``` - `Buffer` / `Texture` 经隐式转换让单资源绑定保持一行。 -- buffer 解释(CBV / SRV-structured / SRV-byteaddress / UAV / typed)由槽位的 `ResourceLayout` 决定,公共面无 `BufferViewType`。 -- 校验:UAV 槽位要求 `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`,shape 与 layout 不匹配在 `Write` 时报错。 +- buffer 解释(CBV / SRV-structured / SRV-byteaddress / UAV / typed)由槽位的 `ResourceLayout` 决定。 +- 校验:UAV 槽位要求 `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`,shape / layout / 格式族不匹配在 `Write` 时报错。 ## 7. SwapChain @@ -417,13 +521,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G } ``` -设计要点: - -- backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板);不公开 `FrameBuffer`,不公开 image index。 +- backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板)。 - `Present(...)` 返回 `CommandSubmission`,与 `CommandBuffer.Submit(...)` 形态对称;接收任意队列的 wait。 -- 当前 image index 完全是后端事务,不在公共面出现。 - -三端均原生支持 `Present(waits) → CommandSubmission` 的形态:DX12 以 `IDXGISwapChain3::Present` 搭配每个 wait 的 `graphicsQueue.Wait(fence, value)`;Vulkan 1.4 用一次桥接 `vkQueueSubmit2`,把 timeline waits 翻译成供 `vkQueuePresentKHR` 消费的 binary `renderFinished` semaphore;Metal 4 为 `commandBuffer.EncodeWaitForEvent(...)` → `commandBuffer.Present(drawable)` → `commandBuffer.Commit()`。 +- 当前 image index 完全是后端事务。 ## 8. 帧循环 @@ -457,7 +557,6 @@ while (running) } : null; - // viewport / scissor 已按 attachment 自动填充;如需自定义可后续覆盖 cmd.BeginRenderPass(colors, depth); // draws cmd.EndRenderPass(); @@ -468,57 +567,9 @@ while (running) } ``` -每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面完全对称。 - -## 9. 三端可行性对照 - -§3–§7 的每个公共原语,三端均有原生对应: - -| 概念 | DX12 | Vulkan 1.4 | Metal 4 | -|---|---|---|---| -| 内联 RenderPass | `ID3D12GraphicsCommandList4::BeginRenderPass` / `EndRenderPass` | `vkCmdBeginRendering` / `vkCmdEndRendering` | `MTL4CommandBuffer::MakeRenderCommandEncoder` / `endEncoding` | -| LoadAction | `RenderPassBeginningAccessType` | `VkAttachmentLoadOp` | `MTLLoadAction` | -| StoreAction(含 Resolve) | `RenderPassEndingAccessType` | `VkAttachmentStoreOp` + `pResolveAttachments` + `resolveMode` | `MTLStoreAction` + `resolveTexture` | -| 显式 Transition(SRV ↔ UAV) | `ResourceBarrier(Transition / UAV)` | `vkCmdPipelineBarrier2`(`VkImageMemoryBarrier2` / `VkBufferMemoryBarrier2`) | `MTL4ComputeCommandEncoder.BarrierAfterEncoderStages` | -| 全局 `MemoryBarrier()` | `ResourceBarrier(UAV)` 全局 | `vkCmdPipelineBarrier2` 配 `VK_ACCESS_2_MEMORY_READ/WRITE_BIT` | `BarrierAfterEncoderStages` 覆盖全阶段 | -| Aspect 推断源 | format → plane index | `Texture.Format` → `VkImageAspectFlags` | format → 自动 | - -## 10. RDG 接口自检 - -RDG 由 RHI 用户自行封装,本节只回答:「本 RHI 是否暴露了 RDG 所需的所有原语?」 - -| RDG 需求 | 本 RHI 是否提供 | 对应入口 | -|---|---|---| -| 显式资源状态转换 | ✅ | `CommandBuffer.Transition(Buffer/Texture, TransitionState)` | -| 全局内存屏障 | ✅ | `CommandBuffer.MemoryBarrier()` | -| 子范围寻址(用作 alias / hazard key) | ✅ | `BufferRange` / `TextureSubresourceRange`(`record struct`,可哈希) | -| 跨队列依赖 | ✅ | `CommandSubmission` waits(`Submit` / `Present` 同形态) | -| 队列完成查询 / 等待 | ✅ | `CommandQueue.WaitForIdle()` / `CommandSubmission.Wait()` | -| 短生命周期 RenderPass | ✅ | 内联 `BeginRenderPass(colorAttachments, depth)` | +每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面对称。 -结论:RDG 实现方可在不修改 RHI 公共面的前提下完成封装。 - -## 11. 互操作 / 原生句柄 - -面向 Skia / DLSS / FSR / RenderDoc / 工具链等需要拿后端原生句柄的场景。设计原则: - -- 公共面只暴露 `nint` + 一组枚举;不出现任何后端类型 / 平台条件属性 -- RHI 不替外部库做事,也不提供任何 escape hatch 让外部代码修改 RHI 的资源状态缓存 -- 互操作 API 是 RHI 内部本就维护的状态的副产品,零额外运行时成本 - -**状态不变契约**:通过 `GetNativeObject(...)` 暴露给外部库的资源,外部库返回后必须将其留在与当前 `TransitionState` 一致的底层状态: - -- DX12:`ShaderResource` → `D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE`;`UnorderedAccess` → `D3D12_RESOURCE_STATE_UNORDERED_ACCESS` -- Vulkan:`ShaderResource` → `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`;`UnorderedAccess` → `VK_IMAGE_LAYOUT_GENERAL` -- Metal 4:状态由 driver 自管,无需配合 - -DLSS / XeSS / FFX-FSR / NRD 这类 compute 后处理库本就遵守该契约(入口前后状态一致)。主动修改 layout 的库(典型如 Skia)不适合共享 cmd,应采用: - -1. **纹理拷贝**(推荐):让外部库在其私有资源上渲染,结束后以 `Texture` 包装其输出(未来 import 接口,见 § 11.5)或直接以原生拷贝 API 搬进 RHI 资源。 -2. **獨占 swapchain**:纯 UI 应用可将整个 swapchain 让渡给外部库,RHI 不参与该 swapchain 的渲染。 -3. **配置外部库还原 layout**:Skia 可通过 `GrBackendTexture::setVkImageLayout` 要求每次 flush 后还原到 RHI 期望的 layout。 - -### 11.1 NativeObjectType 枚举 +## 9. 原生句柄 ```csharp public enum NativeObjectType @@ -556,15 +607,7 @@ public enum NativeObjectType VkBuffer, VkSampler } -``` - -命名沿用各原生 API 既有前缀:DX12 体系按 `Dxgi` / `D3D12` 区分;Metal 4 与旧 Metal 共存时用 `Mtl` / `Mtl4` 区分;Vulkan 一律 `Vk`。`D3D12CpuDescriptorHandle*` 按 view 类型拆开,避免单入口语义模糊。 -非句柄的标量信息(如 `VkQueueFamilyIndex`)也走同一入口,以 `nint` 承载 `uint`,公共面不挂任何平台条件属性。 - -### 11.2 INativeObject 接口 - -```csharp public interface INativeObject { /// 枚举不匹配本对象 / 当前后端时返回 0。 @@ -584,82 +627,6 @@ public abstract class GraphicsResource(GraphicsContext context) : DisposableObje } ``` -所有 `GraphicsResource` 子类(`Buffer` / `Texture` / `Sampler` / `CommandQueue` / `CommandBuffer` / `SwapChain` / `Pipeline` / `Shader` / `ResourceTable` / `ResourceLayout`)一律实现该接口;后端按 `switch` 实现,不感兴趣的子类返回 0。第三方代码可走接口编程,不必区分 context / resource。 - -后端实现示例: - -```csharp -// VKTexture -public override nint GetNativeObject(NativeObjectType type) => type switch -{ - NativeObjectType.VkImage => (nint)Image.Handle, - NativeObjectType.VkImageView => (nint)View.Handle, - _ => 0 -}; - -// DXBuffer -public override nint GetNativeObject(NativeObjectType type) => type switch -{ - NativeObjectType.D3D12Resource => (nint)Resource.Handle, - _ => 0 -}; - -// 暂不暴露的子类 -public override nint GetNativeObject(NativeObjectType type) => 0; -``` - -### 11.3 互操作调用约定 - -互操作不引入专用的 `BeginExternalCommands` / `EndExternalCommands` 动词。这类调用顺序 / RHI 缓存联动问题属于开发期能够一次性保证的事项,RHI 不以运行时 API 兜底。调用方需自行满足: - -1. 调用外部库时不在 `BeginRenderPass` / `EndRenderPass` 之间(典型互操作都是 compute,天然如此)。 -2. 若调用顺序让 RHI 缓存的 PSO / descriptor / VB / IB / viewport / scissor 与外部库可能冲突,由调用方在外部库返回后重新 `SetPipeline` / `PushResourceTable` 等。 -3. **外部库返回后调一次 `cmd.MemoryBarrier()`**(见 § 6)隔离外部写入与后续 RHI 命令。 - -加上 § 11 引言的"状态不变契约",互操作场景仅依赖 `GetNativeObject` + `MemoryBarrier`,无需专用 API。 - -### 11.4 调用方模板 - -```csharp -// === DLSS (DX12) — 共享 cmd,遵守状态不变契约 === -cmd.Transition(colorIn, TransitionState.ShaderResource); -cmd.Transition(colorOut, TransitionState.UnorderedAccess); - -DLSS.Evaluate( - cmd.GetNativeObject(NativeObjectType.D3D12GraphicsCommandList), - colorIn.GetNativeObject(NativeObjectType.D3D12Resource), - colorOut.GetNativeObject(NativeObjectType.D3D12Resource)); -// 返回时:colorIn 仍为 NON_PIXEL_SHADER_RESOURCE,colorOut 仍为 UNORDERED_ACCESS -cmd.MemoryBarrier(); // 隔离 DLSS 写入与后续 RHI 命令 -cmd.Transition(colorOut, TransitionState.ShaderResource); - -// === FSR/FFX (Vulkan) — 共享 cmd,同契约 === -cmd.Transition(colorIn, TransitionState.ShaderResource); -cmd.Transition(colorOut, TransitionState.UnorderedAccess); - -Ffx.Fsr2Dispatch( - cmd.GetNativeObject(NativeObjectType.VkCommandBuffer), - colorIn.GetNativeObject(NativeObjectType.VkImage), - colorOut.GetNativeObject(NativeObjectType.VkImage)); -cmd.MemoryBarrier(); -cmd.Transition(colorOut, TransitionState.ShaderResource); - -// === Skia (Vulkan) — 纹理拷贝路径 === -// Skia 主动修改 layout,不适合共享 cmd 契约。 -// 让 Skia 在其私有 VkImage 上完成绘制,然后在 Skia 自己的 cmd buffer 里 -// 把结果拷贝到一张 RHI 拥有的 Texture(TransferDst | Sampled);Skia 在 -// flush 时 signal 一个 timeline semaphore,由调用方包装为 CommandSubmission -// 传入下一次 RHI Submit 的 wait。由于 SkiaSharp 共享 VkImage / flush 信号 -// 的 API 易变,该集成始终留在调用方侧。 - -// === RenderDoc — 仅 device 句柄 === -nint device = ctx.GetNativeObject(NativeObjectType.D3D12Device); -RenderDocApi.StartFrameCapture(device, IntPtr.Zero); -// ... 渲染一帧 ... -RenderDocApi.EndFrameCapture(device, IntPtr.Zero); -``` - -### 11.5 不做的事 - -- 不提供「导入外部 native 资源」入口(首版);如未来需要:通过 `GraphicsContext.ImportTexture(TextureDesc, nint, TransitionState)` 加入,签名同形态 -- 不提供互操作 scope 动词(`BeginExternalCommands` / `EndExternalCommands`);调用顺序与缓存联动由开发期保证,运行时仅需一次 `MemoryBarrier()`(见 § 11.3) +- 命名沿用各原生 API 既有前缀:DX12 体系按 `Dxgi` / `D3D12` 区分;Metal 4 与旧 Metal 共存时用 `Mtl` / `Mtl4` 区分;Vulkan 一律 `Vk`。`D3D12CpuDescriptorHandle*` 按 view 类型拆开,避免单入口语义模糊。 +- 非句柄的标量信息(如 `VkQueueFamilyIndex`)也走同一入口,以 `nint` 承载 `uint`,公共面不挂任何平台条件属性。 +- 所有 `GraphicsResource` 子类一律实现该接口;不感兴趣的子类返回 0。 From 4863772476f461b2190c152e2060f67a8e1b6058 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 23 Apr 2026 16:44:59 +0800 Subject: [PATCH 032/466] Add core functionality for Zenith.NET - Introduced ValidationMessageArgs class for handling validation messages. - Created Zenith.NET project file with necessary properties. - Implemented ZenithHelper class with various utility methods for texture and pixel format handling. - Added ZenithMarshal class for memory management and string manipulation with native pointers. --- Zenith.NET.slnx | 51 +++++++++--------- .../Experiments/CornellBox/App.cs | 0 .../CornellBox/Assets/Fonts/msyh.ttf | Bin .../CornellBox/Assets/Shaders/.clang-format | 0 .../Assets/Shaders/PathTracing.slang | 0 .../Assets/Shaders/Rasterization.slang | 0 .../Experiments/CornellBox/CornellBox.csproj | 2 +- .../CornellBox/Handlers/CameraHandler.cs | 0 .../CornellBox/Handlers/ImGuiHandler.cs | 0 .../CornellBox/Helpers/CocoaHelper.cs | 0 .../CornellBox/Helpers/CornellBoxGeometry.cs | 0 .../Experiments/CornellBox/Program.cs | 0 .../CornellBox/Properties/launchSettings.json | 0 .../Renderers/PathTracingRenderer.cs | 0 .../Renderers/RasterizationRenderer.cs | 0 .../CornellBox/Renderers/Renderer.cs | 0 .../Experiments/Directory.Packages.props | 2 +- .../MemoryPlayground/MemoryPlayground.csproj | 2 +- .../Experiments/MemoryPlayground/Program.cs | 0 .../Properties/launchSettings.json | 0 .../PlatformDetection.csproj | 2 +- .../Experiments/PlatformDetection/Program.cs | 0 .../Properties/launchSettings.json | 0 sources/{ => Version1}/Experiments/README.md | 0 .../IImGuiPlatformBindings.cs | 0 .../ImGuiColorSpace.cs | 0 .../ImGuiController.cs | 0 .../ImGuiRenderer.cs | 0 .../Zenith.NET.Extensions.ImGui.csproj | 4 +- .../Extensions.cs | 0 .../Zenith.NET.Extensions.ImageSharp.csproj | 4 +- .../Zenith.NET.Extensions.Skia.csproj | 4 +- .../Zenith.NET.Extensions.Slang/Extensions.cs | 0 .../Zenith.NET.Extensions.Slang.csproj | 4 +- .../Zenith.NET.Views.Avalonia/Surface.cs | 0 .../Zenith.NET.Views.Avalonia.csproj | 4 +- .../Zenith.NET.Views.Avalonia/ZenithView.cs | 0 .../Views/Zenith.NET.Views.Maui/Extensions.cs | 0 .../Platforms/Android/MauiZenithView.cs | 0 .../Platforms/MacCatalyst/MauiZenithView.cs | 0 .../Platforms/Windows/D3D.cs | 0 .../Platforms/Windows/D3DTexture.cs | 0 .../Windows/ISwapChainPanelNative.cs | 0 .../Platforms/Windows/MauiZenithView.cs | 0 .../Platforms/iOS/MauiZenithView.cs | 0 .../Zenith.NET.Views.Maui.csproj | 4 +- .../Views/Zenith.NET.Views.Maui/ZenithView.cs | 0 .../Views/Zenith.NET.Views.WPF/D3D.cs | 0 .../Views/Zenith.NET.Views.WPF/D3DTexture.cs | 0 .../Zenith.NET.Views.WPF.csproj | 4 +- .../Views/Zenith.NET.Views.WPF/ZenithView.cs | 0 .../Zenith.NET.Views.WinForms.csproj | 4 +- .../Zenith.NET.Views.WinForms/ZenithView.cs | 0 .../Zenith.NET.Views.WinUI.csproj | 4 +- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 0 .../ZenithView.WinUI.cs | 0 .../Zenith.NET.Views.WinUI/ZenithView.cs | 0 .../Views/Zenith.NET.Views/FrameScheduler.cs | 0 .../Views/Zenith.NET.Views/IZenithView.cs | 0 .../Views/Zenith.NET.Views/RenderEventArgs.cs | 0 .../Views/Zenith.NET.Views/UpdateEventArgs.cs | 0 .../Zenith.NET.Views/Zenith.NET.Views.csproj | 4 +- .../Zenith.NET.Views/ZenithViewHelper.cs | 0 .../DXBottomLevelAccelerationStructure.cs | 0 .../Zenith.NET.DirectX12/DXBuffer.cs | 0 .../Zenith.NET.DirectX12/DXBufferView.cs | 0 .../Zenith.NET.DirectX12/DXCapabilities.cs | 0 .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 0 .../Zenith.NET.DirectX12/DXCommandQueue.cs | 0 .../Zenith.NET.DirectX12/DXComputePipeline.cs | 0 .../DXDescriptorAllocator.cs | 0 .../Zenith.NET.DirectX12/DXDescriptorPool.cs | 0 .../Zenith.NET.DirectX12/DXDescriptorTable.cs | 0 .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 0 .../Zenith.NET.DirectX12/DXFence.cs | 0 .../Zenith.NET.DirectX12/DXFormats.cs | 0 .../Zenith.NET.DirectX12/DXFrameBuffer.cs | 0 .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 0 .../DXGraphicsPipeline.cs | 0 .../Zenith.NET.DirectX12/DXHeap.cs | 0 .../DXMeshShadingPipeline.cs | 0 .../Zenith.NET.DirectX12/DXQueryHeap.cs | 0 .../Zenith.NET.DirectX12/DXResourceTable.cs | 0 .../Zenith.NET.DirectX12/DXSampler.cs | 0 .../Zenith.NET.DirectX12/DXShader.cs | 0 .../Zenith.NET.DirectX12/DXSwapChain.cs | 0 .../DXSwapChainFrameBuffer.cs | 0 .../Zenith.NET.DirectX12/DXTexture.cs | 0 .../Zenith.NET.DirectX12/DXTextureView.cs | 0 .../DXTopLevelAccelerationStructure.cs | 0 .../Zenith.NET.DirectX12/DXValidationLayer.cs | 0 .../Zenith.NET.DirectX12/Extensions.cs | 0 .../PipelineStateStream2.cs | 0 .../Zenith.NET.DirectX12/PixHelpers.cs | 0 .../Zenith.NET.DirectX12/Usings.cs | 0 .../Zenith.NET.DirectX12.csproj | 4 +- .../Zenith.NET.Metal/Extensions.cs | 0 .../MTLBottomLevelAccelerationStructure.cs | 0 .../Zenith.NET.Metal/MTLBuffer.cs | 0 .../Zenith.NET.Metal/MTLBufferView.cs | 0 .../Zenith.NET.Metal/MTLCapabilities.cs | 0 .../Zenith.NET.Metal/MTLCommandBuffer.cs | 0 .../Zenith.NET.Metal/MTLCommandEncoder.cs | 0 .../Zenith.NET.Metal/MTLCommandQueue.cs | 0 .../Zenith.NET.Metal/MTLComputePipeline.cs | 0 .../Zenith.NET.Metal/MTLFence.cs | 0 .../Zenith.NET.Metal/MTLFormats.cs | 0 .../Zenith.NET.Metal/MTLFrameBuffer.cs | 0 .../Zenith.NET.Metal/MTLGraphicsContext.cs | 0 .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 0 .../Zenith.NET.Metal/MTLHeap.cs | 0 .../MTLMeshShadingPipeline.cs | 0 .../Zenith.NET.Metal/MTLQueryHeap.cs | 0 .../Zenith.NET.Metal/MTLResourceTable.cs | 0 .../Zenith.NET.Metal/MTLSampler.cs | 0 .../Zenith.NET.Metal/MTLShader.cs | 0 .../Zenith.NET.Metal/MTLSwapChain.cs | 0 .../MTLSwapChainFrameBuffer.cs | 0 .../Zenith.NET.Metal/MTLTexture.cs | 0 .../Zenith.NET.Metal/MTLTextureView.cs | 0 .../MTLTopLevelAccelerationStructure.cs | 0 .../Zenith.NET.Metal/NSAutorelease.cs | 0 .../{ => Version1}/Zenith.NET.Metal/Usings.cs | 0 .../Zenith.NET.Metal/Zenith.NET.Metal.csproj | 4 +- .../Zenith.NET.Vulkan/Extensions.cs | 0 .../Zenith.NET.Vulkan/KhrRayQuery.cs | 0 .../Zenith.NET.Vulkan/Usings.cs | 0 .../VKBottomLevelAccelerationStructure.cs | 0 .../Zenith.NET.Vulkan/VKBuffer.cs | 0 .../Zenith.NET.Vulkan/VKBufferView.cs | 0 .../Zenith.NET.Vulkan/VKCapabilities.cs | 0 .../Zenith.NET.Vulkan/VKCommandBuffer.cs | 0 .../Zenith.NET.Vulkan/VKCommandQueue.cs | 0 .../Zenith.NET.Vulkan/VKComputePipeline.cs | 0 .../Zenith.NET.Vulkan/VKDeviceMemory.cs | 0 .../Zenith.NET.Vulkan/VKFence.cs | 0 .../Zenith.NET.Vulkan/VKFormats.cs | 0 .../Zenith.NET.Vulkan/VKFrameBuffer.cs | 0 .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 0 .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 0 .../VKMeshShadingPipeline.cs | 0 .../Zenith.NET.Vulkan/VKQueryHeap.cs | 0 .../Zenith.NET.Vulkan/VKResourceTable.cs | 0 .../Zenith.NET.Vulkan/VKSampler.cs | 0 .../Zenith.NET.Vulkan/VKShader.cs | 0 .../Zenith.NET.Vulkan/VKSwapChain.cs | 0 .../VKSwapChainFrameBuffer.cs | 0 .../Zenith.NET.Vulkan/VKTexture.cs | 0 .../Zenith.NET.Vulkan/VKTextureView.cs | 0 .../VKTopLevelAccelerationStructure.cs | 0 .../Zenith.NET.Vulkan/VKValidationLayer.cs | 0 .../Zenith.NET.Vulkan.csproj | 4 +- .../{ => Version1}/Zenith.NET/BlendStates.cs | 0 .../BottomLevelAccelerationStructure.cs | 0 sources/{ => Version1}/Zenith.NET/Buffer.cs | 0 .../{ => Version1}/Zenith.NET/BufferView.cs | 0 .../{ => Version1}/Zenith.NET/Capabilities.cs | 0 .../{ => Version1}/Zenith.NET/ClearValues.cs | 0 .../Zenith.NET/CommandBuffer.cs | 0 .../{ => Version1}/Zenith.NET/CommandQueue.cs | 0 .../Zenith.NET/ComputePipeline.cs | 0 .../Zenith.NET/DepthStencilStates.cs | 0 .../Zenith.NET/DisposableObject.cs | 0 .../Enums/AccelerationStructureBuildFlags.cs | 0 .../Zenith.NET/Enums/AddressMode.cs | 0 .../Zenith.NET/Enums/Backend.cs | 0 .../{ => Version1}/Zenith.NET/Enums/Blend.cs | 0 .../Zenith.NET/Enums/BlendOp.cs | 0 .../Zenith.NET/Enums/BorderColor.cs | 0 .../Zenith.NET/Enums/BufferUsageFlags.cs | 0 .../Zenith.NET/Enums/ClearFlags.cs | 0 .../Zenith.NET/Enums/ColorComponentFlags.cs | 0 .../Zenith.NET/Enums/CommandQueueType.cs | 0 .../Zenith.NET/Enums/ComparisonFunc.cs | 0 .../Zenith.NET/Enums/CullMode.cs | 0 .../Zenith.NET/Enums/ElementFormat.cs | 0 .../Zenith.NET/Enums/ElementSemantic.cs | 0 .../Zenith.NET/Enums/FillMode.cs | 0 .../{ => Version1}/Zenith.NET/Enums/Filter.cs | 0 .../Zenith.NET/Enums/FrontFace.cs | 0 .../Zenith.NET/Enums/IndexFormat.cs | 0 .../Zenith.NET/Enums/MessageSeverity.cs | 0 .../Zenith.NET/Enums/MessageSource.cs | 0 .../Zenith.NET/Enums/PixelFormat.cs | 0 .../Zenith.NET/Enums/PrimitiveTopology.cs | 0 .../Zenith.NET/Enums/QueryType.cs | 0 .../Enums/RayTracingGeometryFlags.cs | 0 .../Enums/RayTracingGeometryType.cs | 0 .../Enums/RayTracingInstanceFlags.cs | 0 .../Zenith.NET/Enums/ResourceType.cs | 0 .../Zenith.NET/Enums/SampleCount.cs | 0 .../Zenith.NET/Enums/ShaderStageFlags.cs | 0 .../Zenith.NET/Enums/StencilOp.cs | 0 .../Zenith.NET/Enums/StringEncoding.cs | 0 .../Zenith.NET/Enums/SurfaceType.cs | 0 .../Zenith.NET/Enums/TextureType.cs | 0 .../Zenith.NET/Enums/TextureUsageFlags.cs | 0 .../{ => Version1}/Zenith.NET/FrameBuffer.cs | 0 .../Zenith.NET/GraphicsContext.cs | 0 .../Zenith.NET/GraphicsPipeline.cs | 0 .../Zenith.NET/GraphicsResource.cs | 0 .../Interfaces/IBindableResource.cs | 0 .../Interfaces/IDisposableObject.cs | 0 .../Zenith.NET/MeshShadingPipeline.cs | 0 sources/{ => Version1}/Zenith.NET/Pipeline.cs | 0 .../{ => Version1}/Zenith.NET/QueryHeap.cs | 0 .../Zenith.NET/RasterizerStates.cs | 0 .../Zenith.NET/ResourceTable.cs | 0 sources/{ => Version1}/Zenith.NET/Sampler.cs | 0 sources/{ => Version1}/Zenith.NET/Shader.cs | 0 .../Zenith.NET/Structs/BlendState.cs | 0 .../Structs/BlendStateRenderTarget.cs | 0 .../BottomLevelAccelerationStructureDesc.cs | 0 .../Zenith.NET/Structs/BufferDesc.cs | 0 .../Zenith.NET/Structs/BufferViewDesc.cs | 0 .../Zenith.NET/Structs/ClearValue.cs | 0 .../Zenith.NET/Structs/ComputePipelineDesc.cs | 0 .../Zenith.NET/Structs/DepthStencilState.cs | 0 .../Zenith.NET/Structs/DepthStencilStateOp.cs | 0 .../Structs/FrameBufferAttachment.cs | 0 .../Zenith.NET/Structs/FrameBufferDesc.cs | 0 .../Structs/GraphicsPipelineDesc.cs | 0 .../Structs/IndirectDispatchArgs.cs | 0 .../Structs/IndirectDispatchMeshArgs.cs | 0 .../Zenith.NET/Structs/IndirectDrawArgs.cs | 0 .../Structs/IndirectDrawIndexedArgs.cs | 0 .../Zenith.NET/Structs/InputElement.cs | 0 .../Zenith.NET/Structs/InputLayout.cs | 0 .../Zenith.NET/Structs/MappedMemory.cs | 0 .../Structs/MeshShadingPipelineDesc.cs | 0 .../Zenith.NET/Structs/Output.cs | 0 .../Zenith.NET/Structs/QueryHeapDesc.cs | 0 .../Zenith.NET/Structs/RasterizerState.cs | 0 .../Zenith.NET/Structs/RayTracingAABBs.cs | 0 .../Zenith.NET/Structs/RayTracingGeometry.cs | 0 .../Zenith.NET/Structs/RayTracingInstance.cs | 0 .../Zenith.NET/Structs/RayTracingTriangles.cs | 0 .../Zenith.NET/Structs/RenderStates.cs | 0 .../Zenith.NET/Structs/ResourceBinding.cs | 0 .../Zenith.NET/Structs/ResourceTableDesc.cs | 0 .../Zenith.NET/Structs/SamplerDesc.cs | 0 .../Zenith.NET/Structs/Scissor.cs | 0 .../Zenith.NET/Structs/ShaderDesc.cs | 0 .../Zenith.NET/Structs/Surface.cs | 0 .../Zenith.NET/Structs/SwapChainDesc.cs | 0 .../Zenith.NET/Structs/TextureDesc.cs | 0 .../Zenith.NET/Structs/TextureExtent.cs | 0 .../Zenith.NET/Structs/TextureOffset.cs | 0 .../Zenith.NET/Structs/TextureSlice.cs | 0 .../Zenith.NET/Structs/TextureViewDesc.cs | 0 .../TopLevelAccelerationStructureDesc.cs | 0 .../Zenith.NET/Structs/Viewport.cs | 0 .../{ => Version1}/Zenith.NET/SwapChain.cs | 0 sources/{ => Version1}/Zenith.NET/Texture.cs | 0 .../{ => Version1}/Zenith.NET/TextureView.cs | 0 .../TopLevelAccelerationStructure.cs | 0 sources/{ => Version1}/Zenith.NET/Uploader.cs | 0 .../Zenith.NET/ValidationLayer.cs | 0 .../Zenith.NET/ValidationMessageArgs.cs | 0 .../Zenith.NET/Zenith.NET.csproj | 4 +- .../{ => Version1}/Zenith.NET/ZenithHelper.cs | 0 .../Zenith.NET/ZenithMarshal.cs | 0 262 files changed, 59 insertions(+), 56 deletions(-) rename sources/{ => Version1}/Experiments/CornellBox/App.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Assets/Fonts/msyh.ttf (100%) rename sources/{ => Version1}/Experiments/CornellBox/Assets/Shaders/.clang-format (100%) rename sources/{ => Version1}/Experiments/CornellBox/Assets/Shaders/PathTracing.slang (100%) rename sources/{ => Version1}/Experiments/CornellBox/Assets/Shaders/Rasterization.slang (100%) rename sources/{ => Version1}/Experiments/CornellBox/CornellBox.csproj (96%) rename sources/{ => Version1}/Experiments/CornellBox/Handlers/CameraHandler.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Handlers/ImGuiHandler.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Helpers/CocoaHelper.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Program.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Properties/launchSettings.json (100%) rename sources/{ => Version1}/Experiments/CornellBox/Renderers/PathTracingRenderer.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Renderers/RasterizationRenderer.cs (100%) rename sources/{ => Version1}/Experiments/CornellBox/Renderers/Renderer.cs (100%) rename sources/{ => Version1}/Experiments/Directory.Packages.props (78%) rename sources/{ => Version1}/Experiments/MemoryPlayground/MemoryPlayground.csproj (87%) rename sources/{ => Version1}/Experiments/MemoryPlayground/Program.cs (100%) rename sources/{ => Version1}/Experiments/MemoryPlayground/Properties/launchSettings.json (100%) rename sources/{ => Version1}/Experiments/PlatformDetection/PlatformDetection.csproj (92%) rename sources/{ => Version1}/Experiments/PlatformDetection/Program.cs (100%) rename sources/{ => Version1}/Experiments/PlatformDetection/Properties/launchSettings.json (100%) rename sources/{ => Version1}/Experiments/README.md (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj (78%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj (78%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj (77%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs (100%) rename sources/{ => Version1}/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj (77%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Avalonia/Surface.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj (78%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Avalonia/ZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Extensions.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj (90%) rename sources/{ => Version1}/Views/Zenith.NET.Views.Maui/ZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WPF/D3D.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WPF/D3DTexture.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj (84%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WPF/ZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj (80%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WinForms/ZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj (90%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views.WinUI/ZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views/FrameScheduler.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views/IZenithView.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views/RenderEventArgs.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views/UpdateEventArgs.cs (100%) rename sources/{ => Version1}/Views/Zenith.NET.Views/Zenith.NET.Views.csproj (72%) rename sources/{ => Version1}/Views/Zenith.NET.Views/ZenithViewHelper.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXBufferView.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXCapabilities.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXCommandBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXCommandQueue.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXComputePipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXDescriptorAllocator.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXDescriptorPool.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXDescriptorTable.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXDescriptorToken.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXFence.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXFormats.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXFrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXGraphicsContext.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXGraphicsPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXHeap.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXQueryHeap.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXResourceTable.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXSampler.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXShader.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXSwapChain.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXTexture.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXTextureView.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/DXValidationLayer.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/Extensions.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/PipelineStateStream2.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/PixHelpers.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/Usings.cs (100%) rename sources/{ => Version1}/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj (78%) rename sources/{ => Version1}/Zenith.NET.Metal/Extensions.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLBufferView.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLCapabilities.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLCommandBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLCommandEncoder.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLCommandQueue.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLComputePipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLFence.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLFormats.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLFrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLGraphicsContext.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLGraphicsPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLHeap.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLMeshShadingPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLQueryHeap.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLResourceTable.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLSampler.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLShader.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLSwapChain.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLTexture.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLTextureView.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/NSAutorelease.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/Usings.cs (100%) rename sources/{ => Version1}/Zenith.NET.Metal/Zenith.NET.Metal.csproj (78%) rename sources/{ => Version1}/Zenith.NET.Vulkan/Extensions.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/KhrRayQuery.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/Usings.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKBufferView.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKCapabilities.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKCommandBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKCommandQueue.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKComputePipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKDeviceMemory.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKFence.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKFormats.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKFrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKGraphicsContext.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKGraphicsPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKQueryHeap.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKResourceTable.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKSampler.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKShader.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKSwapChain.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKTexture.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKTextureView.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/VKValidationLayer.cs (100%) rename sources/{ => Version1}/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj (85%) rename sources/{ => Version1}/Zenith.NET/BlendStates.cs (100%) rename sources/{ => Version1}/Zenith.NET/BottomLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET/Buffer.cs (100%) rename sources/{ => Version1}/Zenith.NET/BufferView.cs (100%) rename sources/{ => Version1}/Zenith.NET/Capabilities.cs (100%) rename sources/{ => Version1}/Zenith.NET/ClearValues.cs (100%) rename sources/{ => Version1}/Zenith.NET/CommandBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET/CommandQueue.cs (100%) rename sources/{ => Version1}/Zenith.NET/ComputePipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET/DepthStencilStates.cs (100%) rename sources/{ => Version1}/Zenith.NET/DisposableObject.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/AddressMode.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/Backend.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/Blend.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/BlendOp.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/BorderColor.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/BufferUsageFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ClearFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ColorComponentFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/CommandQueueType.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ComparisonFunc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/CullMode.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ElementFormat.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ElementSemantic.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/FillMode.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/Filter.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/FrontFace.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/IndexFormat.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/MessageSeverity.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/MessageSource.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/PixelFormat.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/PrimitiveTopology.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/QueryType.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/RayTracingGeometryFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/RayTracingGeometryType.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/RayTracingInstanceFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ResourceType.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/SampleCount.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/ShaderStageFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/StencilOp.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/StringEncoding.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/SurfaceType.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/TextureType.cs (100%) rename sources/{ => Version1}/Zenith.NET/Enums/TextureUsageFlags.cs (100%) rename sources/{ => Version1}/Zenith.NET/FrameBuffer.cs (100%) rename sources/{ => Version1}/Zenith.NET/GraphicsContext.cs (100%) rename sources/{ => Version1}/Zenith.NET/GraphicsPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET/GraphicsResource.cs (100%) rename sources/{ => Version1}/Zenith.NET/Interfaces/IBindableResource.cs (100%) rename sources/{ => Version1}/Zenith.NET/Interfaces/IDisposableObject.cs (100%) rename sources/{ => Version1}/Zenith.NET/MeshShadingPipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET/Pipeline.cs (100%) rename sources/{ => Version1}/Zenith.NET/QueryHeap.cs (100%) rename sources/{ => Version1}/Zenith.NET/RasterizerStates.cs (100%) rename sources/{ => Version1}/Zenith.NET/ResourceTable.cs (100%) rename sources/{ => Version1}/Zenith.NET/Sampler.cs (100%) rename sources/{ => Version1}/Zenith.NET/Shader.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/BlendState.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/BlendStateRenderTarget.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/BufferDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/BufferViewDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/ClearValue.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/ComputePipelineDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/DepthStencilState.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/DepthStencilStateOp.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/FrameBufferAttachment.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/FrameBufferDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/GraphicsPipelineDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/IndirectDispatchArgs.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/IndirectDrawArgs.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/InputElement.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/InputLayout.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/MappedMemory.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/MeshShadingPipelineDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/Output.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/QueryHeapDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/RasterizerState.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/RayTracingAABBs.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/RayTracingGeometry.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/RayTracingInstance.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/RayTracingTriangles.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/RenderStates.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/ResourceBinding.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/ResourceTableDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/SamplerDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/Scissor.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/ShaderDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/Surface.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/SwapChainDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/TextureDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/TextureExtent.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/TextureOffset.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/TextureSlice.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/TextureViewDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs (100%) rename sources/{ => Version1}/Zenith.NET/Structs/Viewport.cs (100%) rename sources/{ => Version1}/Zenith.NET/SwapChain.cs (100%) rename sources/{ => Version1}/Zenith.NET/Texture.cs (100%) rename sources/{ => Version1}/Zenith.NET/TextureView.cs (100%) rename sources/{ => Version1}/Zenith.NET/TopLevelAccelerationStructure.cs (100%) rename sources/{ => Version1}/Zenith.NET/Uploader.cs (100%) rename sources/{ => Version1}/Zenith.NET/ValidationLayer.cs (100%) rename sources/{ => Version1}/Zenith.NET/ValidationMessageArgs.cs (100%) rename sources/{ => Version1}/Zenith.NET/Zenith.NET.csproj (64%) rename sources/{ => Version1}/Zenith.NET/ZenithHelper.cs (100%) rename sources/{ => Version1}/Zenith.NET/ZenithMarshal.cs (100%) diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 5aff3b15..5ddb7d49 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -1,31 +1,34 @@ - - - - - - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + \ No newline at end of file diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Version1/Experiments/CornellBox/App.cs similarity index 100% rename from sources/Experiments/CornellBox/App.cs rename to sources/Version1/Experiments/CornellBox/App.cs diff --git a/sources/Experiments/CornellBox/Assets/Fonts/msyh.ttf b/sources/Version1/Experiments/CornellBox/Assets/Fonts/msyh.ttf similarity index 100% rename from sources/Experiments/CornellBox/Assets/Fonts/msyh.ttf rename to sources/Version1/Experiments/CornellBox/Assets/Fonts/msyh.ttf diff --git a/sources/Experiments/CornellBox/Assets/Shaders/.clang-format b/sources/Version1/Experiments/CornellBox/Assets/Shaders/.clang-format similarity index 100% rename from sources/Experiments/CornellBox/Assets/Shaders/.clang-format rename to sources/Version1/Experiments/CornellBox/Assets/Shaders/.clang-format diff --git a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Version1/Experiments/CornellBox/Assets/Shaders/PathTracing.slang similarity index 100% rename from sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang rename to sources/Version1/Experiments/CornellBox/Assets/Shaders/PathTracing.slang diff --git a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Version1/Experiments/CornellBox/Assets/Shaders/Rasterization.slang similarity index 100% rename from sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang rename to sources/Version1/Experiments/CornellBox/Assets/Shaders/Rasterization.slang diff --git a/sources/Experiments/CornellBox/CornellBox.csproj b/sources/Version1/Experiments/CornellBox/CornellBox.csproj similarity index 96% rename from sources/Experiments/CornellBox/CornellBox.csproj rename to sources/Version1/Experiments/CornellBox/CornellBox.csproj index 65e6ad06..59ed9f96 100644 --- a/sources/Experiments/CornellBox/CornellBox.csproj +++ b/sources/Version1/Experiments/CornellBox/CornellBox.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/sources/Experiments/CornellBox/Handlers/CameraHandler.cs b/sources/Version1/Experiments/CornellBox/Handlers/CameraHandler.cs similarity index 100% rename from sources/Experiments/CornellBox/Handlers/CameraHandler.cs rename to sources/Version1/Experiments/CornellBox/Handlers/CameraHandler.cs diff --git a/sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs b/sources/Version1/Experiments/CornellBox/Handlers/ImGuiHandler.cs similarity index 100% rename from sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs rename to sources/Version1/Experiments/CornellBox/Handlers/ImGuiHandler.cs diff --git a/sources/Experiments/CornellBox/Helpers/CocoaHelper.cs b/sources/Version1/Experiments/CornellBox/Helpers/CocoaHelper.cs similarity index 100% rename from sources/Experiments/CornellBox/Helpers/CocoaHelper.cs rename to sources/Version1/Experiments/CornellBox/Helpers/CocoaHelper.cs diff --git a/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs b/sources/Version1/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs similarity index 100% rename from sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs rename to sources/Version1/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs diff --git a/sources/Experiments/CornellBox/Program.cs b/sources/Version1/Experiments/CornellBox/Program.cs similarity index 100% rename from sources/Experiments/CornellBox/Program.cs rename to sources/Version1/Experiments/CornellBox/Program.cs diff --git a/sources/Experiments/CornellBox/Properties/launchSettings.json b/sources/Version1/Experiments/CornellBox/Properties/launchSettings.json similarity index 100% rename from sources/Experiments/CornellBox/Properties/launchSettings.json rename to sources/Version1/Experiments/CornellBox/Properties/launchSettings.json diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Version1/Experiments/CornellBox/Renderers/PathTracingRenderer.cs similarity index 100% rename from sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs rename to sources/Version1/Experiments/CornellBox/Renderers/PathTracingRenderer.cs diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Version1/Experiments/CornellBox/Renderers/RasterizationRenderer.cs similarity index 100% rename from sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs rename to sources/Version1/Experiments/CornellBox/Renderers/RasterizationRenderer.cs diff --git a/sources/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Version1/Experiments/CornellBox/Renderers/Renderer.cs similarity index 100% rename from sources/Experiments/CornellBox/Renderers/Renderer.cs rename to sources/Version1/Experiments/CornellBox/Renderers/Renderer.cs diff --git a/sources/Experiments/Directory.Packages.props b/sources/Version1/Experiments/Directory.Packages.props similarity index 78% rename from sources/Experiments/Directory.Packages.props rename to sources/Version1/Experiments/Directory.Packages.props index 6f64c1d7..3cec044c 100644 --- a/sources/Experiments/Directory.Packages.props +++ b/sources/Version1/Experiments/Directory.Packages.props @@ -1,6 +1,6 @@  - + diff --git a/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj b/sources/Version1/Experiments/MemoryPlayground/MemoryPlayground.csproj similarity index 87% rename from sources/Experiments/MemoryPlayground/MemoryPlayground.csproj rename to sources/Version1/Experiments/MemoryPlayground/MemoryPlayground.csproj index 1a30047f..bbc61e10 100644 --- a/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj +++ b/sources/Version1/Experiments/MemoryPlayground/MemoryPlayground.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/sources/Experiments/MemoryPlayground/Program.cs b/sources/Version1/Experiments/MemoryPlayground/Program.cs similarity index 100% rename from sources/Experiments/MemoryPlayground/Program.cs rename to sources/Version1/Experiments/MemoryPlayground/Program.cs diff --git a/sources/Experiments/MemoryPlayground/Properties/launchSettings.json b/sources/Version1/Experiments/MemoryPlayground/Properties/launchSettings.json similarity index 100% rename from sources/Experiments/MemoryPlayground/Properties/launchSettings.json rename to sources/Version1/Experiments/MemoryPlayground/Properties/launchSettings.json diff --git a/sources/Experiments/PlatformDetection/PlatformDetection.csproj b/sources/Version1/Experiments/PlatformDetection/PlatformDetection.csproj similarity index 92% rename from sources/Experiments/PlatformDetection/PlatformDetection.csproj rename to sources/Version1/Experiments/PlatformDetection/PlatformDetection.csproj index 76eab2af..0108a0a1 100644 --- a/sources/Experiments/PlatformDetection/PlatformDetection.csproj +++ b/sources/Version1/Experiments/PlatformDetection/PlatformDetection.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/sources/Experiments/PlatformDetection/Program.cs b/sources/Version1/Experiments/PlatformDetection/Program.cs similarity index 100% rename from sources/Experiments/PlatformDetection/Program.cs rename to sources/Version1/Experiments/PlatformDetection/Program.cs diff --git a/sources/Experiments/PlatformDetection/Properties/launchSettings.json b/sources/Version1/Experiments/PlatformDetection/Properties/launchSettings.json similarity index 100% rename from sources/Experiments/PlatformDetection/Properties/launchSettings.json rename to sources/Version1/Experiments/PlatformDetection/Properties/launchSettings.json diff --git a/sources/Experiments/README.md b/sources/Version1/Experiments/README.md similarity index 100% rename from sources/Experiments/README.md rename to sources/Version1/Experiments/README.md diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs b/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs similarity index 100% rename from sources/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs b/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs similarity index 100% rename from sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs b/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs similarity index 100% rename from sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs similarity index 100% rename from sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj b/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj similarity index 78% rename from sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj index 74c150c9..61a98dad 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj +++ b/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs similarity index 100% rename from sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj b/sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj similarity index 78% rename from sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj rename to sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj index 9b04ef4c..9186d7c8 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj +++ b/sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj b/sources/Version1/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj similarity index 77% rename from sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj rename to sources/Version1/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj index 5bb54b48..b438a665 100644 --- a/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj +++ b/sources/Version1/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs b/sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs similarity index 100% rename from sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs rename to sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj b/sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj similarity index 77% rename from sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj rename to sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj index f7e52318..0027a99e 100644 --- a/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj +++ b/sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Version1/Views/Zenith.NET.Views.Avalonia/Surface.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Avalonia/Surface.cs rename to sources/Version1/Views/Zenith.NET.Views.Avalonia/Surface.cs diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj b/sources/Version1/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj similarity index 78% rename from sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj rename to sources/Version1/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj index 60649f30..2c663920 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj +++ b/sources/Version1/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.Avalonia/ZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.Avalonia/ZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Extensions.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Extensions.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Extensions.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Extensions.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj b/sources/Version1/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj similarity index 90% rename from sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj rename to sources/Version1/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj index bda7142e..4ee114d1 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj +++ b/sources/Version1/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework)-android;$(StandardTargetFramework)-ios;$(StandardTargetFramework)-maccatalyst @@ -19,6 +19,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.Maui/ZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.Maui/ZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.Maui/ZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.WPF/D3D.cs b/sources/Version1/Views/Zenith.NET.Views.WPF/D3D.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WPF/D3D.cs rename to sources/Version1/Views/Zenith.NET.Views.WPF/D3D.cs diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Version1/Views/Zenith.NET.Views.WPF/D3DTexture.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs rename to sources/Version1/Views/Zenith.NET.Views.WPF/D3DTexture.cs diff --git a/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj b/sources/Version1/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj similarity index 84% rename from sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj rename to sources/Version1/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj index 93ef8433..5f5bceca 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj +++ b/sources/Version1/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework)-windows @@ -15,6 +15,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.WPF/ZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WPF/ZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.WPF/ZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj b/sources/Version1/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj similarity index 80% rename from sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj rename to sources/Version1/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj index 3a1ad8e3..f155f384 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj +++ b/sources/Version1/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework)-windows @@ -10,6 +10,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.WinForms/ZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.WinForms/ZenithView.cs diff --git a/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj b/sources/Version1/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj similarity index 90% rename from sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj rename to sources/Version1/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj index 52329452..b14c982a 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj +++ b/sources/Version1/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -23,6 +23,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs rename to sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs rename to sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.cs b/sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views.WinUI/ZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.cs diff --git a/sources/Views/Zenith.NET.Views/FrameScheduler.cs b/sources/Version1/Views/Zenith.NET.Views/FrameScheduler.cs similarity index 100% rename from sources/Views/Zenith.NET.Views/FrameScheduler.cs rename to sources/Version1/Views/Zenith.NET.Views/FrameScheduler.cs diff --git a/sources/Views/Zenith.NET.Views/IZenithView.cs b/sources/Version1/Views/Zenith.NET.Views/IZenithView.cs similarity index 100% rename from sources/Views/Zenith.NET.Views/IZenithView.cs rename to sources/Version1/Views/Zenith.NET.Views/IZenithView.cs diff --git a/sources/Views/Zenith.NET.Views/RenderEventArgs.cs b/sources/Version1/Views/Zenith.NET.Views/RenderEventArgs.cs similarity index 100% rename from sources/Views/Zenith.NET.Views/RenderEventArgs.cs rename to sources/Version1/Views/Zenith.NET.Views/RenderEventArgs.cs diff --git a/sources/Views/Zenith.NET.Views/UpdateEventArgs.cs b/sources/Version1/Views/Zenith.NET.Views/UpdateEventArgs.cs similarity index 100% rename from sources/Views/Zenith.NET.Views/UpdateEventArgs.cs rename to sources/Version1/Views/Zenith.NET.Views/UpdateEventArgs.cs diff --git a/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj b/sources/Version1/Views/Zenith.NET.Views/Zenith.NET.Views.csproj similarity index 72% rename from sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj rename to sources/Version1/Views/Zenith.NET.Views/Zenith.NET.Views.csproj index e2df005b..a9be9526 100644 --- a/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj +++ b/sources/Version1/Views/Zenith.NET.Views/Zenith.NET.Views.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -8,6 +8,6 @@ - + diff --git a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs b/sources/Version1/Views/Zenith.NET.Views/ZenithViewHelper.cs similarity index 100% rename from sources/Views/Zenith.NET.Views/ZenithViewHelper.cs rename to sources/Version1/Views/Zenith.NET.Views/ZenithViewHelper.cs diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Version1/Zenith.NET.DirectX12/DXBuffer.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXBuffer.cs rename to sources/Version1/Zenith.NET.DirectX12/DXBuffer.cs diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Version1/Zenith.NET.DirectX12/DXBufferView.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXBufferView.cs rename to sources/Version1/Zenith.NET.DirectX12/DXBufferView.cs diff --git a/sources/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Version1/Zenith.NET.DirectX12/DXCapabilities.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXCapabilities.cs rename to sources/Version1/Zenith.NET.DirectX12/DXCapabilities.cs diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Version1/Zenith.NET.DirectX12/DXCommandBuffer.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXCommandBuffer.cs rename to sources/Version1/Zenith.NET.DirectX12/DXCommandBuffer.cs diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Version1/Zenith.NET.DirectX12/DXCommandQueue.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXCommandQueue.cs rename to sources/Version1/Zenith.NET.DirectX12/DXCommandQueue.cs diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Version1/Zenith.NET.DirectX12/DXComputePipeline.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXComputePipeline.cs rename to sources/Version1/Zenith.NET.DirectX12/DXComputePipeline.cs diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs b/sources/Version1/Zenith.NET.DirectX12/DXDescriptorAllocator.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs rename to sources/Version1/Zenith.NET.DirectX12/DXDescriptorAllocator.cs diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorPool.cs b/sources/Version1/Zenith.NET.DirectX12/DXDescriptorPool.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXDescriptorPool.cs rename to sources/Version1/Zenith.NET.DirectX12/DXDescriptorPool.cs diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorTable.cs b/sources/Version1/Zenith.NET.DirectX12/DXDescriptorTable.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXDescriptorTable.cs rename to sources/Version1/Zenith.NET.DirectX12/DXDescriptorTable.cs diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Version1/Zenith.NET.DirectX12/DXDescriptorToken.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXDescriptorToken.cs rename to sources/Version1/Zenith.NET.DirectX12/DXDescriptorToken.cs diff --git a/sources/Zenith.NET.DirectX12/DXFence.cs b/sources/Version1/Zenith.NET.DirectX12/DXFence.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXFence.cs rename to sources/Version1/Zenith.NET.DirectX12/DXFence.cs diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Version1/Zenith.NET.DirectX12/DXFormats.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXFormats.cs rename to sources/Version1/Zenith.NET.DirectX12/DXFormats.cs diff --git a/sources/Zenith.NET.DirectX12/DXFrameBuffer.cs b/sources/Version1/Zenith.NET.DirectX12/DXFrameBuffer.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXFrameBuffer.cs rename to sources/Version1/Zenith.NET.DirectX12/DXFrameBuffer.cs diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Version1/Zenith.NET.DirectX12/DXGraphicsContext.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXGraphicsContext.cs rename to sources/Version1/Zenith.NET.DirectX12/DXGraphicsContext.cs diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Version1/Zenith.NET.DirectX12/DXGraphicsPipeline.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs rename to sources/Version1/Zenith.NET.DirectX12/DXGraphicsPipeline.cs diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Version1/Zenith.NET.DirectX12/DXHeap.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXHeap.cs rename to sources/Version1/Zenith.NET.DirectX12/DXHeap.cs diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Version1/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs rename to sources/Version1/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Version1/Zenith.NET.DirectX12/DXQueryHeap.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXQueryHeap.cs rename to sources/Version1/Zenith.NET.DirectX12/DXQueryHeap.cs diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Version1/Zenith.NET.DirectX12/DXResourceTable.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXResourceTable.cs rename to sources/Version1/Zenith.NET.DirectX12/DXResourceTable.cs diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Version1/Zenith.NET.DirectX12/DXSampler.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXSampler.cs rename to sources/Version1/Zenith.NET.DirectX12/DXSampler.cs diff --git a/sources/Zenith.NET.DirectX12/DXShader.cs b/sources/Version1/Zenith.NET.DirectX12/DXShader.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXShader.cs rename to sources/Version1/Zenith.NET.DirectX12/DXShader.cs diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Version1/Zenith.NET.DirectX12/DXSwapChain.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXSwapChain.cs rename to sources/Version1/Zenith.NET.DirectX12/DXSwapChain.cs diff --git a/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs b/sources/Version1/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs rename to sources/Version1/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Version1/Zenith.NET.DirectX12/DXTexture.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXTexture.cs rename to sources/Version1/Zenith.NET.DirectX12/DXTexture.cs diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Version1/Zenith.NET.DirectX12/DXTextureView.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXTextureView.cs rename to sources/Version1/Zenith.NET.DirectX12/DXTextureView.cs diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Version1/Zenith.NET.DirectX12/DXValidationLayer.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/DXValidationLayer.cs rename to sources/Version1/Zenith.NET.DirectX12/DXValidationLayer.cs diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Version1/Zenith.NET.DirectX12/Extensions.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/Extensions.cs rename to sources/Version1/Zenith.NET.DirectX12/Extensions.cs diff --git a/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs b/sources/Version1/Zenith.NET.DirectX12/PipelineStateStream2.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/PipelineStateStream2.cs rename to sources/Version1/Zenith.NET.DirectX12/PipelineStateStream2.cs diff --git a/sources/Zenith.NET.DirectX12/PixHelpers.cs b/sources/Version1/Zenith.NET.DirectX12/PixHelpers.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/PixHelpers.cs rename to sources/Version1/Zenith.NET.DirectX12/PixHelpers.cs diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Version1/Zenith.NET.DirectX12/Usings.cs similarity index 100% rename from sources/Zenith.NET.DirectX12/Usings.cs rename to sources/Version1/Zenith.NET.DirectX12/Usings.cs diff --git a/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj b/sources/Version1/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj similarity index 78% rename from sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj rename to sources/Version1/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj index 92a72466..bc0501fd 100644 --- a/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj +++ b/sources/Version1/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Version1/Zenith.NET.Metal/Extensions.cs similarity index 100% rename from sources/Zenith.NET.Metal/Extensions.cs rename to sources/Version1/Zenith.NET.Metal/Extensions.cs diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Version1/Zenith.NET.Metal/MTLBuffer.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLBuffer.cs rename to sources/Version1/Zenith.NET.Metal/MTLBuffer.cs diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Version1/Zenith.NET.Metal/MTLBufferView.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLBufferView.cs rename to sources/Version1/Zenith.NET.Metal/MTLBufferView.cs diff --git a/sources/Zenith.NET.Metal/MTLCapabilities.cs b/sources/Version1/Zenith.NET.Metal/MTLCapabilities.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLCapabilities.cs rename to sources/Version1/Zenith.NET.Metal/MTLCapabilities.cs diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Version1/Zenith.NET.Metal/MTLCommandBuffer.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLCommandBuffer.cs rename to sources/Version1/Zenith.NET.Metal/MTLCommandBuffer.cs diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Version1/Zenith.NET.Metal/MTLCommandEncoder.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLCommandEncoder.cs rename to sources/Version1/Zenith.NET.Metal/MTLCommandEncoder.cs diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Version1/Zenith.NET.Metal/MTLCommandQueue.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLCommandQueue.cs rename to sources/Version1/Zenith.NET.Metal/MTLCommandQueue.cs diff --git a/sources/Zenith.NET.Metal/MTLComputePipeline.cs b/sources/Version1/Zenith.NET.Metal/MTLComputePipeline.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLComputePipeline.cs rename to sources/Version1/Zenith.NET.Metal/MTLComputePipeline.cs diff --git a/sources/Zenith.NET.Metal/MTLFence.cs b/sources/Version1/Zenith.NET.Metal/MTLFence.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLFence.cs rename to sources/Version1/Zenith.NET.Metal/MTLFence.cs diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Version1/Zenith.NET.Metal/MTLFormats.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLFormats.cs rename to sources/Version1/Zenith.NET.Metal/MTLFormats.cs diff --git a/sources/Zenith.NET.Metal/MTLFrameBuffer.cs b/sources/Version1/Zenith.NET.Metal/MTLFrameBuffer.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLFrameBuffer.cs rename to sources/Version1/Zenith.NET.Metal/MTLFrameBuffer.cs diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Version1/Zenith.NET.Metal/MTLGraphicsContext.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLGraphicsContext.cs rename to sources/Version1/Zenith.NET.Metal/MTLGraphicsContext.cs diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Version1/Zenith.NET.Metal/MTLGraphicsPipeline.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs rename to sources/Version1/Zenith.NET.Metal/MTLGraphicsPipeline.cs diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Version1/Zenith.NET.Metal/MTLHeap.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLHeap.cs rename to sources/Version1/Zenith.NET.Metal/MTLHeap.cs diff --git a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs b/sources/Version1/Zenith.NET.Metal/MTLMeshShadingPipeline.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs rename to sources/Version1/Zenith.NET.Metal/MTLMeshShadingPipeline.cs diff --git a/sources/Zenith.NET.Metal/MTLQueryHeap.cs b/sources/Version1/Zenith.NET.Metal/MTLQueryHeap.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLQueryHeap.cs rename to sources/Version1/Zenith.NET.Metal/MTLQueryHeap.cs diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Version1/Zenith.NET.Metal/MTLResourceTable.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLResourceTable.cs rename to sources/Version1/Zenith.NET.Metal/MTLResourceTable.cs diff --git a/sources/Zenith.NET.Metal/MTLSampler.cs b/sources/Version1/Zenith.NET.Metal/MTLSampler.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLSampler.cs rename to sources/Version1/Zenith.NET.Metal/MTLSampler.cs diff --git a/sources/Zenith.NET.Metal/MTLShader.cs b/sources/Version1/Zenith.NET.Metal/MTLShader.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLShader.cs rename to sources/Version1/Zenith.NET.Metal/MTLShader.cs diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Version1/Zenith.NET.Metal/MTLSwapChain.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLSwapChain.cs rename to sources/Version1/Zenith.NET.Metal/MTLSwapChain.cs diff --git a/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs b/sources/Version1/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs rename to sources/Version1/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Version1/Zenith.NET.Metal/MTLTexture.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLTexture.cs rename to sources/Version1/Zenith.NET.Metal/MTLTexture.cs diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Version1/Zenith.NET.Metal/MTLTextureView.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLTextureView.cs rename to sources/Version1/Zenith.NET.Metal/MTLTextureView.cs diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.Metal/NSAutorelease.cs b/sources/Version1/Zenith.NET.Metal/NSAutorelease.cs similarity index 100% rename from sources/Zenith.NET.Metal/NSAutorelease.cs rename to sources/Version1/Zenith.NET.Metal/NSAutorelease.cs diff --git a/sources/Zenith.NET.Metal/Usings.cs b/sources/Version1/Zenith.NET.Metal/Usings.cs similarity index 100% rename from sources/Zenith.NET.Metal/Usings.cs rename to sources/Version1/Zenith.NET.Metal/Usings.cs diff --git a/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj b/sources/Version1/Zenith.NET.Metal/Zenith.NET.Metal.csproj similarity index 78% rename from sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj rename to sources/Version1/Zenith.NET.Metal/Zenith.NET.Metal.csproj index ecf99eae..ff9099d2 100644 --- a/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj +++ b/sources/Version1/Zenith.NET.Metal/Zenith.NET.Metal.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -12,6 +12,6 @@ - + diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Version1/Zenith.NET.Vulkan/Extensions.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/Extensions.cs rename to sources/Version1/Zenith.NET.Vulkan/Extensions.cs diff --git a/sources/Zenith.NET.Vulkan/KhrRayQuery.cs b/sources/Version1/Zenith.NET.Vulkan/KhrRayQuery.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/KhrRayQuery.cs rename to sources/Version1/Zenith.NET.Vulkan/KhrRayQuery.cs diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Version1/Zenith.NET.Vulkan/Usings.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/Usings.cs rename to sources/Version1/Zenith.NET.Vulkan/Usings.cs diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Version1/Zenith.NET.Vulkan/VKBuffer.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKBuffer.cs rename to sources/Version1/Zenith.NET.Vulkan/VKBuffer.cs diff --git a/sources/Zenith.NET.Vulkan/VKBufferView.cs b/sources/Version1/Zenith.NET.Vulkan/VKBufferView.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKBufferView.cs rename to sources/Version1/Zenith.NET.Vulkan/VKBufferView.cs diff --git a/sources/Zenith.NET.Vulkan/VKCapabilities.cs b/sources/Version1/Zenith.NET.Vulkan/VKCapabilities.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKCapabilities.cs rename to sources/Version1/Zenith.NET.Vulkan/VKCapabilities.cs diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Version1/Zenith.NET.Vulkan/VKCommandBuffer.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKCommandBuffer.cs rename to sources/Version1/Zenith.NET.Vulkan/VKCommandBuffer.cs diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Version1/Zenith.NET.Vulkan/VKCommandQueue.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKCommandQueue.cs rename to sources/Version1/Zenith.NET.Vulkan/VKCommandQueue.cs diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Version1/Zenith.NET.Vulkan/VKComputePipeline.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKComputePipeline.cs rename to sources/Version1/Zenith.NET.Vulkan/VKComputePipeline.cs diff --git a/sources/Zenith.NET.Vulkan/VKDeviceMemory.cs b/sources/Version1/Zenith.NET.Vulkan/VKDeviceMemory.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKDeviceMemory.cs rename to sources/Version1/Zenith.NET.Vulkan/VKDeviceMemory.cs diff --git a/sources/Zenith.NET.Vulkan/VKFence.cs b/sources/Version1/Zenith.NET.Vulkan/VKFence.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKFence.cs rename to sources/Version1/Zenith.NET.Vulkan/VKFence.cs diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Version1/Zenith.NET.Vulkan/VKFormats.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKFormats.cs rename to sources/Version1/Zenith.NET.Vulkan/VKFormats.cs diff --git a/sources/Zenith.NET.Vulkan/VKFrameBuffer.cs b/sources/Version1/Zenith.NET.Vulkan/VKFrameBuffer.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKFrameBuffer.cs rename to sources/Version1/Zenith.NET.Vulkan/VKFrameBuffer.cs diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Version1/Zenith.NET.Vulkan/VKGraphicsContext.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKGraphicsContext.cs rename to sources/Version1/Zenith.NET.Vulkan/VKGraphicsContext.cs diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Version1/Zenith.NET.Vulkan/VKGraphicsPipeline.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs rename to sources/Version1/Zenith.NET.Vulkan/VKGraphicsPipeline.cs diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Version1/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs rename to sources/Version1/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs diff --git a/sources/Zenith.NET.Vulkan/VKQueryHeap.cs b/sources/Version1/Zenith.NET.Vulkan/VKQueryHeap.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKQueryHeap.cs rename to sources/Version1/Zenith.NET.Vulkan/VKQueryHeap.cs diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Version1/Zenith.NET.Vulkan/VKResourceTable.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKResourceTable.cs rename to sources/Version1/Zenith.NET.Vulkan/VKResourceTable.cs diff --git a/sources/Zenith.NET.Vulkan/VKSampler.cs b/sources/Version1/Zenith.NET.Vulkan/VKSampler.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKSampler.cs rename to sources/Version1/Zenith.NET.Vulkan/VKSampler.cs diff --git a/sources/Zenith.NET.Vulkan/VKShader.cs b/sources/Version1/Zenith.NET.Vulkan/VKShader.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKShader.cs rename to sources/Version1/Zenith.NET.Vulkan/VKShader.cs diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Version1/Zenith.NET.Vulkan/VKSwapChain.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKSwapChain.cs rename to sources/Version1/Zenith.NET.Vulkan/VKSwapChain.cs diff --git a/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs b/sources/Version1/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs rename to sources/Version1/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Version1/Zenith.NET.Vulkan/VKTexture.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKTexture.cs rename to sources/Version1/Zenith.NET.Vulkan/VKTexture.cs diff --git a/sources/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Version1/Zenith.NET.Vulkan/VKTextureView.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKTextureView.cs rename to sources/Version1/Zenith.NET.Vulkan/VKTextureView.cs diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Version1/Zenith.NET.Vulkan/VKValidationLayer.cs similarity index 100% rename from sources/Zenith.NET.Vulkan/VKValidationLayer.cs rename to sources/Version1/Zenith.NET.Vulkan/VKValidationLayer.cs diff --git a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj b/sources/Version1/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj similarity index 85% rename from sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj rename to sources/Version1/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj index 39132514..a12a8713 100644 --- a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj +++ b/sources/Version1/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) @@ -15,6 +15,6 @@ - + diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Version1/Zenith.NET/BlendStates.cs similarity index 100% rename from sources/Zenith.NET/BlendStates.cs rename to sources/Version1/Zenith.NET/BlendStates.cs diff --git a/sources/Zenith.NET/BottomLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET/BottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET/BottomLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET/BottomLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Version1/Zenith.NET/Buffer.cs similarity index 100% rename from sources/Zenith.NET/Buffer.cs rename to sources/Version1/Zenith.NET/Buffer.cs diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Version1/Zenith.NET/BufferView.cs similarity index 100% rename from sources/Zenith.NET/BufferView.cs rename to sources/Version1/Zenith.NET/BufferView.cs diff --git a/sources/Zenith.NET/Capabilities.cs b/sources/Version1/Zenith.NET/Capabilities.cs similarity index 100% rename from sources/Zenith.NET/Capabilities.cs rename to sources/Version1/Zenith.NET/Capabilities.cs diff --git a/sources/Zenith.NET/ClearValues.cs b/sources/Version1/Zenith.NET/ClearValues.cs similarity index 100% rename from sources/Zenith.NET/ClearValues.cs rename to sources/Version1/Zenith.NET/ClearValues.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Version1/Zenith.NET/CommandBuffer.cs similarity index 100% rename from sources/Zenith.NET/CommandBuffer.cs rename to sources/Version1/Zenith.NET/CommandBuffer.cs diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Version1/Zenith.NET/CommandQueue.cs similarity index 100% rename from sources/Zenith.NET/CommandQueue.cs rename to sources/Version1/Zenith.NET/CommandQueue.cs diff --git a/sources/Zenith.NET/ComputePipeline.cs b/sources/Version1/Zenith.NET/ComputePipeline.cs similarity index 100% rename from sources/Zenith.NET/ComputePipeline.cs rename to sources/Version1/Zenith.NET/ComputePipeline.cs diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Version1/Zenith.NET/DepthStencilStates.cs similarity index 100% rename from sources/Zenith.NET/DepthStencilStates.cs rename to sources/Version1/Zenith.NET/DepthStencilStates.cs diff --git a/sources/Zenith.NET/DisposableObject.cs b/sources/Version1/Zenith.NET/DisposableObject.cs similarity index 100% rename from sources/Zenith.NET/DisposableObject.cs rename to sources/Version1/Zenith.NET/DisposableObject.cs diff --git a/sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs b/sources/Version1/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs rename to sources/Version1/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs diff --git a/sources/Zenith.NET/Enums/AddressMode.cs b/sources/Version1/Zenith.NET/Enums/AddressMode.cs similarity index 100% rename from sources/Zenith.NET/Enums/AddressMode.cs rename to sources/Version1/Zenith.NET/Enums/AddressMode.cs diff --git a/sources/Zenith.NET/Enums/Backend.cs b/sources/Version1/Zenith.NET/Enums/Backend.cs similarity index 100% rename from sources/Zenith.NET/Enums/Backend.cs rename to sources/Version1/Zenith.NET/Enums/Backend.cs diff --git a/sources/Zenith.NET/Enums/Blend.cs b/sources/Version1/Zenith.NET/Enums/Blend.cs similarity index 100% rename from sources/Zenith.NET/Enums/Blend.cs rename to sources/Version1/Zenith.NET/Enums/Blend.cs diff --git a/sources/Zenith.NET/Enums/BlendOp.cs b/sources/Version1/Zenith.NET/Enums/BlendOp.cs similarity index 100% rename from sources/Zenith.NET/Enums/BlendOp.cs rename to sources/Version1/Zenith.NET/Enums/BlendOp.cs diff --git a/sources/Zenith.NET/Enums/BorderColor.cs b/sources/Version1/Zenith.NET/Enums/BorderColor.cs similarity index 100% rename from sources/Zenith.NET/Enums/BorderColor.cs rename to sources/Version1/Zenith.NET/Enums/BorderColor.cs diff --git a/sources/Zenith.NET/Enums/BufferUsageFlags.cs b/sources/Version1/Zenith.NET/Enums/BufferUsageFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/BufferUsageFlags.cs rename to sources/Version1/Zenith.NET/Enums/BufferUsageFlags.cs diff --git a/sources/Zenith.NET/Enums/ClearFlags.cs b/sources/Version1/Zenith.NET/Enums/ClearFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/ClearFlags.cs rename to sources/Version1/Zenith.NET/Enums/ClearFlags.cs diff --git a/sources/Zenith.NET/Enums/ColorComponentFlags.cs b/sources/Version1/Zenith.NET/Enums/ColorComponentFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/ColorComponentFlags.cs rename to sources/Version1/Zenith.NET/Enums/ColorComponentFlags.cs diff --git a/sources/Zenith.NET/Enums/CommandQueueType.cs b/sources/Version1/Zenith.NET/Enums/CommandQueueType.cs similarity index 100% rename from sources/Zenith.NET/Enums/CommandQueueType.cs rename to sources/Version1/Zenith.NET/Enums/CommandQueueType.cs diff --git a/sources/Zenith.NET/Enums/ComparisonFunc.cs b/sources/Version1/Zenith.NET/Enums/ComparisonFunc.cs similarity index 100% rename from sources/Zenith.NET/Enums/ComparisonFunc.cs rename to sources/Version1/Zenith.NET/Enums/ComparisonFunc.cs diff --git a/sources/Zenith.NET/Enums/CullMode.cs b/sources/Version1/Zenith.NET/Enums/CullMode.cs similarity index 100% rename from sources/Zenith.NET/Enums/CullMode.cs rename to sources/Version1/Zenith.NET/Enums/CullMode.cs diff --git a/sources/Zenith.NET/Enums/ElementFormat.cs b/sources/Version1/Zenith.NET/Enums/ElementFormat.cs similarity index 100% rename from sources/Zenith.NET/Enums/ElementFormat.cs rename to sources/Version1/Zenith.NET/Enums/ElementFormat.cs diff --git a/sources/Zenith.NET/Enums/ElementSemantic.cs b/sources/Version1/Zenith.NET/Enums/ElementSemantic.cs similarity index 100% rename from sources/Zenith.NET/Enums/ElementSemantic.cs rename to sources/Version1/Zenith.NET/Enums/ElementSemantic.cs diff --git a/sources/Zenith.NET/Enums/FillMode.cs b/sources/Version1/Zenith.NET/Enums/FillMode.cs similarity index 100% rename from sources/Zenith.NET/Enums/FillMode.cs rename to sources/Version1/Zenith.NET/Enums/FillMode.cs diff --git a/sources/Zenith.NET/Enums/Filter.cs b/sources/Version1/Zenith.NET/Enums/Filter.cs similarity index 100% rename from sources/Zenith.NET/Enums/Filter.cs rename to sources/Version1/Zenith.NET/Enums/Filter.cs diff --git a/sources/Zenith.NET/Enums/FrontFace.cs b/sources/Version1/Zenith.NET/Enums/FrontFace.cs similarity index 100% rename from sources/Zenith.NET/Enums/FrontFace.cs rename to sources/Version1/Zenith.NET/Enums/FrontFace.cs diff --git a/sources/Zenith.NET/Enums/IndexFormat.cs b/sources/Version1/Zenith.NET/Enums/IndexFormat.cs similarity index 100% rename from sources/Zenith.NET/Enums/IndexFormat.cs rename to sources/Version1/Zenith.NET/Enums/IndexFormat.cs diff --git a/sources/Zenith.NET/Enums/MessageSeverity.cs b/sources/Version1/Zenith.NET/Enums/MessageSeverity.cs similarity index 100% rename from sources/Zenith.NET/Enums/MessageSeverity.cs rename to sources/Version1/Zenith.NET/Enums/MessageSeverity.cs diff --git a/sources/Zenith.NET/Enums/MessageSource.cs b/sources/Version1/Zenith.NET/Enums/MessageSource.cs similarity index 100% rename from sources/Zenith.NET/Enums/MessageSource.cs rename to sources/Version1/Zenith.NET/Enums/MessageSource.cs diff --git a/sources/Zenith.NET/Enums/PixelFormat.cs b/sources/Version1/Zenith.NET/Enums/PixelFormat.cs similarity index 100% rename from sources/Zenith.NET/Enums/PixelFormat.cs rename to sources/Version1/Zenith.NET/Enums/PixelFormat.cs diff --git a/sources/Zenith.NET/Enums/PrimitiveTopology.cs b/sources/Version1/Zenith.NET/Enums/PrimitiveTopology.cs similarity index 100% rename from sources/Zenith.NET/Enums/PrimitiveTopology.cs rename to sources/Version1/Zenith.NET/Enums/PrimitiveTopology.cs diff --git a/sources/Zenith.NET/Enums/QueryType.cs b/sources/Version1/Zenith.NET/Enums/QueryType.cs similarity index 100% rename from sources/Zenith.NET/Enums/QueryType.cs rename to sources/Version1/Zenith.NET/Enums/QueryType.cs diff --git a/sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs b/sources/Version1/Zenith.NET/Enums/RayTracingGeometryFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs rename to sources/Version1/Zenith.NET/Enums/RayTracingGeometryFlags.cs diff --git a/sources/Zenith.NET/Enums/RayTracingGeometryType.cs b/sources/Version1/Zenith.NET/Enums/RayTracingGeometryType.cs similarity index 100% rename from sources/Zenith.NET/Enums/RayTracingGeometryType.cs rename to sources/Version1/Zenith.NET/Enums/RayTracingGeometryType.cs diff --git a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs b/sources/Version1/Zenith.NET/Enums/RayTracingInstanceFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs rename to sources/Version1/Zenith.NET/Enums/RayTracingInstanceFlags.cs diff --git a/sources/Zenith.NET/Enums/ResourceType.cs b/sources/Version1/Zenith.NET/Enums/ResourceType.cs similarity index 100% rename from sources/Zenith.NET/Enums/ResourceType.cs rename to sources/Version1/Zenith.NET/Enums/ResourceType.cs diff --git a/sources/Zenith.NET/Enums/SampleCount.cs b/sources/Version1/Zenith.NET/Enums/SampleCount.cs similarity index 100% rename from sources/Zenith.NET/Enums/SampleCount.cs rename to sources/Version1/Zenith.NET/Enums/SampleCount.cs diff --git a/sources/Zenith.NET/Enums/ShaderStageFlags.cs b/sources/Version1/Zenith.NET/Enums/ShaderStageFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/ShaderStageFlags.cs rename to sources/Version1/Zenith.NET/Enums/ShaderStageFlags.cs diff --git a/sources/Zenith.NET/Enums/StencilOp.cs b/sources/Version1/Zenith.NET/Enums/StencilOp.cs similarity index 100% rename from sources/Zenith.NET/Enums/StencilOp.cs rename to sources/Version1/Zenith.NET/Enums/StencilOp.cs diff --git a/sources/Zenith.NET/Enums/StringEncoding.cs b/sources/Version1/Zenith.NET/Enums/StringEncoding.cs similarity index 100% rename from sources/Zenith.NET/Enums/StringEncoding.cs rename to sources/Version1/Zenith.NET/Enums/StringEncoding.cs diff --git a/sources/Zenith.NET/Enums/SurfaceType.cs b/sources/Version1/Zenith.NET/Enums/SurfaceType.cs similarity index 100% rename from sources/Zenith.NET/Enums/SurfaceType.cs rename to sources/Version1/Zenith.NET/Enums/SurfaceType.cs diff --git a/sources/Zenith.NET/Enums/TextureType.cs b/sources/Version1/Zenith.NET/Enums/TextureType.cs similarity index 100% rename from sources/Zenith.NET/Enums/TextureType.cs rename to sources/Version1/Zenith.NET/Enums/TextureType.cs diff --git a/sources/Zenith.NET/Enums/TextureUsageFlags.cs b/sources/Version1/Zenith.NET/Enums/TextureUsageFlags.cs similarity index 100% rename from sources/Zenith.NET/Enums/TextureUsageFlags.cs rename to sources/Version1/Zenith.NET/Enums/TextureUsageFlags.cs diff --git a/sources/Zenith.NET/FrameBuffer.cs b/sources/Version1/Zenith.NET/FrameBuffer.cs similarity index 100% rename from sources/Zenith.NET/FrameBuffer.cs rename to sources/Version1/Zenith.NET/FrameBuffer.cs diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Version1/Zenith.NET/GraphicsContext.cs similarity index 100% rename from sources/Zenith.NET/GraphicsContext.cs rename to sources/Version1/Zenith.NET/GraphicsContext.cs diff --git a/sources/Zenith.NET/GraphicsPipeline.cs b/sources/Version1/Zenith.NET/GraphicsPipeline.cs similarity index 100% rename from sources/Zenith.NET/GraphicsPipeline.cs rename to sources/Version1/Zenith.NET/GraphicsPipeline.cs diff --git a/sources/Zenith.NET/GraphicsResource.cs b/sources/Version1/Zenith.NET/GraphicsResource.cs similarity index 100% rename from sources/Zenith.NET/GraphicsResource.cs rename to sources/Version1/Zenith.NET/GraphicsResource.cs diff --git a/sources/Zenith.NET/Interfaces/IBindableResource.cs b/sources/Version1/Zenith.NET/Interfaces/IBindableResource.cs similarity index 100% rename from sources/Zenith.NET/Interfaces/IBindableResource.cs rename to sources/Version1/Zenith.NET/Interfaces/IBindableResource.cs diff --git a/sources/Zenith.NET/Interfaces/IDisposableObject.cs b/sources/Version1/Zenith.NET/Interfaces/IDisposableObject.cs similarity index 100% rename from sources/Zenith.NET/Interfaces/IDisposableObject.cs rename to sources/Version1/Zenith.NET/Interfaces/IDisposableObject.cs diff --git a/sources/Zenith.NET/MeshShadingPipeline.cs b/sources/Version1/Zenith.NET/MeshShadingPipeline.cs similarity index 100% rename from sources/Zenith.NET/MeshShadingPipeline.cs rename to sources/Version1/Zenith.NET/MeshShadingPipeline.cs diff --git a/sources/Zenith.NET/Pipeline.cs b/sources/Version1/Zenith.NET/Pipeline.cs similarity index 100% rename from sources/Zenith.NET/Pipeline.cs rename to sources/Version1/Zenith.NET/Pipeline.cs diff --git a/sources/Zenith.NET/QueryHeap.cs b/sources/Version1/Zenith.NET/QueryHeap.cs similarity index 100% rename from sources/Zenith.NET/QueryHeap.cs rename to sources/Version1/Zenith.NET/QueryHeap.cs diff --git a/sources/Zenith.NET/RasterizerStates.cs b/sources/Version1/Zenith.NET/RasterizerStates.cs similarity index 100% rename from sources/Zenith.NET/RasterizerStates.cs rename to sources/Version1/Zenith.NET/RasterizerStates.cs diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Version1/Zenith.NET/ResourceTable.cs similarity index 100% rename from sources/Zenith.NET/ResourceTable.cs rename to sources/Version1/Zenith.NET/ResourceTable.cs diff --git a/sources/Zenith.NET/Sampler.cs b/sources/Version1/Zenith.NET/Sampler.cs similarity index 100% rename from sources/Zenith.NET/Sampler.cs rename to sources/Version1/Zenith.NET/Sampler.cs diff --git a/sources/Zenith.NET/Shader.cs b/sources/Version1/Zenith.NET/Shader.cs similarity index 100% rename from sources/Zenith.NET/Shader.cs rename to sources/Version1/Zenith.NET/Shader.cs diff --git a/sources/Zenith.NET/Structs/BlendState.cs b/sources/Version1/Zenith.NET/Structs/BlendState.cs similarity index 100% rename from sources/Zenith.NET/Structs/BlendState.cs rename to sources/Version1/Zenith.NET/Structs/BlendState.cs diff --git a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs b/sources/Version1/Zenith.NET/Structs/BlendStateRenderTarget.cs similarity index 100% rename from sources/Zenith.NET/Structs/BlendStateRenderTarget.cs rename to sources/Version1/Zenith.NET/Structs/BlendStateRenderTarget.cs diff --git a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs b/sources/Version1/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs rename to sources/Version1/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Version1/Zenith.NET/Structs/BufferDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/BufferDesc.cs rename to sources/Version1/Zenith.NET/Structs/BufferDesc.cs diff --git a/sources/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Version1/Zenith.NET/Structs/BufferViewDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/BufferViewDesc.cs rename to sources/Version1/Zenith.NET/Structs/BufferViewDesc.cs diff --git a/sources/Zenith.NET/Structs/ClearValue.cs b/sources/Version1/Zenith.NET/Structs/ClearValue.cs similarity index 100% rename from sources/Zenith.NET/Structs/ClearValue.cs rename to sources/Version1/Zenith.NET/Structs/ClearValue.cs diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Version1/Zenith.NET/Structs/ComputePipelineDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/ComputePipelineDesc.cs rename to sources/Version1/Zenith.NET/Structs/ComputePipelineDesc.cs diff --git a/sources/Zenith.NET/Structs/DepthStencilState.cs b/sources/Version1/Zenith.NET/Structs/DepthStencilState.cs similarity index 100% rename from sources/Zenith.NET/Structs/DepthStencilState.cs rename to sources/Version1/Zenith.NET/Structs/DepthStencilState.cs diff --git a/sources/Zenith.NET/Structs/DepthStencilStateOp.cs b/sources/Version1/Zenith.NET/Structs/DepthStencilStateOp.cs similarity index 100% rename from sources/Zenith.NET/Structs/DepthStencilStateOp.cs rename to sources/Version1/Zenith.NET/Structs/DepthStencilStateOp.cs diff --git a/sources/Zenith.NET/Structs/FrameBufferAttachment.cs b/sources/Version1/Zenith.NET/Structs/FrameBufferAttachment.cs similarity index 100% rename from sources/Zenith.NET/Structs/FrameBufferAttachment.cs rename to sources/Version1/Zenith.NET/Structs/FrameBufferAttachment.cs diff --git a/sources/Zenith.NET/Structs/FrameBufferDesc.cs b/sources/Version1/Zenith.NET/Structs/FrameBufferDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/FrameBufferDesc.cs rename to sources/Version1/Zenith.NET/Structs/FrameBufferDesc.cs diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Version1/Zenith.NET/Structs/GraphicsPipelineDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs rename to sources/Version1/Zenith.NET/Structs/GraphicsPipelineDesc.cs diff --git a/sources/Zenith.NET/Structs/IndirectDispatchArgs.cs b/sources/Version1/Zenith.NET/Structs/IndirectDispatchArgs.cs similarity index 100% rename from sources/Zenith.NET/Structs/IndirectDispatchArgs.cs rename to sources/Version1/Zenith.NET/Structs/IndirectDispatchArgs.cs diff --git a/sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs b/sources/Version1/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs similarity index 100% rename from sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs rename to sources/Version1/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs diff --git a/sources/Zenith.NET/Structs/IndirectDrawArgs.cs b/sources/Version1/Zenith.NET/Structs/IndirectDrawArgs.cs similarity index 100% rename from sources/Zenith.NET/Structs/IndirectDrawArgs.cs rename to sources/Version1/Zenith.NET/Structs/IndirectDrawArgs.cs diff --git a/sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs b/sources/Version1/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs similarity index 100% rename from sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs rename to sources/Version1/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs diff --git a/sources/Zenith.NET/Structs/InputElement.cs b/sources/Version1/Zenith.NET/Structs/InputElement.cs similarity index 100% rename from sources/Zenith.NET/Structs/InputElement.cs rename to sources/Version1/Zenith.NET/Structs/InputElement.cs diff --git a/sources/Zenith.NET/Structs/InputLayout.cs b/sources/Version1/Zenith.NET/Structs/InputLayout.cs similarity index 100% rename from sources/Zenith.NET/Structs/InputLayout.cs rename to sources/Version1/Zenith.NET/Structs/InputLayout.cs diff --git a/sources/Zenith.NET/Structs/MappedMemory.cs b/sources/Version1/Zenith.NET/Structs/MappedMemory.cs similarity index 100% rename from sources/Zenith.NET/Structs/MappedMemory.cs rename to sources/Version1/Zenith.NET/Structs/MappedMemory.cs diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Version1/Zenith.NET/Structs/MeshShadingPipelineDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs rename to sources/Version1/Zenith.NET/Structs/MeshShadingPipelineDesc.cs diff --git a/sources/Zenith.NET/Structs/Output.cs b/sources/Version1/Zenith.NET/Structs/Output.cs similarity index 100% rename from sources/Zenith.NET/Structs/Output.cs rename to sources/Version1/Zenith.NET/Structs/Output.cs diff --git a/sources/Zenith.NET/Structs/QueryHeapDesc.cs b/sources/Version1/Zenith.NET/Structs/QueryHeapDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/QueryHeapDesc.cs rename to sources/Version1/Zenith.NET/Structs/QueryHeapDesc.cs diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Version1/Zenith.NET/Structs/RasterizerState.cs similarity index 100% rename from sources/Zenith.NET/Structs/RasterizerState.cs rename to sources/Version1/Zenith.NET/Structs/RasterizerState.cs diff --git a/sources/Zenith.NET/Structs/RayTracingAABBs.cs b/sources/Version1/Zenith.NET/Structs/RayTracingAABBs.cs similarity index 100% rename from sources/Zenith.NET/Structs/RayTracingAABBs.cs rename to sources/Version1/Zenith.NET/Structs/RayTracingAABBs.cs diff --git a/sources/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Version1/Zenith.NET/Structs/RayTracingGeometry.cs similarity index 100% rename from sources/Zenith.NET/Structs/RayTracingGeometry.cs rename to sources/Version1/Zenith.NET/Structs/RayTracingGeometry.cs diff --git a/sources/Zenith.NET/Structs/RayTracingInstance.cs b/sources/Version1/Zenith.NET/Structs/RayTracingInstance.cs similarity index 100% rename from sources/Zenith.NET/Structs/RayTracingInstance.cs rename to sources/Version1/Zenith.NET/Structs/RayTracingInstance.cs diff --git a/sources/Zenith.NET/Structs/RayTracingTriangles.cs b/sources/Version1/Zenith.NET/Structs/RayTracingTriangles.cs similarity index 100% rename from sources/Zenith.NET/Structs/RayTracingTriangles.cs rename to sources/Version1/Zenith.NET/Structs/RayTracingTriangles.cs diff --git a/sources/Zenith.NET/Structs/RenderStates.cs b/sources/Version1/Zenith.NET/Structs/RenderStates.cs similarity index 100% rename from sources/Zenith.NET/Structs/RenderStates.cs rename to sources/Version1/Zenith.NET/Structs/RenderStates.cs diff --git a/sources/Zenith.NET/Structs/ResourceBinding.cs b/sources/Version1/Zenith.NET/Structs/ResourceBinding.cs similarity index 100% rename from sources/Zenith.NET/Structs/ResourceBinding.cs rename to sources/Version1/Zenith.NET/Structs/ResourceBinding.cs diff --git a/sources/Zenith.NET/Structs/ResourceTableDesc.cs b/sources/Version1/Zenith.NET/Structs/ResourceTableDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/ResourceTableDesc.cs rename to sources/Version1/Zenith.NET/Structs/ResourceTableDesc.cs diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Version1/Zenith.NET/Structs/SamplerDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/SamplerDesc.cs rename to sources/Version1/Zenith.NET/Structs/SamplerDesc.cs diff --git a/sources/Zenith.NET/Structs/Scissor.cs b/sources/Version1/Zenith.NET/Structs/Scissor.cs similarity index 100% rename from sources/Zenith.NET/Structs/Scissor.cs rename to sources/Version1/Zenith.NET/Structs/Scissor.cs diff --git a/sources/Zenith.NET/Structs/ShaderDesc.cs b/sources/Version1/Zenith.NET/Structs/ShaderDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/ShaderDesc.cs rename to sources/Version1/Zenith.NET/Structs/ShaderDesc.cs diff --git a/sources/Zenith.NET/Structs/Surface.cs b/sources/Version1/Zenith.NET/Structs/Surface.cs similarity index 100% rename from sources/Zenith.NET/Structs/Surface.cs rename to sources/Version1/Zenith.NET/Structs/Surface.cs diff --git a/sources/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Version1/Zenith.NET/Structs/SwapChainDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/SwapChainDesc.cs rename to sources/Version1/Zenith.NET/Structs/SwapChainDesc.cs diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Version1/Zenith.NET/Structs/TextureDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/TextureDesc.cs rename to sources/Version1/Zenith.NET/Structs/TextureDesc.cs diff --git a/sources/Zenith.NET/Structs/TextureExtent.cs b/sources/Version1/Zenith.NET/Structs/TextureExtent.cs similarity index 100% rename from sources/Zenith.NET/Structs/TextureExtent.cs rename to sources/Version1/Zenith.NET/Structs/TextureExtent.cs diff --git a/sources/Zenith.NET/Structs/TextureOffset.cs b/sources/Version1/Zenith.NET/Structs/TextureOffset.cs similarity index 100% rename from sources/Zenith.NET/Structs/TextureOffset.cs rename to sources/Version1/Zenith.NET/Structs/TextureOffset.cs diff --git a/sources/Zenith.NET/Structs/TextureSlice.cs b/sources/Version1/Zenith.NET/Structs/TextureSlice.cs similarity index 100% rename from sources/Zenith.NET/Structs/TextureSlice.cs rename to sources/Version1/Zenith.NET/Structs/TextureSlice.cs diff --git a/sources/Zenith.NET/Structs/TextureViewDesc.cs b/sources/Version1/Zenith.NET/Structs/TextureViewDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/TextureViewDesc.cs rename to sources/Version1/Zenith.NET/Structs/TextureViewDesc.cs diff --git a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs b/sources/Version1/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs similarity index 100% rename from sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs rename to sources/Version1/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs diff --git a/sources/Zenith.NET/Structs/Viewport.cs b/sources/Version1/Zenith.NET/Structs/Viewport.cs similarity index 100% rename from sources/Zenith.NET/Structs/Viewport.cs rename to sources/Version1/Zenith.NET/Structs/Viewport.cs diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Version1/Zenith.NET/SwapChain.cs similarity index 100% rename from sources/Zenith.NET/SwapChain.cs rename to sources/Version1/Zenith.NET/SwapChain.cs diff --git a/sources/Zenith.NET/Texture.cs b/sources/Version1/Zenith.NET/Texture.cs similarity index 100% rename from sources/Zenith.NET/Texture.cs rename to sources/Version1/Zenith.NET/Texture.cs diff --git a/sources/Zenith.NET/TextureView.cs b/sources/Version1/Zenith.NET/TextureView.cs similarity index 100% rename from sources/Zenith.NET/TextureView.cs rename to sources/Version1/Zenith.NET/TextureView.cs diff --git a/sources/Zenith.NET/TopLevelAccelerationStructure.cs b/sources/Version1/Zenith.NET/TopLevelAccelerationStructure.cs similarity index 100% rename from sources/Zenith.NET/TopLevelAccelerationStructure.cs rename to sources/Version1/Zenith.NET/TopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Version1/Zenith.NET/Uploader.cs similarity index 100% rename from sources/Zenith.NET/Uploader.cs rename to sources/Version1/Zenith.NET/Uploader.cs diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Version1/Zenith.NET/ValidationLayer.cs similarity index 100% rename from sources/Zenith.NET/ValidationLayer.cs rename to sources/Version1/Zenith.NET/ValidationLayer.cs diff --git a/sources/Zenith.NET/ValidationMessageArgs.cs b/sources/Version1/Zenith.NET/ValidationMessageArgs.cs similarity index 100% rename from sources/Zenith.NET/ValidationMessageArgs.cs rename to sources/Version1/Zenith.NET/ValidationMessageArgs.cs diff --git a/sources/Zenith.NET/Zenith.NET.csproj b/sources/Version1/Zenith.NET/Zenith.NET.csproj similarity index 64% rename from sources/Zenith.NET/Zenith.NET.csproj rename to sources/Version1/Zenith.NET/Zenith.NET.csproj index bace72ae..d8b41106 100644 --- a/sources/Zenith.NET/Zenith.NET.csproj +++ b/sources/Version1/Zenith.NET/Zenith.NET.csproj @@ -1,10 +1,10 @@ - + $(StandardTargetFramework) IDE0130 - + diff --git a/sources/Zenith.NET/ZenithHelper.cs b/sources/Version1/Zenith.NET/ZenithHelper.cs similarity index 100% rename from sources/Zenith.NET/ZenithHelper.cs rename to sources/Version1/Zenith.NET/ZenithHelper.cs diff --git a/sources/Zenith.NET/ZenithMarshal.cs b/sources/Version1/Zenith.NET/ZenithMarshal.cs similarity index 100% rename from sources/Zenith.NET/ZenithMarshal.cs rename to sources/Version1/Zenith.NET/ZenithMarshal.cs From 70b40c7cf24f6c06295f1e723d1d113b20fba54b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 23 Apr 2026 18:20:19 +0800 Subject: [PATCH 033/466] Add core functionality for Zenith.NET library - Introduced ValidationMessageArgs class for event handling with message source, severity, and timestamp. - Created Zenith.NET project file to define project properties and dependencies. - Implemented ZenithHelper class with various utility methods for texture and pixel format handling, including mip level calculations and size in bytes. - Developed ZenithMarshal class for memory management and string manipulation, including methods for allocating memory and converting strings to pointers. --- Zenith.NET.slnx | 47 +- rhi-redesign.draft.md | 709 ++++++++++-------- rhi-redesign.zh.draft.md | 704 +++++++++-------- .../Experiments/CornellBox/App.cs | 0 .../CornellBox/Assets/Fonts/msyh.ttf | Bin .../CornellBox/Assets/Shaders/.clang-format | 0 .../Assets/Shaders/PathTracing.slang | 0 .../Assets/Shaders/Rasterization.slang | 0 .../Experiments/CornellBox/CornellBox.csproj | 0 .../CornellBox/Handlers/CameraHandler.cs | 0 .../CornellBox/Handlers/ImGuiHandler.cs | 0 .../CornellBox/Helpers/CocoaHelper.cs | 0 .../CornellBox/Helpers/CornellBoxGeometry.cs | 0 .../Experiments/CornellBox/Program.cs | 0 .../CornellBox/Properties/launchSettings.json | 0 .../Renderers/PathTracingRenderer.cs | 0 .../Renderers/RasterizationRenderer.cs | 0 .../CornellBox/Renderers/Renderer.cs | 0 .../Experiments/Directory.Packages.props | 2 +- .../MemoryPlayground/MemoryPlayground.csproj | 0 .../Experiments/MemoryPlayground/Program.cs | 0 .../Properties/launchSettings.json | 0 .../PlatformDetection.csproj | 0 .../Experiments/PlatformDetection/Program.cs | 0 .../Properties/launchSettings.json | 0 sources/{Version1 => }/Experiments/README.md | 0 .../IImGuiPlatformBindings.cs | 0 .../ImGuiColorSpace.cs | 0 .../ImGuiController.cs | 0 .../ImGuiRenderer.cs | 0 .../Zenith.NET.Extensions.ImGui.csproj | 2 +- .../Extensions.cs | 0 .../Zenith.NET.Extensions.ImageSharp.csproj | 2 +- .../Zenith.NET.Extensions.Skia.csproj | 2 +- .../Zenith.NET.Extensions.Slang/Extensions.cs | 0 .../Zenith.NET.Extensions.Slang.csproj | 2 +- .../Zenith.NET.Views.Avalonia/Surface.cs | 0 .../Zenith.NET.Views.Avalonia.csproj | 2 +- .../Zenith.NET.Views.Avalonia/ZenithView.cs | 0 .../Views/Zenith.NET.Views.Maui/Extensions.cs | 0 .../Platforms/Android/MauiZenithView.cs | 0 .../Platforms/MacCatalyst/MauiZenithView.cs | 0 .../Platforms/Windows/D3D.cs | 0 .../Platforms/Windows/D3DTexture.cs | 0 .../Windows/ISwapChainPanelNative.cs | 0 .../Platforms/Windows/MauiZenithView.cs | 0 .../Platforms/iOS/MauiZenithView.cs | 0 .../Zenith.NET.Views.Maui.csproj | 2 +- .../Views/Zenith.NET.Views.Maui/ZenithView.cs | 0 .../Views/Zenith.NET.Views.WPF/D3D.cs | 0 .../Views/Zenith.NET.Views.WPF/D3DTexture.cs | 0 .../Zenith.NET.Views.WPF.csproj | 2 +- .../Views/Zenith.NET.Views.WPF/ZenithView.cs | 0 .../Zenith.NET.Views.WinForms.csproj | 2 +- .../Zenith.NET.Views.WinForms/ZenithView.cs | 0 .../Zenith.NET.Views.WinUI.csproj | 2 +- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 0 .../ZenithView.WinUI.cs | 0 .../Zenith.NET.Views.WinUI/ZenithView.cs | 0 .../Views/Zenith.NET.Views/FrameScheduler.cs | 0 .../Views/Zenith.NET.Views/IZenithView.cs | 0 .../Views/Zenith.NET.Views/RenderEventArgs.cs | 0 .../Views/Zenith.NET.Views/UpdateEventArgs.cs | 0 .../Zenith.NET.Views/Zenith.NET.Views.csproj | 2 +- .../Zenith.NET.Views/ZenithViewHelper.cs | 0 .../DXBottomLevelAccelerationStructure.cs | 0 .../Zenith.NET.DirectX12/DXBuffer.cs | 0 .../Zenith.NET.DirectX12/DXBufferView.cs | 0 .../Zenith.NET.DirectX12/DXCapabilities.cs | 0 .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 0 .../Zenith.NET.DirectX12/DXCommandQueue.cs | 0 .../Zenith.NET.DirectX12/DXComputePipeline.cs | 0 .../DXDescriptorAllocator.cs | 0 .../Zenith.NET.DirectX12/DXDescriptorPool.cs | 0 .../Zenith.NET.DirectX12/DXDescriptorTable.cs | 0 .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 0 .../Zenith.NET.DirectX12/DXFence.cs | 0 .../Zenith.NET.DirectX12/DXFormats.cs | 0 .../Zenith.NET.DirectX12/DXFrameBuffer.cs | 0 .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 0 .../DXGraphicsPipeline.cs | 0 .../Zenith.NET.DirectX12/DXHeap.cs | 0 .../DXMeshShadingPipeline.cs | 0 .../Zenith.NET.DirectX12/DXQueryHeap.cs | 0 .../Zenith.NET.DirectX12/DXResourceTable.cs | 0 .../Zenith.NET.DirectX12/DXSampler.cs | 0 .../Zenith.NET.DirectX12/DXShader.cs | 0 .../Zenith.NET.DirectX12/DXSwapChain.cs | 0 .../DXSwapChainFrameBuffer.cs | 0 .../Zenith.NET.DirectX12/DXTexture.cs | 0 .../Zenith.NET.DirectX12/DXTextureView.cs | 0 .../DXTopLevelAccelerationStructure.cs | 0 .../Zenith.NET.DirectX12/DXValidationLayer.cs | 0 .../Zenith.NET.DirectX12/Extensions.cs | 0 .../PipelineStateStream2.cs | 0 .../Zenith.NET.DirectX12/PixHelpers.cs | 0 .../Zenith.NET.DirectX12/Usings.cs | 0 .../Zenith.NET.DirectX12.csproj | 2 +- .../Zenith.NET.Metal/Extensions.cs | 0 .../MTLBottomLevelAccelerationStructure.cs | 0 .../Zenith.NET.Metal/MTLBuffer.cs | 0 .../Zenith.NET.Metal/MTLBufferView.cs | 0 .../Zenith.NET.Metal/MTLCapabilities.cs | 0 .../Zenith.NET.Metal/MTLCommandBuffer.cs | 0 .../Zenith.NET.Metal/MTLCommandEncoder.cs | 0 .../Zenith.NET.Metal/MTLCommandQueue.cs | 0 .../Zenith.NET.Metal/MTLComputePipeline.cs | 0 .../Zenith.NET.Metal/MTLFence.cs | 0 .../Zenith.NET.Metal/MTLFormats.cs | 0 .../Zenith.NET.Metal/MTLFrameBuffer.cs | 0 .../Zenith.NET.Metal/MTLGraphicsContext.cs | 0 .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 0 .../Zenith.NET.Metal/MTLHeap.cs | 0 .../MTLMeshShadingPipeline.cs | 0 .../Zenith.NET.Metal/MTLQueryHeap.cs | 0 .../Zenith.NET.Metal/MTLResourceTable.cs | 0 .../Zenith.NET.Metal/MTLSampler.cs | 0 .../Zenith.NET.Metal/MTLShader.cs | 0 .../Zenith.NET.Metal/MTLSwapChain.cs | 0 .../MTLSwapChainFrameBuffer.cs | 0 .../Zenith.NET.Metal/MTLTexture.cs | 0 .../Zenith.NET.Metal/MTLTextureView.cs | 0 .../MTLTopLevelAccelerationStructure.cs | 0 .../Zenith.NET.Metal/NSAutorelease.cs | 0 .../{Version1 => }/Zenith.NET.Metal/Usings.cs | 0 .../Zenith.NET.Metal/Zenith.NET.Metal.csproj | 2 +- .../Zenith.NET.Vulkan/Extensions.cs | 0 .../Zenith.NET.Vulkan/KhrRayQuery.cs | 0 .../Zenith.NET.Vulkan/Usings.cs | 0 .../VKBottomLevelAccelerationStructure.cs | 0 .../Zenith.NET.Vulkan/VKBuffer.cs | 0 .../Zenith.NET.Vulkan/VKBufferView.cs | 0 .../Zenith.NET.Vulkan/VKCapabilities.cs | 0 .../Zenith.NET.Vulkan/VKCommandBuffer.cs | 0 .../Zenith.NET.Vulkan/VKCommandQueue.cs | 0 .../Zenith.NET.Vulkan/VKComputePipeline.cs | 0 .../Zenith.NET.Vulkan/VKDeviceMemory.cs | 0 .../Zenith.NET.Vulkan/VKFence.cs | 0 .../Zenith.NET.Vulkan/VKFormats.cs | 0 .../Zenith.NET.Vulkan/VKFrameBuffer.cs | 0 .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 0 .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 0 .../VKMeshShadingPipeline.cs | 0 .../Zenith.NET.Vulkan/VKQueryHeap.cs | 0 .../Zenith.NET.Vulkan/VKResourceTable.cs | 0 .../Zenith.NET.Vulkan/VKSampler.cs | 0 .../Zenith.NET.Vulkan/VKShader.cs | 0 .../Zenith.NET.Vulkan/VKSwapChain.cs | 0 .../VKSwapChainFrameBuffer.cs | 0 .../Zenith.NET.Vulkan/VKTexture.cs | 0 .../Zenith.NET.Vulkan/VKTextureView.cs | 0 .../VKTopLevelAccelerationStructure.cs | 0 .../Zenith.NET.Vulkan/VKValidationLayer.cs | 0 .../Zenith.NET.Vulkan.csproj | 2 +- .../{Version1 => }/Zenith.NET/BlendStates.cs | 0 .../BottomLevelAccelerationStructure.cs | 0 sources/{Version1 => }/Zenith.NET/Buffer.cs | 0 .../{Version1 => }/Zenith.NET/BufferView.cs | 0 .../{Version1 => }/Zenith.NET/Capabilities.cs | 0 .../{Version1 => }/Zenith.NET/ClearValues.cs | 0 .../Zenith.NET/CommandBuffer.cs | 0 .../{Version1 => }/Zenith.NET/CommandQueue.cs | 0 .../Zenith.NET/ComputePipeline.cs | 0 .../Zenith.NET/DepthStencilStates.cs | 0 .../Zenith.NET/DisposableObject.cs | 0 .../Enums/AccelerationStructureBuildFlags.cs | 0 .../Zenith.NET/Enums/AddressMode.cs | 0 .../Zenith.NET/Enums/Backend.cs | 0 .../{Version1 => }/Zenith.NET/Enums/Blend.cs | 0 .../Zenith.NET/Enums/BlendOp.cs | 0 .../Zenith.NET/Enums/BorderColor.cs | 0 .../Zenith.NET/Enums/BufferUsageFlags.cs | 0 .../Zenith.NET/Enums/ClearFlags.cs | 0 .../Zenith.NET/Enums/ColorComponentFlags.cs | 0 .../Zenith.NET/Enums/CommandQueueType.cs | 0 .../Zenith.NET/Enums/ComparisonFunc.cs | 0 .../Zenith.NET/Enums/CullMode.cs | 0 .../Zenith.NET/Enums/ElementFormat.cs | 0 .../Zenith.NET/Enums/ElementSemantic.cs | 0 .../Zenith.NET/Enums/FillMode.cs | 0 .../{Version1 => }/Zenith.NET/Enums/Filter.cs | 0 .../Zenith.NET/Enums/FrontFace.cs | 0 .../Zenith.NET/Enums/IndexFormat.cs | 0 .../Zenith.NET/Enums/MessageSeverity.cs | 0 .../Zenith.NET/Enums/MessageSource.cs | 0 .../Zenith.NET/Enums/PixelFormat.cs | 0 .../Zenith.NET/Enums/PrimitiveTopology.cs | 0 .../Zenith.NET/Enums/QueryType.cs | 0 .../Enums/RayTracingGeometryFlags.cs | 0 .../Enums/RayTracingGeometryType.cs | 0 .../Enums/RayTracingInstanceFlags.cs | 0 .../Zenith.NET/Enums/ResourceType.cs | 0 .../Zenith.NET/Enums/SampleCount.cs | 0 .../Zenith.NET/Enums/ShaderStageFlags.cs | 0 .../Zenith.NET/Enums/StencilOp.cs | 0 .../Zenith.NET/Enums/StringEncoding.cs | 0 .../Zenith.NET/Enums/SurfaceType.cs | 0 .../Zenith.NET/Enums/TextureType.cs | 0 .../Zenith.NET/Enums/TextureUsageFlags.cs | 0 .../{Version1 => }/Zenith.NET/FrameBuffer.cs | 0 .../Zenith.NET/GraphicsContext.cs | 0 .../Zenith.NET/GraphicsPipeline.cs | 0 .../Zenith.NET/GraphicsResource.cs | 0 .../Interfaces/IBindableResource.cs | 0 .../Interfaces/IDisposableObject.cs | 0 .../Zenith.NET/MeshShadingPipeline.cs | 0 sources/{Version1 => }/Zenith.NET/Pipeline.cs | 0 .../{Version1 => }/Zenith.NET/QueryHeap.cs | 0 .../Zenith.NET/RasterizerStates.cs | 0 .../Zenith.NET/ResourceTable.cs | 0 sources/{Version1 => }/Zenith.NET/Sampler.cs | 0 sources/{Version1 => }/Zenith.NET/Shader.cs | 0 .../Zenith.NET/Structs/BlendState.cs | 0 .../Structs/BlendStateRenderTarget.cs | 0 .../BottomLevelAccelerationStructureDesc.cs | 0 .../Zenith.NET/Structs/BufferDesc.cs | 0 .../Zenith.NET/Structs/BufferViewDesc.cs | 0 .../Zenith.NET/Structs/ClearValue.cs | 0 .../Zenith.NET/Structs/ComputePipelineDesc.cs | 0 .../Zenith.NET/Structs/DepthStencilState.cs | 0 .../Zenith.NET/Structs/DepthStencilStateOp.cs | 0 .../Structs/FrameBufferAttachment.cs | 0 .../Zenith.NET/Structs/FrameBufferDesc.cs | 0 .../Structs/GraphicsPipelineDesc.cs | 0 .../Structs/IndirectDispatchArgs.cs | 0 .../Structs/IndirectDispatchMeshArgs.cs | 0 .../Zenith.NET/Structs/IndirectDrawArgs.cs | 0 .../Structs/IndirectDrawIndexedArgs.cs | 0 .../Zenith.NET/Structs/InputElement.cs | 0 .../Zenith.NET/Structs/InputLayout.cs | 0 .../Zenith.NET/Structs/MappedMemory.cs | 0 .../Structs/MeshShadingPipelineDesc.cs | 0 .../Zenith.NET/Structs/Output.cs | 0 .../Zenith.NET/Structs/QueryHeapDesc.cs | 0 .../Zenith.NET/Structs/RasterizerState.cs | 0 .../Zenith.NET/Structs/RayTracingAABBs.cs | 0 .../Zenith.NET/Structs/RayTracingGeometry.cs | 0 .../Zenith.NET/Structs/RayTracingInstance.cs | 0 .../Zenith.NET/Structs/RayTracingTriangles.cs | 0 .../Zenith.NET/Structs/RenderStates.cs | 0 .../Zenith.NET/Structs/ResourceBinding.cs | 0 .../Zenith.NET/Structs/ResourceTableDesc.cs | 0 .../Zenith.NET/Structs/SamplerDesc.cs | 0 .../Zenith.NET/Structs/Scissor.cs | 0 .../Zenith.NET/Structs/ShaderDesc.cs | 0 .../Zenith.NET/Structs/Surface.cs | 0 .../Zenith.NET/Structs/SwapChainDesc.cs | 0 .../Zenith.NET/Structs/TextureDesc.cs | 0 .../Zenith.NET/Structs/TextureExtent.cs | 0 .../Zenith.NET/Structs/TextureOffset.cs | 0 .../Zenith.NET/Structs/TextureSlice.cs | 0 .../Zenith.NET/Structs/TextureViewDesc.cs | 0 .../TopLevelAccelerationStructureDesc.cs | 0 .../Zenith.NET/Structs/Viewport.cs | 0 .../{Version1 => }/Zenith.NET/SwapChain.cs | 0 sources/{Version1 => }/Zenith.NET/Texture.cs | 0 .../{Version1 => }/Zenith.NET/TextureView.cs | 0 .../TopLevelAccelerationStructure.cs | 0 sources/{Version1 => }/Zenith.NET/Uploader.cs | 0 .../Zenith.NET/ValidationLayer.cs | 0 .../Zenith.NET/ValidationMessageArgs.cs | 0 .../Zenith.NET/Zenith.NET.csproj | 2 +- .../{Version1 => }/Zenith.NET/ZenithHelper.cs | 0 .../Zenith.NET/ZenithMarshal.cs | 0 264 files changed, 807 insertions(+), 683 deletions(-) rename sources/{Version1 => }/Experiments/CornellBox/App.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Assets/Fonts/msyh.ttf (100%) rename sources/{Version1 => }/Experiments/CornellBox/Assets/Shaders/.clang-format (100%) rename sources/{Version1 => }/Experiments/CornellBox/Assets/Shaders/PathTracing.slang (100%) rename sources/{Version1 => }/Experiments/CornellBox/Assets/Shaders/Rasterization.slang (100%) rename sources/{Version1 => }/Experiments/CornellBox/CornellBox.csproj (100%) rename sources/{Version1 => }/Experiments/CornellBox/Handlers/CameraHandler.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Handlers/ImGuiHandler.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Helpers/CocoaHelper.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Program.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Properties/launchSettings.json (100%) rename sources/{Version1 => }/Experiments/CornellBox/Renderers/PathTracingRenderer.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Renderers/RasterizationRenderer.cs (100%) rename sources/{Version1 => }/Experiments/CornellBox/Renderers/Renderer.cs (100%) rename sources/{Version1 => }/Experiments/Directory.Packages.props (78%) rename sources/{Version1 => }/Experiments/MemoryPlayground/MemoryPlayground.csproj (100%) rename sources/{Version1 => }/Experiments/MemoryPlayground/Program.cs (100%) rename sources/{Version1 => }/Experiments/MemoryPlayground/Properties/launchSettings.json (100%) rename sources/{Version1 => }/Experiments/PlatformDetection/PlatformDetection.csproj (100%) rename sources/{Version1 => }/Experiments/PlatformDetection/Program.cs (100%) rename sources/{Version1 => }/Experiments/PlatformDetection/Properties/launchSettings.json (100%) rename sources/{Version1 => }/Experiments/README.md (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj (86%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj (86%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj (86%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs (100%) rename sources/{Version1 => }/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj (86%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Avalonia/Surface.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj (86%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Avalonia/ZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Extensions.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj (93%) rename sources/{Version1 => }/Views/Zenith.NET.Views.Maui/ZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WPF/D3D.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WPF/D3DTexture.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj (90%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WPF/ZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj (87%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WinForms/ZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj (94%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views.WinUI/ZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views/FrameScheduler.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views/IZenithView.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views/RenderEventArgs.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views/UpdateEventArgs.cs (100%) rename sources/{Version1 => }/Views/Zenith.NET.Views/Zenith.NET.Views.csproj (82%) rename sources/{Version1 => }/Views/Zenith.NET.Views/ZenithViewHelper.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXBufferView.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXCapabilities.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXCommandBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXCommandQueue.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXComputePipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXDescriptorAllocator.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXDescriptorPool.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXDescriptorTable.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXDescriptorToken.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXFence.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXFormats.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXFrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXGraphicsContext.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXGraphicsPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXHeap.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXQueryHeap.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXResourceTable.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXSampler.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXShader.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXSwapChain.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXTexture.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXTextureView.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/DXValidationLayer.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/Extensions.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/PipelineStateStream2.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/PixHelpers.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/Usings.cs (100%) rename sources/{Version1 => }/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj (87%) rename sources/{Version1 => }/Zenith.NET.Metal/Extensions.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLBufferView.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLCapabilities.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLCommandBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLCommandEncoder.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLCommandQueue.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLComputePipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLFence.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLFormats.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLFrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLGraphicsContext.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLGraphicsPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLHeap.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLMeshShadingPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLQueryHeap.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLResourceTable.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLSampler.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLShader.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLSwapChain.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLTexture.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLTextureView.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/NSAutorelease.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/Usings.cs (100%) rename sources/{Version1 => }/Zenith.NET.Metal/Zenith.NET.Metal.csproj (86%) rename sources/{Version1 => }/Zenith.NET.Vulkan/Extensions.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/KhrRayQuery.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/Usings.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKBufferView.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKCapabilities.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKCommandBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKCommandQueue.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKComputePipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKDeviceMemory.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKFence.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKFormats.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKFrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKGraphicsContext.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKGraphicsPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKQueryHeap.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKResourceTable.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKSampler.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKShader.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKSwapChain.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKTexture.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKTextureView.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/VKValidationLayer.cs (100%) rename sources/{Version1 => }/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj (91%) rename sources/{Version1 => }/Zenith.NET/BlendStates.cs (100%) rename sources/{Version1 => }/Zenith.NET/BottomLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET/Buffer.cs (100%) rename sources/{Version1 => }/Zenith.NET/BufferView.cs (100%) rename sources/{Version1 => }/Zenith.NET/Capabilities.cs (100%) rename sources/{Version1 => }/Zenith.NET/ClearValues.cs (100%) rename sources/{Version1 => }/Zenith.NET/CommandBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET/CommandQueue.cs (100%) rename sources/{Version1 => }/Zenith.NET/ComputePipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET/DepthStencilStates.cs (100%) rename sources/{Version1 => }/Zenith.NET/DisposableObject.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/AddressMode.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/Backend.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/Blend.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/BlendOp.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/BorderColor.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/BufferUsageFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ClearFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ColorComponentFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/CommandQueueType.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ComparisonFunc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/CullMode.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ElementFormat.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ElementSemantic.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/FillMode.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/Filter.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/FrontFace.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/IndexFormat.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/MessageSeverity.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/MessageSource.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/PixelFormat.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/PrimitiveTopology.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/QueryType.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/RayTracingGeometryFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/RayTracingGeometryType.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/RayTracingInstanceFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ResourceType.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/SampleCount.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/ShaderStageFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/StencilOp.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/StringEncoding.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/SurfaceType.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/TextureType.cs (100%) rename sources/{Version1 => }/Zenith.NET/Enums/TextureUsageFlags.cs (100%) rename sources/{Version1 => }/Zenith.NET/FrameBuffer.cs (100%) rename sources/{Version1 => }/Zenith.NET/GraphicsContext.cs (100%) rename sources/{Version1 => }/Zenith.NET/GraphicsPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET/GraphicsResource.cs (100%) rename sources/{Version1 => }/Zenith.NET/Interfaces/IBindableResource.cs (100%) rename sources/{Version1 => }/Zenith.NET/Interfaces/IDisposableObject.cs (100%) rename sources/{Version1 => }/Zenith.NET/MeshShadingPipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET/Pipeline.cs (100%) rename sources/{Version1 => }/Zenith.NET/QueryHeap.cs (100%) rename sources/{Version1 => }/Zenith.NET/RasterizerStates.cs (100%) rename sources/{Version1 => }/Zenith.NET/ResourceTable.cs (100%) rename sources/{Version1 => }/Zenith.NET/Sampler.cs (100%) rename sources/{Version1 => }/Zenith.NET/Shader.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/BlendState.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/BlendStateRenderTarget.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/BufferDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/BufferViewDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/ClearValue.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/ComputePipelineDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/DepthStencilState.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/DepthStencilStateOp.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/FrameBufferAttachment.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/FrameBufferDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/GraphicsPipelineDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/IndirectDispatchArgs.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/IndirectDrawArgs.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/InputElement.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/InputLayout.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/MappedMemory.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/MeshShadingPipelineDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/Output.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/QueryHeapDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/RasterizerState.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/RayTracingAABBs.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/RayTracingGeometry.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/RayTracingInstance.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/RayTracingTriangles.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/RenderStates.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/ResourceBinding.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/ResourceTableDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/SamplerDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/Scissor.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/ShaderDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/Surface.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/SwapChainDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/TextureDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/TextureExtent.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/TextureOffset.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/TextureSlice.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/TextureViewDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs (100%) rename sources/{Version1 => }/Zenith.NET/Structs/Viewport.cs (100%) rename sources/{Version1 => }/Zenith.NET/SwapChain.cs (100%) rename sources/{Version1 => }/Zenith.NET/Texture.cs (100%) rename sources/{Version1 => }/Zenith.NET/TextureView.cs (100%) rename sources/{Version1 => }/Zenith.NET/TopLevelAccelerationStructure.cs (100%) rename sources/{Version1 => }/Zenith.NET/Uploader.cs (100%) rename sources/{Version1 => }/Zenith.NET/ValidationLayer.cs (100%) rename sources/{Version1 => }/Zenith.NET/ValidationMessageArgs.cs (100%) rename sources/{Version1 => }/Zenith.NET/Zenith.NET.csproj (78%) rename sources/{Version1 => }/Zenith.NET/ZenithHelper.cs (100%) rename sources/{Version1 => }/Zenith.NET/ZenithMarshal.cs (100%) diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 5ddb7d49..02f603da 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -4,31 +4,30 @@ - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - \ No newline at end of file + diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index 124bd59a..7f7f1676 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -1,34 +1,226 @@ -# RHI Redesign — API Specification +# RHI Redesign — Requirements Specification > Working spec for the redesigned public surface. Not for commit. Mirrors `rhi-redesign.zh.draft.md`. -> The same surface is laid out as a C# skeleton under `sources/Zenith.NET.New/`. +> The document is organized as nine numbered requirements; shared types live in §0. -## 1. Goals +## 0. Common Conventions -- Public API maps 1:1 to Metal 4 / Vulkan 1.4 / DirectX 12 (Enhanced Barriers). -- Hot path is allocation-free; multi-element parameters use `ReadOnlySpan`. -- Cross-queue synchronization via a single value type (`CommandSubmission`). -- Synchronization primitive is `(stage, access)` pairs, modelled after Metal 4 `barrierAfterStages:beforeStages:visibilityOptions:`. -- Inline render passes; no long-lived `FrameBuffer`. -- Subresource / sub-range information is a **call-site value type**, not a long-lived `View` object. -- One `ResourceTable` per pipeline, aligned with the Metal 4 argument-table model. +### 0.1 Naming and Code Style -## 2. Conventions - -- All public value types: `record struct` with public mutable fields. -- Multi-element parameters: `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, no `IEnumerable`. +- Every public value type is a `record struct` with public mutable fields. +- Multi-element parameters use `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, no `IEnumerable`. - All method bodies use `{ ... }`. **Exception:** `ref` / `ref readonly` returning properties keep `=> ref _field;`. - Every byte-denominated parameter or field carries the `*InBytes` suffix. - Backend hooks use the `*Core` suffix only when they share a name with a non-`Core` wrapper (e.g. `Wait` / `WaitCore`); otherwise they keep their natural name. - Every abstract member is plain `protected abstract`; same-assembly callers go through an `internal` wrapper. + +### 0.2 Public Surface Boundaries + +- No global-memory-barrier shape beyond the resource-less `Barrier(...)` form. +- No residency primitives (`MakeResident` / `Evict`) on the public surface. +- No long-lived `View` objects; view information is a call-site value. +- No long-lived `FrameBuffer` object. - The only public synchronization result type is `CommandSubmission`. -- There are **no global barriers** and **no residency primitives** on the public surface. +- Texture image-layout (DX12 / VK) is fully internal to the backend; it is computed from `BarrierAccess` plus RenderPass attachment metadata. + +### 0.3 Resource Handle Bases + +```csharp +public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject +{ + public GraphicsContext Context { get; } = context; + + public abstract nint GetNativeObject(NativeObjectType type); +} + +public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) +{ + private BufferDesc desc = desc; + + public ref readonly BufferDesc Desc => ref desc; + + /// + /// Maps the buffer into a CPU-visible region. Only valid when Desc.Flags contains + /// or ; + /// must be paired with . + /// + public abstract MappedMemory Map(); + + public abstract void Unmap(); +} + +public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) +{ + private TextureDesc desc = desc; + + public ref readonly TextureDesc Desc => ref desc; + + public static implicit operator TextureSubresourceRange(Texture texture) + { + return new() + { + BaseMipLevel = 0, + LevelCount = texture.Desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = texture.Desc.ArrayLayers + }; + } +} + +public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) +{ + private SamplerDesc desc = desc; + + public ref readonly SamplerDesc Desc => ref desc; +} +``` + +### 0.4 BufferDesc / Capability Flags + +Follows the existing framework design; no `MemoryAccess` enum is introduced. CPU visibility is expressed within the capability set via `BufferUsageFlags.MapRead` / `MapWrite`. + +```csharp +[Flags] +public enum BufferUsageFlags +{ + None = 0, + Vertex = 1 << 0, + Index = 1 << 1, + Indirect = 1 << 2, + AccelerationStructure = 1 << 3, + Constant = 1 << 4, + ShaderResource = 1 << 5, + UnorderedAccess = 1 << 6, + MapRead = 1 << 7, + MapWrite = 1 << 8 +} + +public record struct BufferDesc +{ + public uint SizeInBytes; + + public uint StrideInBytes; + + public BufferUsageFlags Flags; +} +``` + +`BufferUsageFlags` is a **capability set**, immutable for the lifetime of the resource. It is layered against §6 `BarrierAccess`: `Flags` declares "which accesses are permitted", `BarrierAccess` describes "which access is happening right now". A buffer declared `Vertex | ShaderResource` can be bound directly via `SetVertexBuffer` — there is no "type conversion" step; barriers only carry visibility between previous and next access. + +CPU upload paths split two ways: +- A buffer that declares `MapRead` / `MapWrite` is written application-side via paired `Map()` / `Unmap()`. +- Otherwise, writes flow through `CommandBuffer.Upload` / `CopyBuffer` against a staging buffer. + +--- + +## 1. ReadOnlySpan over Arrays + +Requirement: every multi-element input parameter on the public API uses `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, `IList`, `IEnumerable`, or `IReadOnlyList` appears in public signatures. -## 3. Public Type Catalog +Rationale: +- Pairs naturally with .NET 10's `params ReadOnlySpan` and collection expressions `[ ... ]`, giving zero-allocation call sites. +- The same signature accepts stack-allocated, array-backed, or single-element inputs uniformly. +- Backends can copy directly into native arrays without enumerating. + +Surfaces (non-exhaustive): + +| Parameter | Old | New | +|---|---|---| +| Submit waits | `Fence[]` | `ReadOnlySpan` | +| Vertex buffer slots | `Buffer[]` + `ulong[]` | `ReadOnlySpan` + `ReadOnlySpan` | +| Viewports / scissors | `Viewport[]` | `ReadOnlySpan` | +| RenderPass color attachments | `ColorAttachment[]` | `ReadOnlySpan` | +| ResourceTable array bindings | `IBindableResource[]` | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | +| Upload data | `byte[]` + offset/size | `ReadOnlySpan where T : unmanaged` | + +Exception: returning multiple elements as `ReadOnlySpan` requires a backing owner that is more expensive than the convenience is worth. Single-element returns keep their concrete type; bulk returns keep `T[]` or expose indexed accessors. + +--- + +## 2. Cross-queue Synchronization + +Requirement: cross-queue dependencies are expressed through a single value type `CommandSubmission`, modelled after the timeline primitive in all three backends. ```csharp -// === Subresources === +public enum CommandQueueType +{ + Graphics, + Compute, + Copy +} + +/// +/// One point on a queue's completion timeline, produced by Submit / Present. +/// default is the well-defined empty value: backends filter it out of any waits, +/// and is a no-op on it. +/// +public readonly struct CommandSubmission(CommandQueue? queue, ulong value) +{ + public CommandQueue? Queue = queue; + + public ulong Value = value; + public void Wait() + { + Queue?.Wait(Value); + } +} + +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +{ + public CommandQueueType Type { get; } = type; + + /// Acquires a recording-ready CommandBuffer, recycling a retired one when available. + public CommandBuffer CommandBuffer(); + + /// Waits until every submission issued on this queue has completed. Idempotent. + public void WaitForIdle(); + + /// Waits until this queue's timeline reaches . + public void Wait(ulong value); + + protected abstract ulong GetCompletedValue(); + + protected abstract void WaitCore(ulong value); + + protected abstract void SubmitCore(CommandBuffer commandBuffer, + ReadOnlySpan waits, + ulong signalValue); + + protected abstract CommandBuffer CreateCommandBuffer(); +} + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandQueue Queue { get; } = queue; + + /// Closes recording and enqueues this buffer on its owning queue. + public CommandSubmission Submit(params ReadOnlySpan waits); +} +``` + +Design points: +- Each queue owns one monotonic completion timeline. Each `Submit` advances `Value` and exposes the new point as `CommandSubmission(queue, value)`. +- Cross-queue dependency = pass an upstream `CommandSubmission` into a downstream `Submit(waits)`. +- `GetCompletedValue()` is a method, not a property: all three backends are call-shaped. +- CommandBuffer pooling is a queue-internal detail; the public surface only offers `CommandBuffer()` + `Submit(...)`. +- `GraphicsContext.Graphics` / `Compute` / `Copy` are the three queues; `Present` always runs on `Graphics`. + +Backend timeline mapping: + +| Backend | Timeline object | API | +|---|---|---| +| Metal 4 | one `MTLSharedEvent` per queue | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` | +| Vulkan 1.4 | one timeline `VkSemaphore` per queue | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` | +| DX12 | one `ID3D12Fence` per queue | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` | + +--- + +## 3. Subresource / Offset / Range as Value Types + +Requirement: replace the legacy `TextureSlice` / `TextureOffset` / `TextureExtent` with `TextureSubresource` / `TextureSubresourceLayers` / `TextureSubresourceRange` / `Offset3D` / `Extent3D`. The triplet expresses single-point / single-mip-multiple-layers / multi-mip-multi-layer ranges respectively. + +```csharp public record struct TextureSubresource { public uint MipLevel; @@ -73,9 +265,27 @@ public record struct Extent3D public uint Depth; } +``` + +| Type | Shape | Used by | +|---|---|---| +| `TextureSubresource` | one mip × one layer | RTV / DSV / Resolve endpoints | +| `TextureSubresourceLayers` | one mip × contiguous layer range | Copy / Upload | +| `TextureSubresourceRange` | contiguous mip range × contiguous layer range | Barrier / `TextureView.Range` | + +Conventions: +- The public surface does not carry an aspect field; backends derive aspect (color / depth / stencil) from `Texture.Format`. +- Cube faces are addressed as `ArrayLayer = cubeIndex * 6 + face`; there is no separate face axis. +- `Texture` implicitly converts to `TextureSubresourceRange` (full coverage) so common single-resource calls fit on one line. +- Suffix naming follows VK: `Subresource` (point) / `SubresourceLayers` (slab) / `SubresourceRange` (volume). + +--- + +## 4. Drop BufferView, unify on BufferRange -// === Buffer sub-range === +Requirement: remove the `BufferView` type and any `CreateBufferView` entry point. Buffer sub-ranges are expressed as a call-site `BufferRange` value. +```csharp public record struct BufferRange { public Buffer Buffer; @@ -97,9 +307,21 @@ public record struct BufferRange }; } } +``` + +Rationale: +- Buffers do not need cross-format reinterpretation; offset/size/stride is the full set required to describe a sub-range. +- DX12 `D3D12_*_VIEW_DESC`, VK `VkDescriptorBufferInfo`, Metal `setBuffer:offset:` all take call-site offset/size; a long-lived view object is unnecessary. +- Shader-side interpretation (CBV / structured-SRV / byteaddress-SRV / typed-SRV / UAV) is decided by the slot's `ResourceLayout`, not by the buffer. +- The implicit conversion keeps single-resource calls like `Write(0, vbo)` and `SetVertexBuffer(0, vbo, 0)` on a single line. -// === Texture view (call-site value) === +--- +## 5. TextureView Refactor (Format / ViewType) + +Requirement: redefine `TextureView` as a call-site value. Drop swizzle (5-tuple → 4-tuple) and add `Format` / `ViewType` for compatible-family reinterpretation and dimensionality switches. + +```csharp public enum TextureViewType { Texture1D, @@ -111,36 +333,6 @@ public enum TextureViewType TextureCubeArray } -public enum ComponentSwizzle -{ - Identity, - Zero, - One, - R, - G, - B, - A -} - -public record struct ComponentMapping -{ - public ComponentSwizzle R; - - public ComponentSwizzle G; - - public ComponentSwizzle B; - - public ComponentSwizzle A; - - public static ComponentMapping Identity => new() - { - R = ComponentSwizzle.Identity, - G = ComponentSwizzle.Identity, - B = ComponentSwizzle.Identity, - A = ComponentSwizzle.Identity - }; -} - public record struct TextureView { public Texture Texture; @@ -153,8 +345,6 @@ public record struct TextureView /// null uses Texture.Desc.Format; otherwise reinterprets within the same family. public PixelFormat? Format; - public ComponentMapping Swizzle; - public static implicit operator TextureView(Texture texture) { return new() @@ -162,14 +352,24 @@ public record struct TextureView Texture = texture, Range = texture, ViewType = null, - Format = null, - Swizzle = ComponentMapping.Identity + Format = null }; } } +``` + +- Mirrors VK `VkImageViewCreateInfo` and Metal `newTextureViewWithPixelFormat:textureType:levels:slices:`. +- Backends lazily cache native views keyed by `(Texture, Range, ViewType, Format)`; that cache is an implementation detail. +- Channel swizzle is intentionally not on the public surface: single-channel → grayscale and similar patterns are handled in shader; BGRA ↔ RGBA is covered by `Format` reinterpretation within the same compatibility family. If a real need surfaces later, an optional `ComponentMapping?` field can be added back without breaking existing call sites. +- `Texture` implicitly converts to `TextureView` (full range, original format, original dimensionality) so common single-resource bindings fit on one line. -// === Synchronization (stage + access) === +--- +## 6. Barrier — 1:1 Synchronization Primitive + +Requirement: remove every implicit layout transition. All visibility / execution dependency / layout switches are stated by the caller through a `(stage, access)` pair, mirroring Metal 4 / VK 1.4 / DX12 Enhanced Barriers. + +```csharp [Flags] public enum BarrierStage : uint { @@ -212,9 +412,37 @@ public enum BarrierAccess : uint AccelerationStructureRead = 1u << 15, AccelerationStructureWrite = 1u << 16, } +``` + +The CommandBuffer barrier entry points come in three shapes: + +```csharp +public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); + +public void BufferBarrier(BufferRange range, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); -// === RenderPass attachments === +public void TextureBarrier(TextureView view, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); +``` +Design points: +- `BarrierStage` describes **when** in the pipeline an access happens; `BarrierAccess` describes **what kind** of access it is. They mirror Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`, VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`, DX12 `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS`. +- All three forms share the same parameter order: `(afterStages, afterAccess) → (beforeStages, beforeAccess)`. +- Texture image-layout: maintained internally by the backend, derived from `BarrierAccess` plus RenderPass attachment metadata. There is no public layout enum. +- The public surface does **not** offer simplified shortcuts; the caller must supply explicit stage + access. +- Adjacent same-stage / same-resource barriers are folded to no-ops by the backend; the caller is not required to deduplicate. + +--- + +## 7. Drop FrameBuffer — Inline RenderPass + SwapChain Targets + +Requirement: remove `FrameBuffer` / `RenderPassInfo` long-lived objects. RenderPass is expressed inline via `BeginRenderPass` / `EndRenderPass`; `SwapChain` directly exposes `CurrentColorTarget` / `CurrentDepthStencilTarget`, so backbuffers are shaped like ordinary `Texture` objects. + +```csharp public enum LoadAction { Load, @@ -265,216 +493,57 @@ public record struct DepthStencilAttachment public byte ClearStencil; } -// === Queue submission result === - -/// -/// One point on a queue's completion timeline, produced by Submit / Present. -/// default is the well-defined empty value: backends filter it out of any waits, -/// and is a no-op on it. -/// -public readonly struct CommandSubmission(CommandQueue? queue, ulong value) -{ - public CommandQueue? Queue = queue; - - public ulong Value = value; +// CommandBuffer +public void BeginRenderPass(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment); - public void Wait() - { - Queue?.Wait(Value); - } -} +public void EndRenderPass(); ``` -Subresource triplet: - -| Type | Shape | Used by | -|---|---|---| -| `TextureSubresource` | one mip × one layer | RTV / DSV / Resolve | -| `TextureSubresourceLayers` | one mip × contiguous layer range | Copy / Upload | -| `TextureSubresourceRange` | contiguous mip range × contiguous layer range | Barrier / `TextureView.Range` | - -The public surface does not carry an aspect field; backends derive aspect (color / depth / stencil) from `Texture.Format`. Cube faces are addressed as `ArrayLayer = cubeIndex * 6 + face`; there is no separate face axis. - -`TextureView` mirrors VK `VkImageViewCreateInfo` and Metal `newTextureViewWithPixelFormat:textureType:levels:slices:swizzle:`. The native view object is created lazily and cached per backend, keyed by `(Texture, Range, ViewType, Format, Swizzle)`. DX12 has no first-class swizzle; the backend rejects non-Identity swizzles unless the slot's layout opts in to a shader-side swizzle workaround. - -`BarrierStage` describes **when** in the pipeline an access happens; `BarrierAccess` describes **what kind** of access it is. They mirror Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`, VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`, and DX12 Enhanced Barriers `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS`. - -## 4. Resource Handles - ```csharp -public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) -{ - private BufferDesc desc = desc; - - public ref readonly BufferDesc Desc => ref desc; -} - -public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) -{ - private TextureDesc desc = desc; - - public ref readonly TextureDesc Desc => ref desc; - - public static implicit operator TextureSubresourceRange(Texture texture) - { - return new() - { - BaseMipLevel = 0, - LevelCount = texture.Desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = texture.Desc.ArrayLayers - }; - } -} - -public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) +public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) { - private SamplerDesc desc = desc; - - public ref readonly SamplerDesc Desc => ref desc; -} -``` - -- View objects are not on the public surface. Backends may lazily cache native views keyed by the §3 value types; that cache is a backend implementation detail. -- Texture image-layout (DX12 / VK) is fully internal to the backend; it is computed from the access flags supplied to barriers and from RenderPass attachment metadata. - -## 5. CommandQueue / CommandBuffer - -Each `CommandQueue` owns one monotonic completion timeline. Each `Submit` advances that timeline by one and exposes the new value as `CommandSubmission(queue, value)`. Cross-queue dependencies are expressed by feeding upstream `CommandSubmission`s into a downstream `Submit(waits)`. + private SwapChainDesc desc = desc; -```csharp -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) -{ - public CommandQueueType Type { get; } = type; + public ref readonly SwapChainDesc Desc => ref desc; - /// Acquires a recording-ready CommandBuffer, recycling a retired one when available. - public CommandBuffer CommandBuffer(); + public abstract uint Width { get; } - /// Waits until every submission issued on this queue has completed. Idempotent. - public void WaitForIdle(); + public abstract uint Height { get; } - protected abstract ulong GetCompletedValue(); + public abstract Texture CurrentColorTarget { get; } - protected abstract void WaitCore(ulong value); + public abstract Texture? CurrentDepthStencilTarget { get; } - protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + /// + /// Submits a present on the GraphicsQueue, waiting for ; + /// the returned is the next-backbuffer-writable point. + /// + public abstract CommandSubmission Present(params ReadOnlySpan waits); - protected abstract CommandBuffer CreateCommandBuffer(); -} + public void Resize(uint width, uint height); -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - /// Closes recording and enqueues this buffer on its owning queue. - public CommandSubmission Submit(params ReadOnlySpan waits); + public void Refresh(Surface surface); } ``` -- `GetCompletedValue()` is a method, not a property: all three backends (DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`) are call-shaped. -- `CommandBuffer` pooling is entirely a queue-internal detail; the public surface only offers `CommandBuffer()` and `Submit(...)`. -- `GraphicsContext` exposes three queues: `Graphics` / `Compute` / `Copy`. `Present` always runs on `Graphics`; `SwapChain` pulls that queue reference from the context. +Design points: +- Backbuffers are exposed as ordinary `Texture` instances (color + optional depth-stencil); they participate in `BeginRenderPass`, `TextureBarrier`, `CopyTexture`, `Write(...)` like any other texture. +- `BeginRenderPass` takes an attachment span; pass `null` explicitly when there is no depth target. The backend defaults `SetViewports` / `SetScissors` from attachment dimensions; the caller can override with a follow-up call. +- `Present(...)` is symmetric with `Submit(...)`: takes waits, returns a `CommandSubmission`. +- Current image index, acquire / present synchronization primitives are backend-internal. -### Backend Timeline Mapping +--- -| Backend | Timeline object | API | -|---|---|---| -| Metal 4 | one `MTLSharedEvent` per queue | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | -| Vulkan 1.4 | one timeline `VkSemaphore` per queue | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | -| DX12 | one `ID3D12Fence` per queue | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | +## 8. ResourceTable Overloads + Drop IBindableResource -## 6. CommandBuffer Operations +Requirement: `ResourceTable.Write` is overloaded by resource type (`BufferRange` / `TextureView` / `Sampler`); the public surface no longer routes through an `IBindableResource` polymorphic entry. ```csharp -// === Synchronization === - -public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); - -public void BufferBarrier(BufferRange range, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); - -public void TextureBarrier(TextureView view, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); - -// === Inline RenderPass === - -public void BeginRenderPass(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment); - -public void EndRenderPass(); - -// === Viewports / Scissors === - -public void SetViewports(ReadOnlySpan viewports); - -public void SetScissors(ReadOnlySpan scissors); - -// === Pipeline / Resource binding === - -public void SetPipeline(Pipeline pipeline); - -public void PushResourceTable(ResourceTable table); - -// === Vertex / Index === - -public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); - -public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); - -public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); - -// === Draws / Dispatches === - -public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); - -public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance); - -public void DrawIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); - -public void DrawIndexedIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); - -public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ); - -public void DispatchIndirect(Buffer argsBuffer, ulong offsetInBytes); - -// === Buffer copy / upload === - -public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, - Buffer destination, ulong destinationOffsetInBytes, - ulong sizeInBytes); - -public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; - -// === Texture copy / upload / resolve === - -public void CopyTexture(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, - Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, - Extent3D extent); - -public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, - Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, - Extent3D extent); - -public void CopyTextureToBuffer(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, Extent3D extent, - Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); - -public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, - Texture destination, TextureSubresource destinationSubresource); - -public void Upload(Texture destination, TextureSubresourceLayers layers, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; -``` - -- `BeginRenderPass` accepts an attachment span; pass `null` explicitly for no depth. The implementation auto-emits the attachment-side barriers and auto-fills `SetViewports` / `SetScissors` from attachment dimensions; the caller may override afterward. -- `PushResourceTable` takes no `stages` argument — stage information is carried per-binding in the table's layout. Only one table per pipeline is supported. -- **Push-snapshot semantics**: `PushResourceTable` snapshots `table` into the cmd buffer at the call site; subsequent `Write`s do not affect already-pushed bindings, so the same `ResourceTable` can be repeatedly `Write` + `Push`ed within a frame. -- `Barrier` is the global form (no resource argument). `BufferBarrier` / `TextureBarrier` scope the barrier to a `BufferRange` / `TextureView`. All three carry the same `(afterStages, afterAccess) → (beforeStages, beforeAccess)` shape. - -### ResourceTable - -```csharp -public abstract class ResourceTable +public abstract class ResourceTable(GraphicsContext context, ResourceLayout layout) : GraphicsResource(context) { + public ResourceLayout Layout { get; } = layout; + public void Write(uint binding, BufferRange range); public void Write(uint binding, TextureView view); @@ -486,91 +555,41 @@ public abstract class ResourceTable public void Write(uint binding, ReadOnlySpan views); public void Write(uint binding, ReadOnlySpan samplers); -} -``` - -- `Buffer` / `Texture` flow into the call through their implicit operators, keeping single-resource binds a single line. -- The interpretation of a buffer (CBV / structured SRV / byte-address SRV / UAV / typed) is fixed by the slot's `ResourceLayout`. -- A UAV slot requires `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`; shape / layout / format-family mismatches are reported at `Write` time. - -## 7. SwapChain - -```csharp -public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) -{ - private SwapChainDesc desc = desc; - public ref readonly SwapChainDesc Desc => ref desc; + protected abstract void WriteCore(uint binding, ReadOnlySpan ranges); - public abstract uint Width { get; } + protected abstract void WriteCore(uint binding, ReadOnlySpan views); - public abstract uint Height { get; } - - public abstract Texture CurrentColorTarget { get; } - - public abstract Texture? CurrentDepthStencilTarget { get; } - - /// - /// Submits a present on the GraphicsQueue, waiting on ; - /// returns a that signals when the next backbuffer is writable. - /// - public abstract CommandSubmission Present(params ReadOnlySpan waits); - - public void Resize(uint width, uint height); - - public void Refresh(Surface surface); + protected abstract void WriteCore(uint binding, ReadOnlySpan samplers); } ``` -- Backbuffers are exposed as plain `Texture`s (color + optional depth-stencil). -- `Present(...)` returns a `CommandSubmission` describing when the next backbuffer is writable; it accepts waits from any queue, symmetrically with `CommandBuffer.Submit(...)`. -- The current backbuffer index is entirely a backend concern. - -## 8. Frame Loop +CommandBuffer-side entry points: ```csharp -CommandSubmission imageReady = default; - -while (running) -{ - CommandBuffer cmd = ctx.Graphics.CommandBuffer(); - - ReadOnlySpan colors = - [ - new() - { - Texture = swapChain.CurrentColorTarget, - Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, - LoadAction = LoadAction.Clear, - StoreAction = StoreAction.Store, - ClearColor = new(0, 0, 0, 1) - } - ]; +public void SetPipeline(Pipeline pipeline); - DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds - ? new() - { - Texture = ds, - Subresource = new(), - DepthLoadAction = LoadAction.Clear, - DepthStoreAction = StoreAction.DontCare, - ClearDepth = 1.0f - } - : null; +public void PushResourceTable(ResourceTable table); +``` - cmd.BeginRenderPass(colors, depth); - // draws - cmd.EndRenderPass(); +Design points: +- The §4 / §5 implicit conversions on `Buffer` / `Texture` keep single-resource bindings on one line: `table.Write(0, vbo)` / `table.Write(1, tex)`. +- Buffer interpretation (CBV / SRV-structured / SRV-byteaddress / UAV / typed) is decided by the slot's `ResourceLayout`, not by the supplied `BufferRange`. +- One table per pipeline (aligned with the Metal 4 argument-table model). +- `PushResourceTable` does not take a stages parameter: stages live on each binding inside the layout. +- **Push-snapshot semantics**: `PushResourceTable` snapshots the table's contents into the command buffer at the call site; later `Write`s on the same `ResourceTable` do not affect already-pushed bindings, so a single `ResourceTable` can be `Write` + `Push`-ed repeatedly within a frame. +- Validation: UAV slots require the corresponding `UnorderedAccess` capability bit; shape / layout / format-family mismatches are reported at `Write` time. - CommandSubmission frame = cmd.Submit(imageReady); +Why no `IBindableResource`: +- The three resource kinds have entirely different native write paths (descriptor / argument-buffer slot / sampler heap); a polymorphic interface forces a runtime dispatch that overloads avoid at compile time. +- Overloads let the right `Write` route be selected at compile time, with no boxing or runtime type tests. +- Implicit `Buffer → BufferRange` / `Texture → TextureView` conversions already cover "I just want to pass the handle" ergonomics. - imageReady = swapChain.Present(frame); -} -``` +--- -Every step has the same shape: take a set of `CommandSubmission` waits, return a `CommandSubmission`. `Submit` and `Present` are symmetric on the API surface. +## 9. INativeObject — Unified Native Handle Entry -## 9. Native Handles +Requirement: every public type that wraps a native object implements `INativeObject` and exposes the underlying handle through `GetNativeObject(NativeObjectType)`. The public surface carries no platform-conditional properties. ```csharp public enum NativeObjectType @@ -588,7 +607,7 @@ public enum NativeObjectType D3D12CpuDescriptorHandleSrv, D3D12CpuDescriptorHandleUav, - // Metal + // Metal 4 MtlDevice, Mtl4CommandQueue, Mtl4CommandBuffer, @@ -611,23 +630,67 @@ public enum NativeObjectType public interface INativeObject { - /// Returns 0 when the enum does not match this object or the current backend. + /// Returns 0 when the requested type does not match this object or the active backend. nint GetNativeObject(NativeObjectType type); } -public abstract class GraphicsContext : INativeObject +public abstract class GraphicsContext : DisposableObject, INativeObject { public abstract nint GetNativeObject(NativeObjectType type); } -public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject +// GraphicsResource already implements INativeObject in §0.3. +``` + +Design points: +- Naming follows native API prefixes: DX12 splits into `Dxgi` / `D3D12`; Metal 4 disambiguates from legacy Metal via `Mtl` / `Mtl4`; Vulkan uniformly uses `Vk`. +- `D3D12CpuDescriptorHandle*` is split per view kind to keep the entry point unambiguous. +- Non-handle scalars (e.g. `VkQueueFamilyIndex`) flow through the same entry, packed into `nint`. +- Every `GraphicsResource` subclass implements the interface; subclasses that have nothing to expose return 0 for every requested type. +- Callers are responsible for handling the "returned 0" degraded path; the public surface does not throw. + +--- + +## Appendix A: Typical Frame Loop (combines §2 / §6 / §7) + +```csharp +CommandSubmission imageReady = default; + +while (running) { - public GraphicsContext Context { get; } = context; + CommandBuffer cmd = ctx.Graphics.CommandBuffer(); - public abstract nint GetNativeObject(NativeObjectType type); + ReadOnlySpan colors = + [ + new() + { + Texture = swapChain.CurrentColorTarget, + Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, + LoadAction = LoadAction.Clear, + StoreAction = StoreAction.Store, + ClearColor = new(0, 0, 0, 1) + } + ]; + + DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds + ? new() + { + Texture = ds, + Subresource = new(), + DepthLoadAction = LoadAction.Clear, + DepthStoreAction = StoreAction.DontCare, + ClearDepth = 1.0f + } + : null; + + cmd.BeginRenderPass(colors, depth); + // draws + cmd.EndRenderPass(); + + CommandSubmission frame = cmd.Submit(imageReady); + + imageReady = swapChain.Present(frame); } ``` -- Prefixes follow each native API: DX12 splits into `Dxgi` / `D3D12`; Metal 4 keeps `Mtl4` distinct from the older `Mtl`; Vulkan uses `Vk`. `D3D12CpuDescriptorHandle*` is split per view type to avoid ambiguity at a single entry point. -- Non-handle scalar information (e.g. `VkQueueFamilyIndex`) flows through the same entry point, carrying a `uint` inside an `nint`. The public surface never grows a platform-conditional property. -- Every `GraphicsResource` subclass implements the interface; subclasses with nothing to expose return 0. +Each step has the same shape: take a set of `CommandSubmission` waits, return one `CommandSubmission`. `Submit` and `Present` are symmetric on the public surface. diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index 5ebc4949..c28823e1 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -1,34 +1,224 @@ -# RHI 重设计草案 — API 规范(中文临时版) +# RHI 重设计草案 — 需求规范(中文临时版) > 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 -> 同一份规范以 C# 骨架放在 `sources/Zenith.NET.New/`。 +> 本文档以 9 条需求为骨架;公共类型与 §0 公共约定共享。 -## 1. 目标 +## 0. 公共约定 -- 公共 API 与 Metal 4 / Vulkan 1.4 / DirectX 12(Enhanced Barriers)1:1 对齐 -- 热路径零托管分配;多元素入参一律 `ReadOnlySpan` -- 跨队列同步以单一值类型 `CommandSubmission` 表达 -- 同步原语:`(stage, access)` 二元组,对齐 Metal 4 `barrierAfterStages:beforeStages:visibilityOptions:` -- 内联 RenderPass,去掉长生命周期 `FrameBuffer` -- 子范围 / 子资源信息为**调用现场**值类型,不再有公共 `View` 对象 -- 每个 pipeline 一张 `ResourceTable`,对齐 Metal 4 argument-table 模型 +### 0.1 命名与代码风格 -## 2. 命名与代码风格约定 - -- 所有公共值类型:`record struct`,公共字段可写 +- 所有公共值类型一律 `record struct`,公共字段可写 - 多元素入参一律 `ReadOnlySpan` / `params ReadOnlySpan`,不使用 `T[]` / `IEnumerable` - 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性保留 `=> ref _field;` - 字节单位字段与参数加 `*InBytes` 后缀 -- 抽象后端钩子命名:**只有**与同名非 `Core` 包装共存时才用 `*Core` 后缀(如 `Wait` / `WaitCore`);其余一律用自然名 +- 抽象后端钩子:仅当与同名非 `Core` 包装共存时才用 `*Core` 后缀(如 `Wait` / `WaitCore`);其余一律自然名 - 抽象成员一律 `protected abstract`,同程序集内部调用走 `internal` 包装 + +### 0.2 公共面边界 + +- 公共面**无**全局内存屏障(VK pipeline barrier without resource)以外的特殊形态 +- 公共面**无**驻留集(residency / `MakeResident` / `Evict`)相关原语 +- 公共面**无**长生命周期 `View` 对象;view 信息为调用现场值 +- 公共面**无**长生命周期 `FrameBuffer` 对象 - 公共同步结果只有一种:`CommandSubmission` -- 公共面**无**全局内存屏障、**无**驻留集(residency)相关原语 +- Texture 的 image-layout 完全在后端内部维护,由 `BarrierAccess` + RenderPass attachment 元数据推导 + +### 0.3 资源句柄基类 + +```csharp +public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject +{ + public GraphicsContext Context { get; } = context; + + public abstract nint GetNativeObject(NativeObjectType type); +} + +public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) +{ + private BufferDesc desc = desc; + + public ref readonly BufferDesc Desc => ref desc; + + /// + /// 映射为 CPU 可访问区间。仅在 Desc.Flags + /// 或 时合法,必须与 配对。 + /// + public abstract MappedMemory Map(); + + public abstract void Unmap(); +} + +public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) +{ + private TextureDesc desc = desc; + + public ref readonly TextureDesc Desc => ref desc; + + public static implicit operator TextureSubresourceRange(Texture texture) + { + return new() + { + BaseMipLevel = 0, + LevelCount = texture.Desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = texture.Desc.ArrayLayers + }; + } +} + +public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) +{ + private SamplerDesc desc = desc; + + public ref readonly SamplerDesc Desc => ref desc; +} +``` + +### 0.4 BufferDesc / 能力位 + +沿用现框架设计,不引入 `MemoryAccess` 枚举;CPU 可见性由 `BufferUsageFlags.MapRead` / `MapWrite` 在能力位集合里表达。 + +```csharp +[Flags] +public enum BufferUsageFlags +{ + None = 0, + Vertex = 1 << 0, + Index = 1 << 1, + Indirect = 1 << 2, + AccelerationStructure = 1 << 3, + Constant = 1 << 4, + ShaderResource = 1 << 5, + UnorderedAccess = 1 << 6, + MapRead = 1 << 7, + MapWrite = 1 << 8 +} + +public record struct BufferDesc +{ + public uint SizeInBytes; + + public uint StrideInBytes; + + public BufferUsageFlags Flags; +} +``` + +`BufferUsageFlags` 是**能力位集合**,资源生命周期内不可变;它与 §6 的 `BarrierAccess` 是两层:`Flags` 决定"允许的访问集合",`BarrierAccess` 描述"现在以哪种方式访问"。同一块 buffer 同时声明 `Vertex | ShaderResource` 完全合法,用作顶点输入直接 `SetVertexBuffer` 即可,无任何"格式转换"动作;barrier 只负责"上个用法 → 下个用法"的可见性。 + +CPU 上传路径二选一: +- buffer 声明 `MapRead` / `MapWrite` 时,应用侧 `Map()` / `Unmap()` 直接写 +- 否则沉入 `CommandBuffer.Upload` / `CopyBuffer` 走 staging + +--- + +## 1. ReadOnlySpan 替代数组 + +需求:所有"多元素入参"在公共 API 上一律改为 `ReadOnlySpan` / `params ReadOnlySpan`,不出现 `T[]`、`IList`、`IEnumerable`、`IReadOnlyList`。 -## 3. 公共类型一览 +理由: +- 接 .NET 10 的 `params ReadOnlySpan` 与集合表达式 `[ ... ]`,调用现场零托管分配 +- 同一签名同时接受栈分配 / 数组 / 单元素,调用方写法统一 +- 后端可直接拷贝到原生数组,无需先 enumerate + +适用面(不完全列表): + +| 入参 | 旧形态 | 新形态 | +|---|---|---| +| Submit 等待集 | `Fence[]` | `ReadOnlySpan` | +| 顶点缓冲槽 | `Buffer[]` + `ulong[]` | `ReadOnlySpan` + `ReadOnlySpan` | +| 视口 / 裁剪 | `Viewport[]` | `ReadOnlySpan` | +| RenderPass 颜色附件 | `ColorAttachment[]` | `ReadOnlySpan` | +| ResourceTable 数组绑定 | `IBindableResource[]` | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | +| 上传数据 | `byte[]` + offset/size | `ReadOnlySpan where T : unmanaged` | + +例外:返回多个元素时使用 `ReadOnlySpan` 的成本(必须有持有者)大于价值 —— 单元素返回保留具体类型;批量返回保留 `T[]` 或暴露索引访问。 + +--- + +## 2. 跨队列同步 + +需求:跨队列依赖只暴露**一个**值类型 `CommandSubmission`,对齐三端时间线模型。 ```csharp -// === 子资源 === +public enum CommandQueueType +{ + Graphics, + Compute, + Copy +} + +/// +/// 一次 Submit / Present 产生的一个时间线点。 +/// default 是合法的"空"值,后端会从 waits 中过滤掉;其上 为空操作。 +/// +public readonly struct CommandSubmission(CommandQueue? queue, ulong value) +{ + public CommandQueue? Queue = queue; + + public ulong Value = value; + + public void Wait() + { + Queue?.Wait(Value); + } +} + +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +{ + public CommandQueueType Type { get; } = type; + + /// 获取一个可录制的 CommandBuffer,必要时复用已退役实例。 + public CommandBuffer CommandBuffer(); + + /// 等待此 queue 上所有已提交命令完成。幂等。 + public void WaitForIdle(); + + /// 等待该 queue 时间线推进到 + public void Wait(ulong value); + + protected abstract ulong GetCompletedValue(); + + protected abstract void WaitCore(ulong value); + protected abstract void SubmitCore(CommandBuffer commandBuffer, + ReadOnlySpan waits, + ulong signalValue); + + protected abstract CommandBuffer CreateCommandBuffer(); +} + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandQueue Queue { get; } = queue; + + /// 结束录制并在所属 queue 上入队。 + public CommandSubmission Submit(params ReadOnlySpan waits); +} +``` + +设计要点: +- 每个 queue 持一条单调递增完成时间线;`Submit` 推进 `Value`,并以 `CommandSubmission(queue, value)` 公开 +- 跨队列依赖 = 把上游 `CommandSubmission` 传入下游 `Submit(waits)` +- `GetCompletedValue()` 是方法不是属性:三端原生 API 都是调用语义 +- `CommandBuffer` 池化是 queue 内部实现,公共面只暴露 `CommandBuffer()` + `Submit(...)` +- `GraphicsContext.Graphics` / `Compute` / `Copy` 三条队列;`Present` 始终在 `Graphics` + +后端时间线对照: + +| 后端 | 时间线对象 | API | +|---|---|---| +| Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` | +| Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore` | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` | +| DX12 | 每 queue 一个 `ID3D12Fence` | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` | + +--- + +## 3. 子资源 / 偏移 / 范围的值类型化 + +需求:用 `TextureSubresource` / `TextureSubresourceLayers` / `TextureSubresourceRange` / `Offset3D` / `Extent3D` 替代旧版 `TextureSlice` / `TextureOffset` / `TextureExtent`,统一命名并明确"单点 / 单 mip 多 layer / 多 mip 多 layer"三档。 + +```csharp public record struct TextureSubresource { public uint MipLevel; @@ -73,9 +263,27 @@ public record struct Extent3D public uint Depth; } +``` + +| 类型 | 表达 | 用途 | +|---|---|---| +| `TextureSubresource` | 单 mip × 单 layer | RTV / DSV / Resolve 端点 | +| `TextureSubresourceLayers` | 单 mip × 连续 layer 段 | Copy / Upload | +| `TextureSubresourceRange` | 连续 mip 段 × 连续 layer 段 | Barrier / `TextureView.Range` | + +约定: +- 公共面不暴露 `aspect`;后端从 `Texture.Format` 推断 color / depth / stencil +- Cube face 以 `ArrayLayer = cubeIndex * 6 + face` 表达,无独立 face 轴 +- `Texture` 隐式转 `TextureSubresourceRange`(覆盖整张纹理)让常见调用一行内写完 +- 命名后缀对齐 VK:`Subresource`(点)/ `SubresourceLayers`(面)/ `SubresourceRange`(体) -// === Buffer 子范围 === +--- +## 4. 删除 BufferView,统一为 BufferRange + +需求:移除 `BufferView` 类型与所有 `CreateBufferView` 入口,buffer 子范围一律用调用现场的 `BufferRange` 值表达。 + +```csharp public record struct BufferRange { public Buffer Buffer; @@ -97,9 +305,21 @@ public record struct BufferRange }; } } +``` + +理由: +- buffer 不需要"格式族重解释";offset/size/stride 三元组就是描述子范围所需的全部信息 +- DX12 `D3D12_*_VIEW_DESC`、VK `VkDescriptorBufferInfo`、Metal `setBuffer:offset:` 都接受调用现场的 offset/size,不存在长生命周期 view 对象的必要 +- shader 端的解释维度(CBV / structured-SRV / byteaddress-SRV / typed-SRV / UAV)由槽位的 `ResourceLayout` 决定,与 buffer 本身无关 +- 隐式转换让 `Write(0, vbo)`、`SetVertexBuffer(0, vbo, 0)` 这类常见单资源绑定保持一行 + +--- + +## 5. TextureView 重构(Format / ViewType) -// === Texture view(调用现场值) === +需求:`TextureView` 重新定位为**调用现场值**,5 元组 → 4 元组(去掉 swizzle),加入 `Format` / `ViewType` 用于格式族重解释与维度切换。 +```csharp public enum TextureViewType { Texture1D, @@ -111,36 +331,6 @@ public enum TextureViewType TextureCubeArray } -public enum ComponentSwizzle -{ - Identity, - Zero, - One, - R, - G, - B, - A -} - -public record struct ComponentMapping -{ - public ComponentSwizzle R; - - public ComponentSwizzle G; - - public ComponentSwizzle B; - - public ComponentSwizzle A; - - public static ComponentMapping Identity => new() - { - R = ComponentSwizzle.Identity, - G = ComponentSwizzle.Identity, - B = ComponentSwizzle.Identity, - A = ComponentSwizzle.Identity - }; -} - public record struct TextureView { public Texture Texture; @@ -153,8 +343,6 @@ public record struct TextureView /// null 表示沿用 Texture.Desc.Format;非 null 时按同一兼容族重解释。 public PixelFormat? Format; - public ComponentMapping Swizzle; - public static implicit operator TextureView(Texture texture) { return new() @@ -162,14 +350,24 @@ public record struct TextureView Texture = texture, Range = texture, ViewType = null, - Format = null, - Swizzle = ComponentMapping.Identity + Format = null }; } } +``` -// === 同步(stage + access) === +- 对齐 VK `VkImageViewCreateInfo` 与 Metal `newTextureViewWithPixelFormat:textureType:levels:slices:` 的参数集 +- 后端按 `(Texture, Range, ViewType, Format)` 为 key 懒加载并缓存原生 view,纯实现细节 +- 通道 swizzle **不在公共面暴露**:单通道→灰度类约定在 shader 侧解决;BGRA ↔ RGBA 一类靠 `Format` 在同兼容族内重解释覆盖。如未来出现刚需,可作为可选 `ComponentMapping?` 字段加回,向后兼容 +- `Texture` 隐式转 `TextureView`(全范围、原 format、原维度)让常见单资源绑定一行写完 +--- + +## 6. Barrier — 1:1 同步原语 + +需求:移除所有"隐式布局转换"。所有可见性 / 执行依赖 / 布局切换由调用方通过 `(stage, access)` 二元组显式声明,对齐 Metal 4 / VK 1.4 / DX12 Enhanced Barriers。 + +```csharp [Flags] public enum BarrierStage : uint { @@ -212,9 +410,37 @@ public enum BarrierAccess : uint AccelerationStructureRead = 1u << 15, AccelerationStructureWrite = 1u << 16, } +``` + +CommandBuffer 上的 barrier 入口三种形态: -// === RenderPass 附件 === +```csharp +public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); + +public void BufferBarrier(BufferRange range, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); + +public void TextureBarrier(TextureView view, + BarrierStage afterStages, BarrierAccess afterAccess, + BarrierStage beforeStages, BarrierAccess beforeAccess); +``` + +设计要点: +- `BarrierStage` 描述访问发生在管线**何处**(when);`BarrierAccess` 描述访问的**性质**(what)。两者对齐 Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`、VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`、DX12 `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS` +- 三种形态参数序一致:`(afterStages, afterAccess) → (beforeStages, beforeAccess)` +- texture image-layout:完全在后端内部维护,由 `BarrierAccess` + RenderPass attachment 元数据推导。公共面无 layout 枚举 +- 公共面**没有**全局内存屏障的"快捷形式"以外的简化入口;调用方必须显式给出 stage + access +- 队列内部的"同 stage 同资源连续访问"由后端折叠为 noop,不强求调用方手动去重 + +--- +## 7. 删除 FrameBuffer — 内联 RenderPass + SwapChain 公开当前目标 + +需求:移除 `FrameBuffer` / `RenderPassInfo` 等长生命周期对象。RenderPass 以 `BeginRenderPass` / `EndRenderPass` 内联表达;`SwapChain` 直接暴露 `CurrentColorTarget` / `CurrentDepthStencilTarget`,使 backbuffer 与普通 `Texture` 同形。 + +```csharp public enum LoadAction { Load, @@ -265,215 +491,57 @@ public record struct DepthStencilAttachment public byte ClearStencil; } -// === 队列同步结果 === - -/// -/// 一次 Submit / Present 产生的一个时间线点。 -/// default 是合法的"空"值,后端会从 waits 中过滤掉;其上 为空操作。 -/// -public readonly struct CommandSubmission(CommandQueue? queue, ulong value) -{ - public CommandQueue? Queue = queue; - - public ulong Value = value; +// CommandBuffer +public void BeginRenderPass(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment); - public void Wait() - { - Queue?.Wait(Value); - } -} +public void EndRenderPass(); ``` -子资源三件套: - -| 类型 | 表达 | 用途 | -|---|---|---| -| `TextureSubresource` | 单 mip × 单 layer | RTV / DSV / Resolve | -| `TextureSubresourceLayers` | 单 mip × 连续 layer 段 | Copy / Upload | -| `TextureSubresourceRange` | 连续 mip 段 × 连续 layer 段 | Barrier / `TextureView.Range` | - -公共面不暴露 aspect 字段,后端从 `Texture.Format` 推断。Cube 面以 `ArrayLayer = cubeIndex * 6 + face` 表达,无独立 face 轴。 - -`TextureView` 对齐 VK `VkImageViewCreateInfo` 与 Metal `newTextureViewWithPixelFormat:textureType:levels:slices:swizzle:` 的参数集;背后的原生 view 由后端按 `(Texture, Range, ViewType, Format, Swizzle)` 为 key 懒加载并缓存,纯实现细节。DX12 没有原生 swizzle,后端在槽位 layout 未声明 shader-side swizzle 兼容时拒绝非 Identity 的 swizzle。 - -`BarrierStage` 描述访问发生在管线**何处**;`BarrierAccess` 描述访问的**性质**。两者对齐 Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`、VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`、DX12 Enhanced Barriers `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS`。 - -## 4. 资源句柄 - ```csharp -public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) -{ - private BufferDesc desc = desc; - - public ref readonly BufferDesc Desc => ref desc; -} - -public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) -{ - private TextureDesc desc = desc; - - public ref readonly TextureDesc Desc => ref desc; - - public static implicit operator TextureSubresourceRange(Texture texture) - { - return new() - { - BaseMipLevel = 0, - LevelCount = texture.Desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = texture.Desc.ArrayLayers - }; - } -} - -public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) +public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) { - private SamplerDesc desc = desc; - - public ref readonly SamplerDesc Desc => ref desc; -} -``` - -- View 对象不在公共面出现;后端可按 §3 的值类型做 key 懒加载缓存原生 view,纯实现细节。 -- DX12 / VK 的 texture image-layout 完全在后端内部维护,由 barrier 携带的 access flags 与 RenderPass attachment 元数据推导。 - -## 5. CommandQueue / CommandBuffer - -每个 `CommandQueue` 持有一条单调递增的完成时间线;一次 `Submit` 推进一个值,并以 `CommandSubmission(queue, value)` 公开该点。跨队列依赖通过把上游 `CommandSubmission` 传入下游 `Submit(waits)` 表达。 + private SwapChainDesc desc = desc; -```csharp -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) -{ - public CommandQueueType Type { get; } = type; + public ref readonly SwapChainDesc Desc => ref desc; - /// 获取一个可录制的 CommandBuffer,必要时复用已退役实例。 - public CommandBuffer CommandBuffer(); + public abstract uint Width { get; } - /// 等待此 queue 上所有已提交命令完成。幂等。 - public void WaitForIdle(); + public abstract uint Height { get; } - protected abstract ulong GetCompletedValue(); + public abstract Texture CurrentColorTarget { get; } - protected abstract void WaitCore(ulong value); + public abstract Texture? CurrentDepthStencilTarget { get; } - protected abstract void SubmitCore(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + /// + /// 在 GraphicsQueue 上提交 present,等待 ; + /// 返回的 表示下一张 backbuffer 可写。 + /// + public abstract CommandSubmission Present(params ReadOnlySpan waits); - protected abstract CommandBuffer CreateCommandBuffer(); -} + public void Resize(uint width, uint height); -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - /// 结束录制并在其所属 queue 上入队。 - public CommandSubmission Submit(params ReadOnlySpan waits); + public void Refresh(Surface surface); } ``` -- `GetCompletedValue()` 是方法而非属性:三端后端(DX12 `fence.GetCompletedValue()` / VK `vkGetSemaphoreCounterValue` / Metal `event.SignaledValue`)都是调用语义。 -- CommandBuffer 池化完全是 queue 的内部实现细节;公共面只暴露 `CommandBuffer()` 与 `Submit(...)`。 -- `GraphicsContext` 暴露三条队列:`Graphics` / `Compute` / `Copy`。`Present` 始终在 `Graphics` 上执行;`SwapChain` 从 context 取该队列引用。 +设计要点: +- backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板);它们既能进 `BeginRenderPass`,也能进 `TextureBarrier` / `CopyTexture` / `Write(...)` +- `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;按 attachment 尺寸自动填默认 `SetViewports` / `SetScissors`,调用方可在其后再调一次覆盖 +- `Present(...)` 与 `Submit(...)` 形态对称:吃 waits、产 `CommandSubmission` +- 当前 image index、acquire / present 同步原语都是后端内部事务 -### 后端时间线对照 +--- -| 后端 | 时间线对象 | API | -|---|---|---| -| Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` / `event.NotifyListener` | -| Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore` | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` / `vkWaitSemaphores` | -| DX12 | 每 queue 一个 `ID3D12Fence` | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` / `fence.SetEventOnCompletion` | - -## 6. CommandBuffer 操作 - -```csharp -// === 同步 === - -public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); - -public void BufferBarrier(BufferRange range, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); - -public void TextureBarrier(TextureView view, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); - -// === RenderPass(内联) === - -public void BeginRenderPass(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment); - -public void EndRenderPass(); - -// === 视口 / 裁剪 === - -public void SetViewports(ReadOnlySpan viewports); - -public void SetScissors(ReadOnlySpan scissors); - -// === Pipeline / 资源绑定 === - -public void SetPipeline(Pipeline pipeline); - -public void PushResourceTable(ResourceTable table); - -// === 顶点 / 索引 === - -public void SetVertexBuffer(uint slot, Buffer buffer, ulong offsetInBytes); - -public void SetVertexBuffers(uint firstSlot, ReadOnlySpan buffers, ReadOnlySpan offsetsInBytes); - -public void SetIndexBuffer(Buffer buffer, ulong offsetInBytes, IndexFormat format); - -// === 绘制 / 调度 === - -public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); - -public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance); - -public void DrawIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); - -public void DrawIndexedIndirect(Buffer argsBuffer, ulong offsetInBytes, uint drawCount, uint strideInBytes); - -public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ); - -public void DispatchIndirect(Buffer argsBuffer, ulong offsetInBytes); +## 8. ResourceTable 多重载 + 删除 IBindableResource -// === Buffer 拷贝 / 上传 === - -public void CopyBuffer(Buffer source, ulong sourceOffsetInBytes, - Buffer destination, ulong destinationOffsetInBytes, - ulong sizeInBytes); - -public void Upload(Buffer destination, ulong offsetInBytes, ReadOnlySpan data) where T : unmanaged; - -// === Texture 拷贝 / 上传 / Resolve === - -public void CopyTexture(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, - Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, - Extent3D extent); - -public void CopyBufferToTexture(Buffer source, ulong sourceOffsetInBytes, uint sourceRowPitchInBytes, uint sourceImageHeight, - Texture destination, TextureSubresourceLayers destinationLayers, Offset3D destinationOrigin, - Extent3D extent); - -public void CopyTextureToBuffer(Texture source, TextureSubresourceLayers sourceLayers, Offset3D sourceOrigin, Extent3D extent, - Buffer destination, ulong destinationOffsetInBytes, uint destinationRowPitchInBytes, uint destinationImageHeight); - -public void ResolveTexture(Texture source, TextureSubresource sourceSubresource, - Texture destination, TextureSubresource destinationSubresource); - -public void Upload(Texture destination, TextureSubresourceLayers layers, Offset3D origin, Extent3D extent, ReadOnlySpan data) where T : unmanaged; -``` - -- `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;内部按 attachment 自动发出对应的同步 barrier,并按 attachment 尺寸自动填 `SetViewports` / `SetScissors`,调用方可在其后再调一次覆盖。 -- `PushResourceTable` 不接受 stages 参数:stages 信息由 table 的 layout 在每个 binding 上自带。每个 pipeline 仅支持一张 table。 -- **Push-snapshot 语义**:`PushResourceTable` 在调用现场把 `table` 的当前内容快照进 cmd buffer;之后对该 `table` 的 `Write` 不影响已 push 的绑定,因此同一个 `ResourceTable` 可以在帧内反复 `Write` + `Push`。 -- `Barrier` 是不带资源的全局形式;`BufferBarrier` / `TextureBarrier` 把 barrier 限定到 `BufferRange` / `TextureView`。三者形态一致,都是 `(afterStages, afterAccess) → (beforeStages, beforeAccess)`。 - -### ResourceTable +需求:`ResourceTable.Write` 直接按资源类型重载(`BufferRange` / `TextureView` / `Sampler`),不再走 `IBindableResource` 多态入口。 ```csharp -public abstract class ResourceTable +public abstract class ResourceTable(GraphicsContext context, ResourceLayout layout) : GraphicsResource(context) { + public ResourceLayout Layout { get; } = layout; + public void Write(uint binding, BufferRange range); public void Write(uint binding, TextureView view); @@ -485,91 +553,41 @@ public abstract class ResourceTable public void Write(uint binding, ReadOnlySpan views); public void Write(uint binding, ReadOnlySpan samplers); -} -``` - -- `Buffer` / `Texture` 经隐式转换让单资源绑定保持一行。 -- buffer 解释(CBV / SRV-structured / SRV-byteaddress / UAV / typed)由槽位的 `ResourceLayout` 决定。 -- 校验:UAV 槽位要求 `BufferUsageFlags.UnorderedAccess` / `TextureUsageFlags.UnorderedAccess`,shape / layout / 格式族不匹配在 `Write` 时报错。 - -## 7. SwapChain - -```csharp -public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) -{ - private SwapChainDesc desc = desc; - - public ref readonly SwapChainDesc Desc => ref desc; - public abstract uint Width { get; } + protected abstract void WriteCore(uint binding, ReadOnlySpan ranges); - public abstract uint Height { get; } - - public abstract Texture CurrentColorTarget { get; } + protected abstract void WriteCore(uint binding, ReadOnlySpan views); - public abstract Texture? CurrentDepthStencilTarget { get; } - - /// - /// 在 GraphicsQueue 上提交 present,等待 ; - /// 返回的 表示下一张 backbuffer 可写。 - /// - public abstract CommandSubmission Present(params ReadOnlySpan waits); - - public void Resize(uint width, uint height); - - public void Refresh(Surface surface); + protected abstract void WriteCore(uint binding, ReadOnlySpan samplers); } ``` -- backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板)。 -- `Present(...)` 返回 `CommandSubmission`,与 `CommandBuffer.Submit(...)` 形态对称;接收任意队列的 wait。 -- 当前 image index 完全是后端事务。 - -## 8. 帧循环 +CommandBuffer 上对应入口: ```csharp -CommandSubmission imageReady = default; - -while (running) -{ - CommandBuffer cmd = ctx.Graphics.CommandBuffer(); - - ReadOnlySpan colors = - [ - new() - { - Texture = swapChain.CurrentColorTarget, - Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, - LoadAction = LoadAction.Clear, - StoreAction = StoreAction.Store, - ClearColor = new(0, 0, 0, 1) - } - ]; +public void SetPipeline(Pipeline pipeline); - DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds - ? new() - { - Texture = ds, - Subresource = new(), - DepthLoadAction = LoadAction.Clear, - DepthStoreAction = StoreAction.DontCare, - ClearDepth = 1.0f - } - : null; +public void PushResourceTable(ResourceTable table); +``` - cmd.BeginRenderPass(colors, depth); - // draws - cmd.EndRenderPass(); +设计要点: +- `Buffer` / `Texture` 经 §4 / §5 的隐式转换让单资源绑定一行写完:`table.Write(0, vbo)` / `table.Write(1, tex)` +- buffer 解释(CBV / SRV-structured / SRV-byteaddress / UAV / typed)由槽位的 `ResourceLayout` 决定,与传入的 `BufferRange` 无关 +- 每个 pipeline 仅支持一张 table(对齐 Metal 4 argument-table 模型) +- `PushResourceTable` 不接受 stages 参数:stages 信息由 layout 在每个 binding 上自带 +- **Push-snapshot 语义**:`PushResourceTable` 在调用现场把 `table` 的当前内容快照进 cmd buffer;之后对该 `table` 的 `Write` 不影响已 push 的绑定,所以同一个 `ResourceTable` 可以在帧内反复 `Write` + `Push` +- 校验:UAV 槽位要求资源声明对应 `UnorderedAccess` 能力位,shape / layout / 格式族不匹配在 `Write` 时报错 - CommandSubmission frame = cmd.Submit(imageReady); +为什么不用 `IBindableResource`: +- 三种资源在原生 API 上的写入路径完全不同(descriptor / argument buffer slot / sampler heap),多态接口反而需要在运行时 dispatch +- 重载形式让编译期就能选中正确的 `Write` 路径,避免装箱与运行时 type test +- 隐式转换 `Buffer → BufferRange` / `Texture → TextureView` 已经覆盖"我就想直接传句柄"的便利性 - imageReady = swapChain.Present(frame); -} -``` +--- -每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面对称。 +## 9. INativeObject — 原生句柄统一入口 -## 9. 原生句柄 +需求:所有持有原生对象的公共类型实现 `INativeObject`,通过 `GetNativeObject(NativeObjectType)` 暴露后端句柄;公共面不挂任何平台条件属性。 ```csharp public enum NativeObjectType @@ -587,7 +605,7 @@ public enum NativeObjectType D3D12CpuDescriptorHandleSrv, D3D12CpuDescriptorHandleUav, - // Metal + // Metal 4 MtlDevice, Mtl4CommandQueue, Mtl4CommandBuffer, @@ -614,19 +632,63 @@ public interface INativeObject nint GetNativeObject(NativeObjectType type); } -public abstract class GraphicsContext : INativeObject +public abstract class GraphicsContext : DisposableObject, INativeObject { public abstract nint GetNativeObject(NativeObjectType type); } -public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject +// GraphicsResource 已在 §0.3 实现 INativeObject +``` + +设计要点: +- 命名沿用各原生 API 既有前缀:DX12 体系按 `Dxgi` / `D3D12` 区分;Metal 4 与旧 Metal 共存时用 `Mtl` / `Mtl4` 区分;Vulkan 一律 `Vk` +- `D3D12CpuDescriptorHandle*` 按 view 类型拆开,避免单入口语义模糊 +- 非句柄的标量信息(如 `VkQueueFamilyIndex`)也走同一入口,以 `nint` 承载 `uint` +- 所有 `GraphicsResource` 子类一律实现该接口;不感兴趣的子类对所有类型返回 0 +- 取该接口的代码必须自己处理"返回 0"的退化路径,公共面不抛异常 + +--- + +## 附录 A:典型帧循环(综合 §2 / §6 / §7) + +```csharp +CommandSubmission imageReady = default; + +while (running) { - public GraphicsContext Context { get; } = context; + CommandBuffer cmd = ctx.Graphics.CommandBuffer(); - public abstract nint GetNativeObject(NativeObjectType type); + ReadOnlySpan colors = + [ + new() + { + Texture = swapChain.CurrentColorTarget, + Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, + LoadAction = LoadAction.Clear, + StoreAction = StoreAction.Store, + ClearColor = new(0, 0, 0, 1) + } + ]; + + DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds + ? new() + { + Texture = ds, + Subresource = new(), + DepthLoadAction = LoadAction.Clear, + DepthStoreAction = StoreAction.DontCare, + ClearDepth = 1.0f + } + : null; + + cmd.BeginRenderPass(colors, depth); + // draws + cmd.EndRenderPass(); + + CommandSubmission frame = cmd.Submit(imageReady); + + imageReady = swapChain.Present(frame); } ``` -- 命名沿用各原生 API 既有前缀:DX12 体系按 `Dxgi` / `D3D12` 区分;Metal 4 与旧 Metal 共存时用 `Mtl` / `Mtl4` 区分;Vulkan 一律 `Vk`。`D3D12CpuDescriptorHandle*` 按 view 类型拆开,避免单入口语义模糊。 -- 非句柄的标量信息(如 `VkQueueFamilyIndex`)也走同一入口,以 `nint` 承载 `uint`,公共面不挂任何平台条件属性。 -- 所有 `GraphicsResource` 子类一律实现该接口;不感兴趣的子类返回 0。 +每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面对称。 diff --git a/sources/Version1/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/App.cs rename to sources/Experiments/CornellBox/App.cs diff --git a/sources/Version1/Experiments/CornellBox/Assets/Fonts/msyh.ttf b/sources/Experiments/CornellBox/Assets/Fonts/msyh.ttf similarity index 100% rename from sources/Version1/Experiments/CornellBox/Assets/Fonts/msyh.ttf rename to sources/Experiments/CornellBox/Assets/Fonts/msyh.ttf diff --git a/sources/Version1/Experiments/CornellBox/Assets/Shaders/.clang-format b/sources/Experiments/CornellBox/Assets/Shaders/.clang-format similarity index 100% rename from sources/Version1/Experiments/CornellBox/Assets/Shaders/.clang-format rename to sources/Experiments/CornellBox/Assets/Shaders/.clang-format diff --git a/sources/Version1/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang similarity index 100% rename from sources/Version1/Experiments/CornellBox/Assets/Shaders/PathTracing.slang rename to sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang diff --git a/sources/Version1/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang similarity index 100% rename from sources/Version1/Experiments/CornellBox/Assets/Shaders/Rasterization.slang rename to sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang diff --git a/sources/Version1/Experiments/CornellBox/CornellBox.csproj b/sources/Experiments/CornellBox/CornellBox.csproj similarity index 100% rename from sources/Version1/Experiments/CornellBox/CornellBox.csproj rename to sources/Experiments/CornellBox/CornellBox.csproj diff --git a/sources/Version1/Experiments/CornellBox/Handlers/CameraHandler.cs b/sources/Experiments/CornellBox/Handlers/CameraHandler.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Handlers/CameraHandler.cs rename to sources/Experiments/CornellBox/Handlers/CameraHandler.cs diff --git a/sources/Version1/Experiments/CornellBox/Handlers/ImGuiHandler.cs b/sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Handlers/ImGuiHandler.cs rename to sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs diff --git a/sources/Version1/Experiments/CornellBox/Helpers/CocoaHelper.cs b/sources/Experiments/CornellBox/Helpers/CocoaHelper.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Helpers/CocoaHelper.cs rename to sources/Experiments/CornellBox/Helpers/CocoaHelper.cs diff --git a/sources/Version1/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs b/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs rename to sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs diff --git a/sources/Version1/Experiments/CornellBox/Program.cs b/sources/Experiments/CornellBox/Program.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Program.cs rename to sources/Experiments/CornellBox/Program.cs diff --git a/sources/Version1/Experiments/CornellBox/Properties/launchSettings.json b/sources/Experiments/CornellBox/Properties/launchSettings.json similarity index 100% rename from sources/Version1/Experiments/CornellBox/Properties/launchSettings.json rename to sources/Experiments/CornellBox/Properties/launchSettings.json diff --git a/sources/Version1/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Renderers/PathTracingRenderer.cs rename to sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs diff --git a/sources/Version1/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Renderers/RasterizationRenderer.cs rename to sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs diff --git a/sources/Version1/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Experiments/CornellBox/Renderers/Renderer.cs similarity index 100% rename from sources/Version1/Experiments/CornellBox/Renderers/Renderer.cs rename to sources/Experiments/CornellBox/Renderers/Renderer.cs diff --git a/sources/Version1/Experiments/Directory.Packages.props b/sources/Experiments/Directory.Packages.props similarity index 78% rename from sources/Version1/Experiments/Directory.Packages.props rename to sources/Experiments/Directory.Packages.props index 3cec044c..6f64c1d7 100644 --- a/sources/Version1/Experiments/Directory.Packages.props +++ b/sources/Experiments/Directory.Packages.props @@ -1,6 +1,6 @@  - + diff --git a/sources/Version1/Experiments/MemoryPlayground/MemoryPlayground.csproj b/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj similarity index 100% rename from sources/Version1/Experiments/MemoryPlayground/MemoryPlayground.csproj rename to sources/Experiments/MemoryPlayground/MemoryPlayground.csproj diff --git a/sources/Version1/Experiments/MemoryPlayground/Program.cs b/sources/Experiments/MemoryPlayground/Program.cs similarity index 100% rename from sources/Version1/Experiments/MemoryPlayground/Program.cs rename to sources/Experiments/MemoryPlayground/Program.cs diff --git a/sources/Version1/Experiments/MemoryPlayground/Properties/launchSettings.json b/sources/Experiments/MemoryPlayground/Properties/launchSettings.json similarity index 100% rename from sources/Version1/Experiments/MemoryPlayground/Properties/launchSettings.json rename to sources/Experiments/MemoryPlayground/Properties/launchSettings.json diff --git a/sources/Version1/Experiments/PlatformDetection/PlatformDetection.csproj b/sources/Experiments/PlatformDetection/PlatformDetection.csproj similarity index 100% rename from sources/Version1/Experiments/PlatformDetection/PlatformDetection.csproj rename to sources/Experiments/PlatformDetection/PlatformDetection.csproj diff --git a/sources/Version1/Experiments/PlatformDetection/Program.cs b/sources/Experiments/PlatformDetection/Program.cs similarity index 100% rename from sources/Version1/Experiments/PlatformDetection/Program.cs rename to sources/Experiments/PlatformDetection/Program.cs diff --git a/sources/Version1/Experiments/PlatformDetection/Properties/launchSettings.json b/sources/Experiments/PlatformDetection/Properties/launchSettings.json similarity index 100% rename from sources/Version1/Experiments/PlatformDetection/Properties/launchSettings.json rename to sources/Experiments/PlatformDetection/Properties/launchSettings.json diff --git a/sources/Version1/Experiments/README.md b/sources/Experiments/README.md similarity index 100% rename from sources/Version1/Experiments/README.md rename to sources/Experiments/README.md diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs similarity index 100% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs rename to sources/Extensions/Zenith.NET.Extensions.ImGui/IImGuiPlatformBindings.cs diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs similarity index 100% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs rename to sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiColorSpace.cs diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs similarity index 100% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs rename to sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs similarity index 100% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs rename to sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj b/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj similarity index 86% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj rename to sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj index 61a98dad..2974c1c7 100644 --- a/sources/Version1/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs similarity index 100% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs rename to sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj similarity index 86% rename from sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj rename to sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj index 9186d7c8..42875517 100644 --- a/sources/Version1/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj b/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj similarity index 86% rename from sources/Version1/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj rename to sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj index b438a665..3b50cc98 100644 --- a/sources/Version1/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs similarity index 100% rename from sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs rename to sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs diff --git a/sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj b/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj similarity index 86% rename from sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj rename to sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj index 0027a99e..5e18602f 100644 --- a/sources/Version1/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Avalonia/Surface.cs rename to sources/Views/Zenith.NET.Views.Avalonia/Surface.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj b/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj similarity index 86% rename from sources/Version1/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj rename to sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj index 2c663920..129003ae 100644 --- a/sources/Version1/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj +++ b/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Avalonia/ZenithView.cs rename to sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Extensions.cs b/sources/Views/Zenith.NET.Views.Maui/Extensions.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Extensions.cs rename to sources/Views/Zenith.NET.Views.Maui/Extensions.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/ISwapChainPanelNative.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj b/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj similarity index 93% rename from sources/Version1/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj rename to sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj index 4ee114d1..2441e7d3 100644 --- a/sources/Version1/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj +++ b/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj @@ -19,6 +19,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views.Maui/ZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.Maui/ZenithView.cs rename to sources/Views/Zenith.NET.Views.Maui/ZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WPF/D3D.cs b/sources/Views/Zenith.NET.Views.WPF/D3D.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WPF/D3D.cs rename to sources/Views/Zenith.NET.Views.WPF/D3D.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WPF/D3DTexture.cs rename to sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj b/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj similarity index 90% rename from sources/Version1/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj rename to sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj index 5f5bceca..123c679d 100644 --- a/sources/Version1/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj +++ b/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj @@ -15,6 +15,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WPF/ZenithView.cs rename to sources/Views/Zenith.NET.Views.WPF/ZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj b/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj similarity index 87% rename from sources/Version1/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj rename to sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj index f155f384..1c8d8bf9 100644 --- a/sources/Version1/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj +++ b/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj @@ -10,6 +10,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WinForms/ZenithView.cs rename to sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj b/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj similarity index 94% rename from sources/Version1/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj rename to sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj index b14c982a..c1febc14 100644 --- a/sources/Version1/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj +++ b/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj @@ -23,6 +23,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs rename to sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs rename to sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs diff --git a/sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views.WinUI/ZenithView.cs rename to sources/Views/Zenith.NET.Views.WinUI/ZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views/FrameScheduler.cs b/sources/Views/Zenith.NET.Views/FrameScheduler.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views/FrameScheduler.cs rename to sources/Views/Zenith.NET.Views/FrameScheduler.cs diff --git a/sources/Version1/Views/Zenith.NET.Views/IZenithView.cs b/sources/Views/Zenith.NET.Views/IZenithView.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views/IZenithView.cs rename to sources/Views/Zenith.NET.Views/IZenithView.cs diff --git a/sources/Version1/Views/Zenith.NET.Views/RenderEventArgs.cs b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views/RenderEventArgs.cs rename to sources/Views/Zenith.NET.Views/RenderEventArgs.cs diff --git a/sources/Version1/Views/Zenith.NET.Views/UpdateEventArgs.cs b/sources/Views/Zenith.NET.Views/UpdateEventArgs.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views/UpdateEventArgs.cs rename to sources/Views/Zenith.NET.Views/UpdateEventArgs.cs diff --git a/sources/Version1/Views/Zenith.NET.Views/Zenith.NET.Views.csproj b/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj similarity index 82% rename from sources/Version1/Views/Zenith.NET.Views/Zenith.NET.Views.csproj rename to sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj index a9be9526..a20d2eb4 100644 --- a/sources/Version1/Views/Zenith.NET.Views/Zenith.NET.Views.csproj +++ b/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj @@ -8,6 +8,6 @@ - + diff --git a/sources/Version1/Views/Zenith.NET.Views/ZenithViewHelper.cs b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs similarity index 100% rename from sources/Version1/Views/Zenith.NET.Views/ZenithViewHelper.cs rename to sources/Views/Zenith.NET.Views/ZenithViewHelper.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs rename to sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXBuffer.cs rename to sources/Zenith.NET.DirectX12/DXBuffer.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXBufferView.cs rename to sources/Zenith.NET.DirectX12/DXBufferView.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Zenith.NET.DirectX12/DXCapabilities.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXCapabilities.cs rename to sources/Zenith.NET.DirectX12/DXCapabilities.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXCommandBuffer.cs rename to sources/Zenith.NET.DirectX12/DXCommandBuffer.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXCommandQueue.cs rename to sources/Zenith.NET.DirectX12/DXCommandQueue.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXComputePipeline.cs rename to sources/Zenith.NET.DirectX12/DXComputePipeline.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXDescriptorAllocator.cs b/sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXDescriptorAllocator.cs rename to sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXDescriptorPool.cs b/sources/Zenith.NET.DirectX12/DXDescriptorPool.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXDescriptorPool.cs rename to sources/Zenith.NET.DirectX12/DXDescriptorPool.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXDescriptorTable.cs b/sources/Zenith.NET.DirectX12/DXDescriptorTable.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXDescriptorTable.cs rename to sources/Zenith.NET.DirectX12/DXDescriptorTable.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXDescriptorToken.cs rename to sources/Zenith.NET.DirectX12/DXDescriptorToken.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXFence.cs b/sources/Zenith.NET.DirectX12/DXFence.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXFence.cs rename to sources/Zenith.NET.DirectX12/DXFence.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXFormats.cs rename to sources/Zenith.NET.DirectX12/DXFormats.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXFrameBuffer.cs b/sources/Zenith.NET.DirectX12/DXFrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXFrameBuffer.cs rename to sources/Zenith.NET.DirectX12/DXFrameBuffer.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXGraphicsContext.cs rename to sources/Zenith.NET.DirectX12/DXGraphicsContext.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXGraphicsPipeline.cs rename to sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXHeap.cs rename to sources/Zenith.NET.DirectX12/DXHeap.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs rename to sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXQueryHeap.cs rename to sources/Zenith.NET.DirectX12/DXQueryHeap.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXResourceTable.cs rename to sources/Zenith.NET.DirectX12/DXResourceTable.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXSampler.cs rename to sources/Zenith.NET.DirectX12/DXSampler.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXShader.cs b/sources/Zenith.NET.DirectX12/DXShader.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXShader.cs rename to sources/Zenith.NET.DirectX12/DXShader.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXSwapChain.cs rename to sources/Zenith.NET.DirectX12/DXSwapChain.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs b/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs rename to sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXTexture.cs rename to sources/Zenith.NET.DirectX12/DXTexture.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXTextureView.cs rename to sources/Zenith.NET.DirectX12/DXTextureView.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs rename to sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/DXValidationLayer.cs rename to sources/Zenith.NET.DirectX12/DXValidationLayer.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/Extensions.cs rename to sources/Zenith.NET.DirectX12/Extensions.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/PipelineStateStream2.cs b/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/PipelineStateStream2.cs rename to sources/Zenith.NET.DirectX12/PipelineStateStream2.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/PixHelpers.cs b/sources/Zenith.NET.DirectX12/PixHelpers.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/PixHelpers.cs rename to sources/Zenith.NET.DirectX12/PixHelpers.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs similarity index 100% rename from sources/Version1/Zenith.NET.DirectX12/Usings.cs rename to sources/Zenith.NET.DirectX12/Usings.cs diff --git a/sources/Version1/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj b/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj similarity index 87% rename from sources/Version1/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj rename to sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj index bc0501fd..fb0495be 100644 --- a/sources/Version1/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj +++ b/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/Extensions.cs rename to sources/Zenith.NET.Metal/Extensions.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs rename to sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLBuffer.cs rename to sources/Zenith.NET.Metal/MTLBuffer.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLBufferView.cs rename to sources/Zenith.NET.Metal/MTLBufferView.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLCapabilities.cs b/sources/Zenith.NET.Metal/MTLCapabilities.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLCapabilities.cs rename to sources/Zenith.NET.Metal/MTLCapabilities.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLCommandBuffer.cs rename to sources/Zenith.NET.Metal/MTLCommandBuffer.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLCommandEncoder.cs rename to sources/Zenith.NET.Metal/MTLCommandEncoder.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLCommandQueue.cs rename to sources/Zenith.NET.Metal/MTLCommandQueue.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLComputePipeline.cs b/sources/Zenith.NET.Metal/MTLComputePipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLComputePipeline.cs rename to sources/Zenith.NET.Metal/MTLComputePipeline.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLFence.cs b/sources/Zenith.NET.Metal/MTLFence.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLFence.cs rename to sources/Zenith.NET.Metal/MTLFence.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLFormats.cs rename to sources/Zenith.NET.Metal/MTLFormats.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLFrameBuffer.cs b/sources/Zenith.NET.Metal/MTLFrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLFrameBuffer.cs rename to sources/Zenith.NET.Metal/MTLFrameBuffer.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLGraphicsContext.cs rename to sources/Zenith.NET.Metal/MTLGraphicsContext.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLGraphicsPipeline.cs rename to sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLHeap.cs rename to sources/Zenith.NET.Metal/MTLHeap.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLMeshShadingPipeline.cs b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLMeshShadingPipeline.cs rename to sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLQueryHeap.cs b/sources/Zenith.NET.Metal/MTLQueryHeap.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLQueryHeap.cs rename to sources/Zenith.NET.Metal/MTLQueryHeap.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLResourceTable.cs rename to sources/Zenith.NET.Metal/MTLResourceTable.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLSampler.cs b/sources/Zenith.NET.Metal/MTLSampler.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLSampler.cs rename to sources/Zenith.NET.Metal/MTLSampler.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLShader.cs b/sources/Zenith.NET.Metal/MTLShader.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLShader.cs rename to sources/Zenith.NET.Metal/MTLShader.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLSwapChain.cs rename to sources/Zenith.NET.Metal/MTLSwapChain.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs b/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs rename to sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLTexture.cs rename to sources/Zenith.NET.Metal/MTLTexture.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLTextureView.cs rename to sources/Zenith.NET.Metal/MTLTextureView.cs diff --git a/sources/Version1/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs rename to sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET.Metal/NSAutorelease.cs b/sources/Zenith.NET.Metal/NSAutorelease.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/NSAutorelease.cs rename to sources/Zenith.NET.Metal/NSAutorelease.cs diff --git a/sources/Version1/Zenith.NET.Metal/Usings.cs b/sources/Zenith.NET.Metal/Usings.cs similarity index 100% rename from sources/Version1/Zenith.NET.Metal/Usings.cs rename to sources/Zenith.NET.Metal/Usings.cs diff --git a/sources/Version1/Zenith.NET.Metal/Zenith.NET.Metal.csproj b/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj similarity index 86% rename from sources/Version1/Zenith.NET.Metal/Zenith.NET.Metal.csproj rename to sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj index ff9099d2..77ac94c7 100644 --- a/sources/Version1/Zenith.NET.Metal/Zenith.NET.Metal.csproj +++ b/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj @@ -12,6 +12,6 @@ - + diff --git a/sources/Version1/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/Extensions.cs rename to sources/Zenith.NET.Vulkan/Extensions.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/KhrRayQuery.cs b/sources/Zenith.NET.Vulkan/KhrRayQuery.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/KhrRayQuery.cs rename to sources/Zenith.NET.Vulkan/KhrRayQuery.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/Usings.cs rename to sources/Zenith.NET.Vulkan/Usings.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs rename to sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKBuffer.cs rename to sources/Zenith.NET.Vulkan/VKBuffer.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKBufferView.cs b/sources/Zenith.NET.Vulkan/VKBufferView.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKBufferView.cs rename to sources/Zenith.NET.Vulkan/VKBufferView.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKCapabilities.cs b/sources/Zenith.NET.Vulkan/VKCapabilities.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKCapabilities.cs rename to sources/Zenith.NET.Vulkan/VKCapabilities.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKCommandBuffer.cs rename to sources/Zenith.NET.Vulkan/VKCommandBuffer.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKCommandQueue.cs rename to sources/Zenith.NET.Vulkan/VKCommandQueue.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKComputePipeline.cs rename to sources/Zenith.NET.Vulkan/VKComputePipeline.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKDeviceMemory.cs b/sources/Zenith.NET.Vulkan/VKDeviceMemory.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKDeviceMemory.cs rename to sources/Zenith.NET.Vulkan/VKDeviceMemory.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKFence.cs b/sources/Zenith.NET.Vulkan/VKFence.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKFence.cs rename to sources/Zenith.NET.Vulkan/VKFence.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKFormats.cs rename to sources/Zenith.NET.Vulkan/VKFormats.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKFrameBuffer.cs b/sources/Zenith.NET.Vulkan/VKFrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKFrameBuffer.cs rename to sources/Zenith.NET.Vulkan/VKFrameBuffer.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKGraphicsContext.cs rename to sources/Zenith.NET.Vulkan/VKGraphicsContext.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKGraphicsPipeline.cs rename to sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs rename to sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKQueryHeap.cs b/sources/Zenith.NET.Vulkan/VKQueryHeap.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKQueryHeap.cs rename to sources/Zenith.NET.Vulkan/VKQueryHeap.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKResourceTable.cs rename to sources/Zenith.NET.Vulkan/VKResourceTable.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKSampler.cs b/sources/Zenith.NET.Vulkan/VKSampler.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKSampler.cs rename to sources/Zenith.NET.Vulkan/VKSampler.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKShader.cs b/sources/Zenith.NET.Vulkan/VKShader.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKShader.cs rename to sources/Zenith.NET.Vulkan/VKShader.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKSwapChain.cs rename to sources/Zenith.NET.Vulkan/VKSwapChain.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs b/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs rename to sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKTexture.cs rename to sources/Zenith.NET.Vulkan/VKTexture.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Zenith.NET.Vulkan/VKTextureView.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKTextureView.cs rename to sources/Zenith.NET.Vulkan/VKTextureView.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs rename to sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs similarity index 100% rename from sources/Version1/Zenith.NET.Vulkan/VKValidationLayer.cs rename to sources/Zenith.NET.Vulkan/VKValidationLayer.cs diff --git a/sources/Version1/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj similarity index 91% rename from sources/Version1/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj rename to sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj index a12a8713..c3be14b0 100644 --- a/sources/Version1/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj +++ b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj @@ -15,6 +15,6 @@ - + diff --git a/sources/Version1/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs similarity index 100% rename from sources/Version1/Zenith.NET/BlendStates.cs rename to sources/Zenith.NET/BlendStates.cs diff --git a/sources/Version1/Zenith.NET/BottomLevelAccelerationStructure.cs b/sources/Zenith.NET/BottomLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET/BottomLevelAccelerationStructure.cs rename to sources/Zenith.NET/BottomLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs similarity index 100% rename from sources/Version1/Zenith.NET/Buffer.cs rename to sources/Zenith.NET/Buffer.cs diff --git a/sources/Version1/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs similarity index 100% rename from sources/Version1/Zenith.NET/BufferView.cs rename to sources/Zenith.NET/BufferView.cs diff --git a/sources/Version1/Zenith.NET/Capabilities.cs b/sources/Zenith.NET/Capabilities.cs similarity index 100% rename from sources/Version1/Zenith.NET/Capabilities.cs rename to sources/Zenith.NET/Capabilities.cs diff --git a/sources/Version1/Zenith.NET/ClearValues.cs b/sources/Zenith.NET/ClearValues.cs similarity index 100% rename from sources/Version1/Zenith.NET/ClearValues.cs rename to sources/Zenith.NET/ClearValues.cs diff --git a/sources/Version1/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET/CommandBuffer.cs rename to sources/Zenith.NET/CommandBuffer.cs diff --git a/sources/Version1/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs similarity index 100% rename from sources/Version1/Zenith.NET/CommandQueue.cs rename to sources/Zenith.NET/CommandQueue.cs diff --git a/sources/Version1/Zenith.NET/ComputePipeline.cs b/sources/Zenith.NET/ComputePipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET/ComputePipeline.cs rename to sources/Zenith.NET/ComputePipeline.cs diff --git a/sources/Version1/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs similarity index 100% rename from sources/Version1/Zenith.NET/DepthStencilStates.cs rename to sources/Zenith.NET/DepthStencilStates.cs diff --git a/sources/Version1/Zenith.NET/DisposableObject.cs b/sources/Zenith.NET/DisposableObject.cs similarity index 100% rename from sources/Version1/Zenith.NET/DisposableObject.cs rename to sources/Zenith.NET/DisposableObject.cs diff --git a/sources/Version1/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs b/sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs rename to sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/AddressMode.cs b/sources/Zenith.NET/Enums/AddressMode.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/AddressMode.cs rename to sources/Zenith.NET/Enums/AddressMode.cs diff --git a/sources/Version1/Zenith.NET/Enums/Backend.cs b/sources/Zenith.NET/Enums/Backend.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/Backend.cs rename to sources/Zenith.NET/Enums/Backend.cs diff --git a/sources/Version1/Zenith.NET/Enums/Blend.cs b/sources/Zenith.NET/Enums/Blend.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/Blend.cs rename to sources/Zenith.NET/Enums/Blend.cs diff --git a/sources/Version1/Zenith.NET/Enums/BlendOp.cs b/sources/Zenith.NET/Enums/BlendOp.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/BlendOp.cs rename to sources/Zenith.NET/Enums/BlendOp.cs diff --git a/sources/Version1/Zenith.NET/Enums/BorderColor.cs b/sources/Zenith.NET/Enums/BorderColor.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/BorderColor.cs rename to sources/Zenith.NET/Enums/BorderColor.cs diff --git a/sources/Version1/Zenith.NET/Enums/BufferUsageFlags.cs b/sources/Zenith.NET/Enums/BufferUsageFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/BufferUsageFlags.cs rename to sources/Zenith.NET/Enums/BufferUsageFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/ClearFlags.cs b/sources/Zenith.NET/Enums/ClearFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ClearFlags.cs rename to sources/Zenith.NET/Enums/ClearFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/ColorComponentFlags.cs b/sources/Zenith.NET/Enums/ColorComponentFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ColorComponentFlags.cs rename to sources/Zenith.NET/Enums/ColorComponentFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/CommandQueueType.cs b/sources/Zenith.NET/Enums/CommandQueueType.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/CommandQueueType.cs rename to sources/Zenith.NET/Enums/CommandQueueType.cs diff --git a/sources/Version1/Zenith.NET/Enums/ComparisonFunc.cs b/sources/Zenith.NET/Enums/ComparisonFunc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ComparisonFunc.cs rename to sources/Zenith.NET/Enums/ComparisonFunc.cs diff --git a/sources/Version1/Zenith.NET/Enums/CullMode.cs b/sources/Zenith.NET/Enums/CullMode.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/CullMode.cs rename to sources/Zenith.NET/Enums/CullMode.cs diff --git a/sources/Version1/Zenith.NET/Enums/ElementFormat.cs b/sources/Zenith.NET/Enums/ElementFormat.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ElementFormat.cs rename to sources/Zenith.NET/Enums/ElementFormat.cs diff --git a/sources/Version1/Zenith.NET/Enums/ElementSemantic.cs b/sources/Zenith.NET/Enums/ElementSemantic.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ElementSemantic.cs rename to sources/Zenith.NET/Enums/ElementSemantic.cs diff --git a/sources/Version1/Zenith.NET/Enums/FillMode.cs b/sources/Zenith.NET/Enums/FillMode.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/FillMode.cs rename to sources/Zenith.NET/Enums/FillMode.cs diff --git a/sources/Version1/Zenith.NET/Enums/Filter.cs b/sources/Zenith.NET/Enums/Filter.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/Filter.cs rename to sources/Zenith.NET/Enums/Filter.cs diff --git a/sources/Version1/Zenith.NET/Enums/FrontFace.cs b/sources/Zenith.NET/Enums/FrontFace.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/FrontFace.cs rename to sources/Zenith.NET/Enums/FrontFace.cs diff --git a/sources/Version1/Zenith.NET/Enums/IndexFormat.cs b/sources/Zenith.NET/Enums/IndexFormat.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/IndexFormat.cs rename to sources/Zenith.NET/Enums/IndexFormat.cs diff --git a/sources/Version1/Zenith.NET/Enums/MessageSeverity.cs b/sources/Zenith.NET/Enums/MessageSeverity.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/MessageSeverity.cs rename to sources/Zenith.NET/Enums/MessageSeverity.cs diff --git a/sources/Version1/Zenith.NET/Enums/MessageSource.cs b/sources/Zenith.NET/Enums/MessageSource.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/MessageSource.cs rename to sources/Zenith.NET/Enums/MessageSource.cs diff --git a/sources/Version1/Zenith.NET/Enums/PixelFormat.cs b/sources/Zenith.NET/Enums/PixelFormat.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/PixelFormat.cs rename to sources/Zenith.NET/Enums/PixelFormat.cs diff --git a/sources/Version1/Zenith.NET/Enums/PrimitiveTopology.cs b/sources/Zenith.NET/Enums/PrimitiveTopology.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/PrimitiveTopology.cs rename to sources/Zenith.NET/Enums/PrimitiveTopology.cs diff --git a/sources/Version1/Zenith.NET/Enums/QueryType.cs b/sources/Zenith.NET/Enums/QueryType.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/QueryType.cs rename to sources/Zenith.NET/Enums/QueryType.cs diff --git a/sources/Version1/Zenith.NET/Enums/RayTracingGeometryFlags.cs b/sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/RayTracingGeometryFlags.cs rename to sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/RayTracingGeometryType.cs b/sources/Zenith.NET/Enums/RayTracingGeometryType.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/RayTracingGeometryType.cs rename to sources/Zenith.NET/Enums/RayTracingGeometryType.cs diff --git a/sources/Version1/Zenith.NET/Enums/RayTracingInstanceFlags.cs b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/RayTracingInstanceFlags.cs rename to sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/ResourceType.cs b/sources/Zenith.NET/Enums/ResourceType.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ResourceType.cs rename to sources/Zenith.NET/Enums/ResourceType.cs diff --git a/sources/Version1/Zenith.NET/Enums/SampleCount.cs b/sources/Zenith.NET/Enums/SampleCount.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/SampleCount.cs rename to sources/Zenith.NET/Enums/SampleCount.cs diff --git a/sources/Version1/Zenith.NET/Enums/ShaderStageFlags.cs b/sources/Zenith.NET/Enums/ShaderStageFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/ShaderStageFlags.cs rename to sources/Zenith.NET/Enums/ShaderStageFlags.cs diff --git a/sources/Version1/Zenith.NET/Enums/StencilOp.cs b/sources/Zenith.NET/Enums/StencilOp.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/StencilOp.cs rename to sources/Zenith.NET/Enums/StencilOp.cs diff --git a/sources/Version1/Zenith.NET/Enums/StringEncoding.cs b/sources/Zenith.NET/Enums/StringEncoding.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/StringEncoding.cs rename to sources/Zenith.NET/Enums/StringEncoding.cs diff --git a/sources/Version1/Zenith.NET/Enums/SurfaceType.cs b/sources/Zenith.NET/Enums/SurfaceType.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/SurfaceType.cs rename to sources/Zenith.NET/Enums/SurfaceType.cs diff --git a/sources/Version1/Zenith.NET/Enums/TextureType.cs b/sources/Zenith.NET/Enums/TextureType.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/TextureType.cs rename to sources/Zenith.NET/Enums/TextureType.cs diff --git a/sources/Version1/Zenith.NET/Enums/TextureUsageFlags.cs b/sources/Zenith.NET/Enums/TextureUsageFlags.cs similarity index 100% rename from sources/Version1/Zenith.NET/Enums/TextureUsageFlags.cs rename to sources/Zenith.NET/Enums/TextureUsageFlags.cs diff --git a/sources/Version1/Zenith.NET/FrameBuffer.cs b/sources/Zenith.NET/FrameBuffer.cs similarity index 100% rename from sources/Version1/Zenith.NET/FrameBuffer.cs rename to sources/Zenith.NET/FrameBuffer.cs diff --git a/sources/Version1/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs similarity index 100% rename from sources/Version1/Zenith.NET/GraphicsContext.cs rename to sources/Zenith.NET/GraphicsContext.cs diff --git a/sources/Version1/Zenith.NET/GraphicsPipeline.cs b/sources/Zenith.NET/GraphicsPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET/GraphicsPipeline.cs rename to sources/Zenith.NET/GraphicsPipeline.cs diff --git a/sources/Version1/Zenith.NET/GraphicsResource.cs b/sources/Zenith.NET/GraphicsResource.cs similarity index 100% rename from sources/Version1/Zenith.NET/GraphicsResource.cs rename to sources/Zenith.NET/GraphicsResource.cs diff --git a/sources/Version1/Zenith.NET/Interfaces/IBindableResource.cs b/sources/Zenith.NET/Interfaces/IBindableResource.cs similarity index 100% rename from sources/Version1/Zenith.NET/Interfaces/IBindableResource.cs rename to sources/Zenith.NET/Interfaces/IBindableResource.cs diff --git a/sources/Version1/Zenith.NET/Interfaces/IDisposableObject.cs b/sources/Zenith.NET/Interfaces/IDisposableObject.cs similarity index 100% rename from sources/Version1/Zenith.NET/Interfaces/IDisposableObject.cs rename to sources/Zenith.NET/Interfaces/IDisposableObject.cs diff --git a/sources/Version1/Zenith.NET/MeshShadingPipeline.cs b/sources/Zenith.NET/MeshShadingPipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET/MeshShadingPipeline.cs rename to sources/Zenith.NET/MeshShadingPipeline.cs diff --git a/sources/Version1/Zenith.NET/Pipeline.cs b/sources/Zenith.NET/Pipeline.cs similarity index 100% rename from sources/Version1/Zenith.NET/Pipeline.cs rename to sources/Zenith.NET/Pipeline.cs diff --git a/sources/Version1/Zenith.NET/QueryHeap.cs b/sources/Zenith.NET/QueryHeap.cs similarity index 100% rename from sources/Version1/Zenith.NET/QueryHeap.cs rename to sources/Zenith.NET/QueryHeap.cs diff --git a/sources/Version1/Zenith.NET/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs similarity index 100% rename from sources/Version1/Zenith.NET/RasterizerStates.cs rename to sources/Zenith.NET/RasterizerStates.cs diff --git a/sources/Version1/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs similarity index 100% rename from sources/Version1/Zenith.NET/ResourceTable.cs rename to sources/Zenith.NET/ResourceTable.cs diff --git a/sources/Version1/Zenith.NET/Sampler.cs b/sources/Zenith.NET/Sampler.cs similarity index 100% rename from sources/Version1/Zenith.NET/Sampler.cs rename to sources/Zenith.NET/Sampler.cs diff --git a/sources/Version1/Zenith.NET/Shader.cs b/sources/Zenith.NET/Shader.cs similarity index 100% rename from sources/Version1/Zenith.NET/Shader.cs rename to sources/Zenith.NET/Shader.cs diff --git a/sources/Version1/Zenith.NET/Structs/BlendState.cs b/sources/Zenith.NET/Structs/BlendState.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/BlendState.cs rename to sources/Zenith.NET/Structs/BlendState.cs diff --git a/sources/Version1/Zenith.NET/Structs/BlendStateRenderTarget.cs b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/BlendStateRenderTarget.cs rename to sources/Zenith.NET/Structs/BlendStateRenderTarget.cs diff --git a/sources/Version1/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs rename to sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/BufferDesc.cs rename to sources/Zenith.NET/Structs/BufferDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Zenith.NET/Structs/BufferViewDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/BufferViewDesc.cs rename to sources/Zenith.NET/Structs/BufferViewDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/ClearValue.cs b/sources/Zenith.NET/Structs/ClearValue.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/ClearValue.cs rename to sources/Zenith.NET/Structs/ClearValue.cs diff --git a/sources/Version1/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/ComputePipelineDesc.cs rename to sources/Zenith.NET/Structs/ComputePipelineDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/DepthStencilState.cs b/sources/Zenith.NET/Structs/DepthStencilState.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/DepthStencilState.cs rename to sources/Zenith.NET/Structs/DepthStencilState.cs diff --git a/sources/Version1/Zenith.NET/Structs/DepthStencilStateOp.cs b/sources/Zenith.NET/Structs/DepthStencilStateOp.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/DepthStencilStateOp.cs rename to sources/Zenith.NET/Structs/DepthStencilStateOp.cs diff --git a/sources/Version1/Zenith.NET/Structs/FrameBufferAttachment.cs b/sources/Zenith.NET/Structs/FrameBufferAttachment.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/FrameBufferAttachment.cs rename to sources/Zenith.NET/Structs/FrameBufferAttachment.cs diff --git a/sources/Version1/Zenith.NET/Structs/FrameBufferDesc.cs b/sources/Zenith.NET/Structs/FrameBufferDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/FrameBufferDesc.cs rename to sources/Zenith.NET/Structs/FrameBufferDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/GraphicsPipelineDesc.cs rename to sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/IndirectDispatchArgs.cs b/sources/Zenith.NET/Structs/IndirectDispatchArgs.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/IndirectDispatchArgs.cs rename to sources/Zenith.NET/Structs/IndirectDispatchArgs.cs diff --git a/sources/Version1/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs b/sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs rename to sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs diff --git a/sources/Version1/Zenith.NET/Structs/IndirectDrawArgs.cs b/sources/Zenith.NET/Structs/IndirectDrawArgs.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/IndirectDrawArgs.cs rename to sources/Zenith.NET/Structs/IndirectDrawArgs.cs diff --git a/sources/Version1/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs b/sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs rename to sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs diff --git a/sources/Version1/Zenith.NET/Structs/InputElement.cs b/sources/Zenith.NET/Structs/InputElement.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/InputElement.cs rename to sources/Zenith.NET/Structs/InputElement.cs diff --git a/sources/Version1/Zenith.NET/Structs/InputLayout.cs b/sources/Zenith.NET/Structs/InputLayout.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/InputLayout.cs rename to sources/Zenith.NET/Structs/InputLayout.cs diff --git a/sources/Version1/Zenith.NET/Structs/MappedMemory.cs b/sources/Zenith.NET/Structs/MappedMemory.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/MappedMemory.cs rename to sources/Zenith.NET/Structs/MappedMemory.cs diff --git a/sources/Version1/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/MeshShadingPipelineDesc.cs rename to sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/Output.cs b/sources/Zenith.NET/Structs/Output.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/Output.cs rename to sources/Zenith.NET/Structs/Output.cs diff --git a/sources/Version1/Zenith.NET/Structs/QueryHeapDesc.cs b/sources/Zenith.NET/Structs/QueryHeapDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/QueryHeapDesc.cs rename to sources/Zenith.NET/Structs/QueryHeapDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/RasterizerState.cs rename to sources/Zenith.NET/Structs/RasterizerState.cs diff --git a/sources/Version1/Zenith.NET/Structs/RayTracingAABBs.cs b/sources/Zenith.NET/Structs/RayTracingAABBs.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/RayTracingAABBs.cs rename to sources/Zenith.NET/Structs/RayTracingAABBs.cs diff --git a/sources/Version1/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Zenith.NET/Structs/RayTracingGeometry.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/RayTracingGeometry.cs rename to sources/Zenith.NET/Structs/RayTracingGeometry.cs diff --git a/sources/Version1/Zenith.NET/Structs/RayTracingInstance.cs b/sources/Zenith.NET/Structs/RayTracingInstance.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/RayTracingInstance.cs rename to sources/Zenith.NET/Structs/RayTracingInstance.cs diff --git a/sources/Version1/Zenith.NET/Structs/RayTracingTriangles.cs b/sources/Zenith.NET/Structs/RayTracingTriangles.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/RayTracingTriangles.cs rename to sources/Zenith.NET/Structs/RayTracingTriangles.cs diff --git a/sources/Version1/Zenith.NET/Structs/RenderStates.cs b/sources/Zenith.NET/Structs/RenderStates.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/RenderStates.cs rename to sources/Zenith.NET/Structs/RenderStates.cs diff --git a/sources/Version1/Zenith.NET/Structs/ResourceBinding.cs b/sources/Zenith.NET/Structs/ResourceBinding.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/ResourceBinding.cs rename to sources/Zenith.NET/Structs/ResourceBinding.cs diff --git a/sources/Version1/Zenith.NET/Structs/ResourceTableDesc.cs b/sources/Zenith.NET/Structs/ResourceTableDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/ResourceTableDesc.cs rename to sources/Zenith.NET/Structs/ResourceTableDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/SamplerDesc.cs rename to sources/Zenith.NET/Structs/SamplerDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/Scissor.cs b/sources/Zenith.NET/Structs/Scissor.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/Scissor.cs rename to sources/Zenith.NET/Structs/Scissor.cs diff --git a/sources/Version1/Zenith.NET/Structs/ShaderDesc.cs b/sources/Zenith.NET/Structs/ShaderDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/ShaderDesc.cs rename to sources/Zenith.NET/Structs/ShaderDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/Surface.cs b/sources/Zenith.NET/Structs/Surface.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/Surface.cs rename to sources/Zenith.NET/Structs/Surface.cs diff --git a/sources/Version1/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Zenith.NET/Structs/SwapChainDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/SwapChainDesc.cs rename to sources/Zenith.NET/Structs/SwapChainDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/TextureDesc.cs rename to sources/Zenith.NET/Structs/TextureDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/TextureExtent.cs b/sources/Zenith.NET/Structs/TextureExtent.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/TextureExtent.cs rename to sources/Zenith.NET/Structs/TextureExtent.cs diff --git a/sources/Version1/Zenith.NET/Structs/TextureOffset.cs b/sources/Zenith.NET/Structs/TextureOffset.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/TextureOffset.cs rename to sources/Zenith.NET/Structs/TextureOffset.cs diff --git a/sources/Version1/Zenith.NET/Structs/TextureSlice.cs b/sources/Zenith.NET/Structs/TextureSlice.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/TextureSlice.cs rename to sources/Zenith.NET/Structs/TextureSlice.cs diff --git a/sources/Version1/Zenith.NET/Structs/TextureViewDesc.cs b/sources/Zenith.NET/Structs/TextureViewDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/TextureViewDesc.cs rename to sources/Zenith.NET/Structs/TextureViewDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs rename to sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs diff --git a/sources/Version1/Zenith.NET/Structs/Viewport.cs b/sources/Zenith.NET/Structs/Viewport.cs similarity index 100% rename from sources/Version1/Zenith.NET/Structs/Viewport.cs rename to sources/Zenith.NET/Structs/Viewport.cs diff --git a/sources/Version1/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs similarity index 100% rename from sources/Version1/Zenith.NET/SwapChain.cs rename to sources/Zenith.NET/SwapChain.cs diff --git a/sources/Version1/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs similarity index 100% rename from sources/Version1/Zenith.NET/Texture.cs rename to sources/Zenith.NET/Texture.cs diff --git a/sources/Version1/Zenith.NET/TextureView.cs b/sources/Zenith.NET/TextureView.cs similarity index 100% rename from sources/Version1/Zenith.NET/TextureView.cs rename to sources/Zenith.NET/TextureView.cs diff --git a/sources/Version1/Zenith.NET/TopLevelAccelerationStructure.cs b/sources/Zenith.NET/TopLevelAccelerationStructure.cs similarity index 100% rename from sources/Version1/Zenith.NET/TopLevelAccelerationStructure.cs rename to sources/Zenith.NET/TopLevelAccelerationStructure.cs diff --git a/sources/Version1/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs similarity index 100% rename from sources/Version1/Zenith.NET/Uploader.cs rename to sources/Zenith.NET/Uploader.cs diff --git a/sources/Version1/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs similarity index 100% rename from sources/Version1/Zenith.NET/ValidationLayer.cs rename to sources/Zenith.NET/ValidationLayer.cs diff --git a/sources/Version1/Zenith.NET/ValidationMessageArgs.cs b/sources/Zenith.NET/ValidationMessageArgs.cs similarity index 100% rename from sources/Version1/Zenith.NET/ValidationMessageArgs.cs rename to sources/Zenith.NET/ValidationMessageArgs.cs diff --git a/sources/Version1/Zenith.NET/Zenith.NET.csproj b/sources/Zenith.NET/Zenith.NET.csproj similarity index 78% rename from sources/Version1/Zenith.NET/Zenith.NET.csproj rename to sources/Zenith.NET/Zenith.NET.csproj index d8b41106..70d50a7c 100644 --- a/sources/Version1/Zenith.NET/Zenith.NET.csproj +++ b/sources/Zenith.NET/Zenith.NET.csproj @@ -5,6 +5,6 @@ IDE0130 - + diff --git a/sources/Version1/Zenith.NET/ZenithHelper.cs b/sources/Zenith.NET/ZenithHelper.cs similarity index 100% rename from sources/Version1/Zenith.NET/ZenithHelper.cs rename to sources/Zenith.NET/ZenithHelper.cs diff --git a/sources/Version1/Zenith.NET/ZenithMarshal.cs b/sources/Zenith.NET/ZenithMarshal.cs similarity index 100% rename from sources/Version1/Zenith.NET/ZenithMarshal.cs rename to sources/Zenith.NET/ZenithMarshal.cs From a8aeb785c90bbbb623efb4bdcda7a187b331448e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 23 Apr 2026 18:27:39 +0800 Subject: [PATCH 034/466] Restructure solution folders and add BOM to files Reorganized solution structure in `Zenith.NET.slnx` for clarity and consistency, flattening `/Sources/` and updating folder/project ordering. Added UTF-8 BOM to all `.csproj` and key markdown files; no functional changes to project configurations. --- Zenith.NET.slnx | 30 +++++++++---------- rhi-redesign.draft.md | 2 +- rhi-redesign.zh.draft.md | 2 +- .../Experiments/CornellBox/CornellBox.csproj | 2 +- .../MemoryPlayground/MemoryPlayground.csproj | 2 +- .../PlatformDetection.csproj | 2 +- .../Zenith.NET.Extensions.ImGui.csproj | 2 +- .../Zenith.NET.Extensions.ImageSharp.csproj | 2 +- .../Zenith.NET.Extensions.Skia.csproj | 2 +- .../Zenith.NET.Extensions.Slang.csproj | 2 +- .../Zenith.NET.Views.Avalonia.csproj | 2 +- .../Zenith.NET.Views.Maui.csproj | 2 +- .../Zenith.NET.Views.WPF.csproj | 2 +- .../Zenith.NET.Views.WinForms.csproj | 2 +- .../Zenith.NET.Views.WinUI.csproj | 2 +- .../Zenith.NET.Views/Zenith.NET.Views.csproj | 2 +- .../Zenith.NET.DirectX12.csproj | 2 +- .../Zenith.NET.Metal/Zenith.NET.Metal.csproj | 2 +- .../Zenith.NET.Vulkan.csproj | 2 +- sources/Zenith.NET/Zenith.NET.csproj | 2 +- 20 files changed, 33 insertions(+), 35 deletions(-) diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 02f603da..5aff3b15 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -1,28 +1,22 @@ - - - - - - - - - - - - - + + - + - + + + + + + @@ -30,4 +24,8 @@ - + + + + + \ No newline at end of file diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index 7f7f1676..e0b75ffe 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -1,4 +1,4 @@ -# RHI Redesign — Requirements Specification +# RHI Redesign — Requirements Specification > Working spec for the redesigned public surface. Not for commit. Mirrors `rhi-redesign.zh.draft.md`. > The document is organized as nine numbered requirements; shared types live in §0. diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index c28823e1..4c252439 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -1,4 +1,4 @@ -# RHI 重设计草案 — 需求规范(中文临时版) +# RHI 重设计草案 — 需求规范(中文临时版) > 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 > 本文档以 9 条需求为骨架;公共类型与 §0 公共约定共享。 diff --git a/sources/Experiments/CornellBox/CornellBox.csproj b/sources/Experiments/CornellBox/CornellBox.csproj index 59ed9f96..65e6ad06 100644 --- a/sources/Experiments/CornellBox/CornellBox.csproj +++ b/sources/Experiments/CornellBox/CornellBox.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj b/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj index bbc61e10..1a30047f 100644 --- a/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj +++ b/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/sources/Experiments/PlatformDetection/PlatformDetection.csproj b/sources/Experiments/PlatformDetection/PlatformDetection.csproj index 0108a0a1..76eab2af 100644 --- a/sources/Experiments/PlatformDetection/PlatformDetection.csproj +++ b/sources/Experiments/PlatformDetection/PlatformDetection.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj b/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj index 2974c1c7..74c150c9 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/Zenith.NET.Extensions.ImGui.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj index 42875517..9b04ef4c 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj b/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj index 3b50cc98..5bb54b48 100644 --- a/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/Zenith.NET.Extensions.Skia.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj b/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj index 5e18602f..f7e52318 100644 --- a/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj b/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj index 129003ae..60649f30 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj +++ b/sources/Views/Zenith.NET.Views.Avalonia/Zenith.NET.Views.Avalonia.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj b/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj index 2441e7d3..bda7142e 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj +++ b/sources/Views/Zenith.NET.Views.Maui/Zenith.NET.Views.Maui.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework)-android;$(StandardTargetFramework)-ios;$(StandardTargetFramework)-maccatalyst diff --git a/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj b/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj index 123c679d..93ef8433 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj +++ b/sources/Views/Zenith.NET.Views.WPF/Zenith.NET.Views.WPF.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework)-windows diff --git a/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj b/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj index 1c8d8bf9..3a1ad8e3 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj +++ b/sources/Views/Zenith.NET.Views.WinForms/Zenith.NET.Views.WinForms.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework)-windows diff --git a/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj b/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj index c1febc14..52329452 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj +++ b/sources/Views/Zenith.NET.Views.WinUI/Zenith.NET.Views.WinUI.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj b/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj index a20d2eb4..e2df005b 100644 --- a/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj +++ b/sources/Views/Zenith.NET.Views/Zenith.NET.Views.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj b/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj index fb0495be..92a72466 100644 --- a/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj +++ b/sources/Zenith.NET.DirectX12/Zenith.NET.DirectX12.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj b/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj index 77ac94c7..ecf99eae 100644 --- a/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj +++ b/sources/Zenith.NET.Metal/Zenith.NET.Metal.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj index c3be14b0..39132514 100644 --- a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj +++ b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) diff --git a/sources/Zenith.NET/Zenith.NET.csproj b/sources/Zenith.NET/Zenith.NET.csproj index 70d50a7c..bace72ae 100644 --- a/sources/Zenith.NET/Zenith.NET.csproj +++ b/sources/Zenith.NET/Zenith.NET.csproj @@ -1,4 +1,4 @@ - + $(StandardTargetFramework) From 160e77966b13c7116d5dede2db5a41645674e6c9 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 24 Apr 2026 19:57:37 +0800 Subject: [PATCH 035/466] Refactor to attachment-based rendering, remove FrameBuffer Major API overhaul: - Remove FrameBuffer as a resource; render passes now use explicit ColorAttachment and DepthStencilAttachment records. - Swap chains and surfaces expose current color/depth textures directly. - BeginRenderPass uses attachment spans and per-attachment load/store ops. - Strongly-typed resource table bindings; remove IBindableResource. - Unify buffer/texture upload/download with BufferData/TextureData. - Add new types for subresources, data layouts, and native object access. - Update all backends (DX12, Vulkan, Metal) to match new model. - Remove obsolete types (FrameBuffer, ClearValue, BufferView, etc). - Improve validation and type safety throughout the API. --- sources/Experiments/CornellBox/App.cs | 9 +- .../Renderers/PathTracingRenderer.cs | 24 +- .../Renderers/RasterizationRenderer.cs | 48 ++- .../CornellBox/Renderers/Renderer.cs | 14 +- .../ImGuiController.cs | 4 +- .../ImGuiRenderer.cs | 85 +++-- .../Extensions.cs | 42 ++- .../Zenith.NET.Views.Avalonia/Surface.cs | 83 +---- .../Zenith.NET.Views.Avalonia/ZenithView.cs | 4 +- .../Platforms/Android/MauiZenithView.cs | 5 +- .../Platforms/MacCatalyst/MauiZenithView.cs | 5 +- .../Platforms/Windows/MauiZenithView.cs | 5 +- .../Platforms/iOS/MauiZenithView.cs | 5 +- .../Views/Zenith.NET.Views.Maui/ZenithView.cs | 4 +- .../Views/Zenith.NET.Views.WPF/ZenithView.cs | 5 +- .../Zenith.NET.Views.WinForms/ZenithView.cs | 5 +- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 112 ++---- .../ZenithView.WinUI.cs | 5 +- .../Views/Zenith.NET.Views/RenderEventArgs.cs | 4 +- .../Zenith.NET.Views/ZenithViewHelper.cs | 9 - .../DXBottomLevelAccelerationStructure.cs | 10 +- sources/Zenith.NET.DirectX12/DXBuffer.cs | 28 +- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 346 +++++++++++------- sources/Zenith.NET.DirectX12/DXFrameBuffer.cs | 165 --------- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 9 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 5 +- .../Zenith.NET.DirectX12/DXResourceTable.cs | 15 - sources/Zenith.NET.DirectX12/DXSwapChain.cs | 31 +- ...inFrameBuffer.cs => DXSwapChainTargets.cs} | 33 +- sources/Zenith.NET.DirectX12/DXTexture.cs | 125 ++----- sources/Zenith.NET.DirectX12/DXTextureView.cs | 90 +++-- sources/Zenith.NET.DirectX12/Extensions.cs | 8 - sources/Zenith.NET.Metal/Extensions.cs | 8 - .../MTLBottomLevelAccelerationStructure.cs | 10 +- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 190 ++++++---- sources/Zenith.NET.Metal/MTLCommandEncoder.cs | 26 +- sources/Zenith.NET.Metal/MTLFrameBuffer.cs | 108 ------ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 5 - sources/Zenith.NET.Metal/MTLQueryHeap.cs | 5 +- sources/Zenith.NET.Metal/MTLResourceTable.cs | 4 - sources/Zenith.NET.Metal/MTLSwapChain.cs | 38 +- ...nFrameBuffer.cs => MTLSwapChainTargets.cs} | 32 +- sources/Zenith.NET.Metal/MTLTextureView.cs | 21 +- sources/Zenith.NET.Vulkan/Extensions.cs | 8 - .../VKBottomLevelAccelerationStructure.cs | 10 +- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 285 ++++++++++----- sources/Zenith.NET.Vulkan/VKFrameBuffer.cs | 158 -------- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 5 - sources/Zenith.NET.Vulkan/VKResourceTable.cs | 15 - sources/Zenith.NET.Vulkan/VKSwapChain.cs | 27 +- ...inFrameBuffer.cs => VKSwapChainTargets.cs} | 39 +- sources/Zenith.NET.Vulkan/VKTexture.cs | 213 ++--------- sources/Zenith.NET.Vulkan/VKTextureView.cs | 39 +- sources/Zenith.NET/Buffer.cs | 29 +- sources/Zenith.NET/BufferView.cs | 8 - sources/Zenith.NET/ClearValues.cs | 34 -- sources/Zenith.NET/CommandBuffer.cs | 183 ++++----- sources/Zenith.NET/Enums/ClearFlags.cs | 15 - sources/Zenith.NET/Enums/LoadOp.cs | 10 + sources/Zenith.NET/Enums/NativeObjectType.cs | 5 + sources/Zenith.NET/Enums/StoreOp.cs | 8 + sources/Zenith.NET/FrameBuffer.cs | 18 - sources/Zenith.NET/GraphicsContext.cs | 26 +- sources/Zenith.NET/GraphicsResource.cs | 4 +- .../Interfaces/IBindableResource.cs | 3 - .../Zenith.NET/Interfaces/INativeObject.cs | 6 + sources/Zenith.NET/ResourceTable.cs | 77 +--- sources/Zenith.NET/Sampler.cs | 2 +- sources/Zenith.NET/Structs/BufferData.cs | 8 + .../{BufferViewDesc.cs => BufferRange.cs} | 2 +- sources/Zenith.NET/Structs/ClearValue.cs | 14 - sources/Zenith.NET/Structs/ColorAttachment.cs | 20 + .../Structs/DepthStencilAttachment.cs | 20 + .../Structs/{TextureExtent.cs => Extent3D.cs} | 2 +- .../Structs/FrameBufferAttachment.cs | 8 - sources/Zenith.NET/Structs/FrameBufferDesc.cs | 8 - .../Structs/{TextureOffset.cs => Offset3D.cs} | 4 +- sources/Zenith.NET/Structs/TextureData.cs | 8 + .../Zenith.NET/Structs/TextureDataLayout.cs | 10 + ...{TextureSlice.cs => TextureSubresource.cs} | 4 +- .../Structs/TextureSubresourceLayers.cs | 10 + .../Structs/TextureSubresourceRange.cs | 12 + sources/Zenith.NET/Structs/TextureViewDesc.cs | 8 +- sources/Zenith.NET/SwapChain.cs | 4 +- sources/Zenith.NET/Texture.cs | 17 +- sources/Zenith.NET/TextureView.cs | 2 +- .../TopLevelAccelerationStructure.cs | 2 +- sources/Zenith.NET/ValidationLayer.cs | 162 ++++---- sources/Zenith.NET/ZenithHelper.cs | 74 ++-- 89 files changed, 1415 insertions(+), 2000 deletions(-) delete mode 100644 sources/Zenith.NET.DirectX12/DXFrameBuffer.cs rename sources/Zenith.NET.DirectX12/{DXSwapChainFrameBuffer.cs => DXSwapChainTargets.cs} (61%) delete mode 100644 sources/Zenith.NET.Metal/MTLFrameBuffer.cs rename sources/Zenith.NET.Metal/{MTLSwapChainFrameBuffer.cs => MTLSwapChainTargets.cs} (60%) delete mode 100644 sources/Zenith.NET.Vulkan/VKFrameBuffer.cs rename sources/Zenith.NET.Vulkan/{VKSwapChainFrameBuffer.cs => VKSwapChainTargets.cs} (59%) delete mode 100644 sources/Zenith.NET/BufferView.cs delete mode 100644 sources/Zenith.NET/ClearValues.cs delete mode 100644 sources/Zenith.NET/Enums/ClearFlags.cs create mode 100644 sources/Zenith.NET/Enums/LoadOp.cs create mode 100644 sources/Zenith.NET/Enums/NativeObjectType.cs create mode 100644 sources/Zenith.NET/Enums/StoreOp.cs delete mode 100644 sources/Zenith.NET/FrameBuffer.cs delete mode 100644 sources/Zenith.NET/Interfaces/IBindableResource.cs create mode 100644 sources/Zenith.NET/Interfaces/INativeObject.cs create mode 100644 sources/Zenith.NET/Structs/BufferData.cs rename sources/Zenith.NET/Structs/{BufferViewDesc.cs => BufferRange.cs} (80%) delete mode 100644 sources/Zenith.NET/Structs/ClearValue.cs create mode 100644 sources/Zenith.NET/Structs/ColorAttachment.cs create mode 100644 sources/Zenith.NET/Structs/DepthStencilAttachment.cs rename sources/Zenith.NET/Structs/{TextureExtent.cs => Extent3D.cs} (74%) delete mode 100644 sources/Zenith.NET/Structs/FrameBufferAttachment.cs delete mode 100644 sources/Zenith.NET/Structs/FrameBufferDesc.cs rename sources/Zenith.NET/Structs/{TextureOffset.cs => Offset3D.cs} (70%) create mode 100644 sources/Zenith.NET/Structs/TextureData.cs create mode 100644 sources/Zenith.NET/Structs/TextureDataLayout.cs rename sources/Zenith.NET/Structs/{TextureSlice.cs => TextureSubresource.cs} (59%) create mode 100644 sources/Zenith.NET/Structs/TextureSubresourceLayers.cs create mode 100644 sources/Zenith.NET/Structs/TextureSubresourceRange.cs diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 8646e411..93861793 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -73,7 +73,7 @@ static App() } swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorTargetFormat = PixelFormat.B8G8R8A8UNorm, DepthStencilTargetFormat = PixelFormat.D32FloatS8UInt }); - imGui = new(input, swapChain.FrameBuffer.Output); + imGui = new(input, swapChain.Output); camera = new(input, Matrix4x4.CreateTranslation(278f, 273f, -800f)) { Speed = 240.0f, @@ -176,7 +176,12 @@ public static void Run() activeRenderer.Render(commandBuffer); - imGui.Render(commandBuffer, swapChain.FrameBuffer, ClearValues.Default); + imGui.Render(commandBuffer, new() + { + Texture = swapChain.CurrentColorTarget, + LoadOp = LoadOp.Load, + StoreOp = StoreOp.Store + }); commandBuffer.Submit(true); diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index e454a2c3..1b17adea 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -47,7 +47,11 @@ public PathTracingRenderer() StrideInBytes = (uint)sizeof(Vertex), Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure }); - vertexBuffer.Upload(vertices, 0); + + fixed (Vertex* pointer = vertices) + { + vertexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) }); + } indexBuffer = App.Context.CreateBuffer(new() { @@ -55,7 +59,11 @@ public PathTracingRenderer() StrideInBytes = sizeof(uint), Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure }); - indexBuffer.Upload(indices, 0); + + fixed (uint* pointer = indices) + { + indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); + } materialBuffer = App.Context.CreateBuffer(new() { @@ -63,7 +71,11 @@ public PathTracingRenderer() StrideInBytes = (uint)sizeof(Material), Flags = BufferUsageFlags.ShaderResource }); - materialBuffer.Upload(materials, 0); + + fixed (Material* pointer = materials) + { + materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); + } cameraBuffer = App.Context.CreateBuffer(new() { @@ -146,7 +158,7 @@ public override void Update(CameraHandler camera) Matrix4x4.Invert(view, out Matrix4x4 invView); Matrix4x4.Invert(projection, out Matrix4x4 invProjection); - cameraBuffer.Upload([new() + CameraParams parameters = new() { InvView = invView, InvProjection = invProjection, @@ -154,7 +166,9 @@ public override void Update(CameraHandler camera) FrameCount = FrameCount, Width = App.Width, Height = App.Height - }], 0); + }; + + cameraBuffer.Upload(0, new() { Pointer = (nint)(¶meters), SizeInBytes = (uint)sizeof(CameraParams) }); } public override void Render(CommandBuffer commandBuffer) diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index eeff94c9..bffcca6e 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -36,7 +36,11 @@ public RasterizationRenderer() StrideInBytes = (uint)sizeof(Vertex), Flags = BufferUsageFlags.Vertex }); - vertexBuffer.Upload(vertices, 0); + + fixed (Vertex* pointer = vertices) + { + vertexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) }); + } indexBuffer = App.Context.CreateBuffer(new() { @@ -44,7 +48,11 @@ public RasterizationRenderer() StrideInBytes = sizeof(uint), Flags = BufferUsageFlags.Index }); - indexBuffer.Upload(indices, 0); + + fixed (uint* pointer = indices) + { + indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); + } materialBuffer = App.Context.CreateBuffer(new() { @@ -52,7 +60,11 @@ public RasterizationRenderer() StrideInBytes = (uint)sizeof(Material), Flags = BufferUsageFlags.ShaderResource }); - materialBuffer.Upload(materials, 0); + + fixed (Material* pointer = materials) + { + materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); + } constantBuffer = App.Context.CreateBuffer(new() { @@ -85,13 +97,13 @@ public RasterizationRenderer() ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = FrameBuffer.Output + Output = RenderOutput }); } public override void Update(CameraHandler camera) { - constantBuffer.Upload([new() + RasterConstants constants = new() { Model = Matrix4x4.Identity, View = camera.View, @@ -99,18 +111,30 @@ public override void Update(CameraHandler camera) LightPos = new(278.0f, 547.0f, 280.0f), LightColor = new(2.0f, 1.8f, 1.4f), CameraPos = camera.Position - }], 0); + }; + + constantBuffer.Upload(0, new() { Pointer = (nint)(&constants), SizeInBytes = (uint)sizeof(RasterConstants) }); } public override void Render(CommandBuffer commandBuffer) { - commandBuffer.BeginRenderPass(FrameBuffer, new() + commandBuffer.BeginRenderPass([new() { - ColorValues = [new(0.51f, 0.518f, 0.557f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }, resourceTable); + Texture = Color, + LoadOp = LoadOp.Clear, + StoreOp = StoreOp.Store, + ClearColor = new(0.51f, 0.518f, 0.557f, 1.0f) + }], + new() + { + Texture = DepthStencil, + DepthLoadOp = LoadOp.Clear, + DepthStoreOp = StoreOp.Store, + StencilLoadOp = LoadOp.Clear, + StencilStoreOp = StoreOp.Store, + ClearDepth = 1.0f, + ClearStencil = 0 + }); commandBuffer.SetPipeline(pipeline); commandBuffer.PushResourceTable(resourceTable); diff --git a/sources/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Experiments/CornellBox/Renderers/Renderer.cs index 4dd9bd99..9d6fd975 100644 --- a/sources/Experiments/CornellBox/Renderers/Renderer.cs +++ b/sources/Experiments/CornellBox/Renderers/Renderer.cs @@ -14,7 +14,12 @@ protected Renderer() public Texture DepthStencil { get; private set; } = null!; - public FrameBuffer FrameBuffer { get; private set; } = null!; + public Output RenderOutput => new() + { + ColorAttachments = [Color.Desc.Format], + DepthStencilAttachment = DepthStencil.Desc.Format, + SampleCount = Color.Desc.SampleCount + }; public abstract void Update(CameraHandler camera); @@ -22,7 +27,6 @@ protected Renderer() public virtual void Resize(uint width, uint height) { - FrameBuffer?.Dispose(); DepthStencil?.Dispose(); Color?.Dispose(); @@ -52,16 +56,10 @@ public virtual void Resize(uint width, uint height) Flags = TextureUsageFlags.DepthStencil }); - FrameBuffer = App.Context.CreateFrameBuffer(new() - { - ColorAttachments = [new() { Target = Color }], - DepthStencilAttachment = new() { Target = DepthStencil } - }); } public virtual void Dispose() { - FrameBuffer.Dispose(); DepthStencil.Dispose(); Color.Dispose(); } diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs index 455a570f..806afe66 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs @@ -140,7 +140,7 @@ public void Update(double delta, uint width, uint height) frameBegun = true; } - public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearValue clearValue) + public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment) { HexaImGui.SetCurrentContext(Context); @@ -148,7 +148,7 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa { HexaImGui.Render(); - renderer.Render(commandBuffer, frameBuffer, clearValue, HexaImGui.GetDrawData()); + renderer.Render(commandBuffer, colorAttachment, HexaImGui.GetDrawData()); frameBegun = false; } diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 677d0e72..37d35a6f 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -233,7 +233,7 @@ public ImTextureID Binding(TextureView textureView) return textureID; } - public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearValue clearValue, ImDrawDataPtr drawData) + public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, ImDrawDataPtr drawData) { if (drawData.CmdListsCount is 0) { @@ -263,19 +263,37 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa Flags = TextureUsageFlags.ShaderResource }); - TextureExtent extent = new() { Width = (uint)textureData.Width, Height = (uint)textureData.Height, Depth = 1 }; + Extent3D extent = new() { Width = (uint)textureData.Width, Height = (uint)textureData.Height, Depth = 1 }; if (textureData.Format is ImTextureFormat.Rgba32) { - ReadOnlySpan pixels = new(textureData.Pixels, textureData.Width * textureData.Height); + TextureData data = new() + { + Pointer = (nint)textureData.Pixels, + Layout = new() + { + SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), + RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), + SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + } + }; - commandBuffer.Upload(texture, default, default, extent, pixels); + commandBuffer.Upload(texture, default, default, extent, data); } else { - ReadOnlySpan pixels = new(textureData.Pixels, textureData.Width * textureData.Height); + TextureData data = new() + { + Pointer = (nint)textureData.Pixels, + Layout = new() + { + SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), + RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), + SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + } + }; - commandBuffer.Upload(texture, default, default, extent, pixels); + commandBuffer.Upload(texture, default, default, extent, data); } textureData.SetTexID(Binding(texture)); @@ -293,32 +311,54 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa { ImTextureRect rect = textureData.Updates[j]; - TextureOffset offset = new() { X = rect.X, Y = rect.Y, Z = 0 }; - TextureExtent extent = new() { Width = rect.W, Height = rect.H, Depth = 1 }; + Offset3D offset = new() { X = rect.X, Y = rect.Y, Z = 0 }; + Extent3D extent = new() { Width = rect.W, Height = rect.H, Depth = 1 }; using ZenithMarshal.Scope scope = new(); if (textureData.Format is ImTextureFormat.Rgba32) { - Span pixels = new((int*)ZenithMarshal.Allocate(scope, (uint)(rect.W * rect.H)), rect.W * rect.H); + int* pointer = (int*)ZenithMarshal.Allocate(scope, (uint)(rect.W * rect.H)); for (ushort k = 0; k < rect.H; k++) { - new ReadOnlySpan(textureData.GetPixelsAt(rect.X, rect.Y + k), rect.W).CopyTo(pixels.Slice(k * rect.W, rect.W)); + new ReadOnlySpan(textureData.GetPixelsAt(rect.X, rect.Y + k), rect.W).CopyTo(new(pointer + (k * rect.W), rect.W)); } - commandBuffer.Upload(texture, default, offset, extent, pixels); + TextureData data = new() + { + Pointer = (nint)pointer, + Layout = new() + { + SizeInBytes = (uint)(sizeof(int) * rect.W * rect.H), + RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), + SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + } + }; + + commandBuffer.Upload(texture, default, offset, extent, data); } else { - Span pixels = new((byte*)ZenithMarshal.Allocate(scope, (uint)(rect.W * rect.H)), rect.W * rect.H); + byte* pointer = (byte*)ZenithMarshal.Allocate(scope, (uint)(rect.W * rect.H)); for (ushort k = 0; k < rect.H; k++) { - new ReadOnlySpan(textureData.GetPixelsAt(rect.X, rect.Y + k), rect.W).CopyTo(pixels.Slice(k * rect.W, rect.W)); + new ReadOnlySpan(textureData.GetPixelsAt(rect.X, rect.Y + k), rect.W).CopyTo(new(pointer + (k * rect.W), rect.W)); } - commandBuffer.Upload(texture, default, offset, extent, pixels); + TextureData data = new() + { + Pointer = (nint)pointer, + Layout = new() + { + SizeInBytes = (uint)(rect.W * rect.H), + RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), + SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + } + }; + + commandBuffer.Upload(texture, default, offset, extent, data); } } } @@ -372,17 +412,14 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa { ImDrawListPtr drawListPtr = drawData.CmdLists[i]; - ReadOnlySpan verts = new(drawListPtr.VtxBuffer.Data, drawListPtr.VtxBuffer.Size); - ReadOnlySpan indices = new(drawListPtr.IdxBuffer.Data, drawListPtr.IdxBuffer.Size); - - vertexBuffer.Upload(verts, (uint)(sizeof(ImDrawVert) * vertexOffset)); - indexBuffer.Upload(indices, (uint)(sizeof(ushort) * indexOffset)); + vertexBuffer.Upload((uint)(sizeof(ImDrawVert) * vertexOffset), new() { Pointer = (nint)drawListPtr.VtxBuffer.Data, SizeInBytes = (uint)(sizeof(ImDrawVert) * drawListPtr.VtxBuffer.Size) }); + indexBuffer.Upload((uint)(sizeof(ushort) * indexOffset), new() { Pointer = (nint)drawListPtr.IdxBuffer.Data, SizeInBytes = (uint)(sizeof(ushort) * drawListPtr.IdxBuffer.Size) }); vertexOffset += drawListPtr.VtxBuffer.Size; indexOffset += drawListPtr.IdxBuffer.Size; } - constants.Upload([new Constants + Constants constantsData = new() { Projection = Matrix4x4.CreateOrthographicOffCenter(drawData.DisplayPos.X, drawData.DisplayPos.X + drawData.DisplaySize.X, @@ -390,11 +427,15 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa drawData.DisplayPos.Y, 0.0f, 1.0f) - }], 0); + }; + + constants.Upload(0, new() { Pointer = (nint)(&constantsData), SizeInBytes = (uint)sizeof(Constants) }); commandBuffer.BeginDebugEvent("ImGui"); - commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTableBindings.Values); + ColorAttachment renderTarget = colorAttachment; + + commandBuffer.BeginRenderPass([colorAttachment], null); commandBuffer.SetPipeline(graphicsPipeline); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index bfb4c7fe..37e9eec9 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -32,7 +32,26 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) CommandBuffer commandBuffer = context.Copy.CommandBuffer(); - commandBuffer.Upload(texture, default, default, new() { Width = (uint)image.Width, Height = (uint)image.Height, Depth = 1 }, pixels); + unsafe + { + fixed (Rgba32* pPixels = pixels) + { + Extent3D extent = new() { Width = (uint)image.Width, Height = (uint)image.Height, Depth = 1 }; + + TextureData data = new() + { + Pointer = (nint)pPixels, + Layout = new() + { + SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), + RowPitchInBytes = ZenithHelper.RowPitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), + SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) + } + }; + + commandBuffer.Upload(texture, default, default, extent, data); + } + } for (uint i = 1; i < mipLevels; i++) { @@ -43,7 +62,26 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) pixels = new Rgba32[mipWidth * mipHeight]; mipImage.CopyPixelDataTo(pixels); - commandBuffer.Upload(texture, new() { MipLevel = i }, default, new() { Width = mipWidth, Height = mipHeight, Depth = 1 }, pixels); + unsafe + { + fixed (Rgba32* pPixels = pixels) + { + Extent3D extent = new() { Width = mipWidth, Height = mipHeight, Depth = 1 }; + + TextureData data = new() + { + Pointer = (nint)pPixels, + Layout = new() + { + SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), + RowPitchInBytes = ZenithHelper.RowPitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), + SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) + } + }; + + commandBuffer.Upload(texture, new() { MipLevel = i }, default, extent, data); + } + } } commandBuffer.Submit(true); diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 78cb390a..ac49fd29 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -1,19 +1,14 @@ -using System.Runtime.CompilerServices; -using Avalonia.Media.Imaging; +using Avalonia.Media.Imaging; using Avalonia.Platform; using AvaloniaPixelFormat = Avalonia.Platform.PixelFormat; namespace Zenith.NET.Views.Avalonia; -internal unsafe class Surface : DisposableObject +internal class Surface : DisposableObject { - private readonly Texture color; - private readonly Texture depthStencil; - private readonly Buffer pixels; - public Surface(GraphicsContext graphicsContext, uint width, uint height) { - color = graphicsContext.CreateTexture(new() + Target = graphicsContext.CreateTexture(new() { Type = TextureType.Texture2D, Format = ZenithViewHelper.ColorFormat, @@ -26,42 +21,16 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) Flags = TextureUsageFlags.RenderTarget }); - depthStencil = graphicsContext.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.DepthStencilFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil - }); - - pixels = graphicsContext.CreateBuffer(new() - { - SizeInBytes = ZenithHelper.Align(width * 4, GraphicsContext.TextureRowPitchAlignment) * height, - StrideInBytes = 4, - Flags = BufferUsageFlags.MapRead - }); - - FrameBuffer = graphicsContext.CreateFrameBuffer(new() - { - ColorAttachments = [new() { Target = color }], - DepthStencilAttachment = new() { Target = depthStencil } - }); - - WriteableBitmap = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); + Bitmap = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); GraphicsContext = graphicsContext; Width = width; Height = height; } - public FrameBuffer FrameBuffer { get; } + public Texture Target { get; } - public WriteableBitmap WriteableBitmap { get; } + public WriteableBitmap Bitmap { get; } public GraphicsContext GraphicsContext { get; } @@ -71,42 +40,24 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) public void Present() { - CommandBuffer commandBuffer = GraphicsContext.Copy.CommandBuffer(); - commandBuffer.CopyTextureToBuffer(color, default, default, new() { Width = Width, Height = Height, Depth = 1 }, pixels, 0); - commandBuffer.Submit(true); - - uint rowPitchInBytes = ZenithHelper.Align(Width * 4, GraphicsContext.TextureRowPitchAlignment); - - using ILockedFramebuffer lockedFramebuffer = WriteableBitmap.Lock(); + using ILockedFramebuffer lockedFramebuffer = Bitmap.Lock(); - MappedMemory mappedMemory = pixels.Map(); - - if (lockedFramebuffer.RowBytes == rowPitchInBytes) - { - Unsafe.CopyBlock((void*)lockedFramebuffer.Address, (void*)mappedMemory.Pointer, mappedMemory.SizeInBytes); - } - else + Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() { - Parallel.For(0, Height, y => + Pointer = lockedFramebuffer.Address, + Layout = new() { - byte* srcPtr = (byte*)mappedMemory.Pointer + (rowPitchInBytes * y); - byte* dstPtr = (byte*)lockedFramebuffer.Address + (lockedFramebuffer.RowBytes * y); - - Unsafe.CopyBlock(dstPtr, srcPtr, (uint)lockedFramebuffer.RowBytes); - }); - } - - pixels.Unmap(); + SizeInBytes = (uint)(lockedFramebuffer.RowBytes * Height), + RowPitchInBytes = (uint)lockedFramebuffer.RowBytes, + SlicePitchInBytes = (uint)(lockedFramebuffer.RowBytes * Height) + } + }); } protected override void Destroy() { - WriteableBitmap.Dispose(); - FrameBuffer.Dispose(); - - pixels.Dispose(); - depthStencil.Dispose(); - color.Dispose(); + Bitmap.Dispose(); + Target.Dispose(); } private static AvaloniaPixelFormat ColorFormat() diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index 111fdb3f..7fc4ab17 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs @@ -37,7 +37,7 @@ public override void Render(DrawingContext context) { if (surface is not null) { - context.DrawImage(surface.WriteableBitmap, new(0, 0, Bounds.Width, Bounds.Height)); + context.DrawImage(surface.Bitmap, new(0, 0, Bounds.Width, Bounds.Height)); } if (Design.IsDesignMode) @@ -110,7 +110,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.FrameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Target)); } void IZenithView.Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs index f42077e5..ea16f5b6 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs @@ -26,8 +26,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Android(ANativeWindowFromSurface(JniEnvironment.EnvironmentPointer, Holder!.Surface!.Handle), width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -44,7 +43,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs index 76267c13..79567caa 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs @@ -27,8 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -45,7 +44,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs index 5242cf0e..0d44aa89 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs @@ -27,8 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); this.As().SetSwapChain(texture.SwapChain); @@ -45,7 +44,7 @@ public void Tick() texture.AcquireSync(); handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs index 770ce036..1dbd74f2 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs @@ -27,8 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -45,7 +44,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs index 0c554666..8e9aa35c 100644 --- a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs @@ -86,9 +86,9 @@ internal void OnUpdateRequested() UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); } - internal void OnRenderRequested(FrameBuffer frameBuffer) + internal void OnRenderRequested(Texture target) { - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, frameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, target)); } void IZenithView.UI(Action action) diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index 29b914e4..3ecfe8f5 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -111,8 +111,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); } } @@ -127,7 +126,7 @@ void IZenithView.Tick() texture.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.FrameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentColorTarget)); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs index c92671d4..11b6de49 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs @@ -81,8 +81,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.Win32(Handle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -99,7 +98,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.FrameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentColorTarget)); } void IZenithView.Present() diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index 5706b434..79742d16 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -23,7 +23,7 @@ void IZenithView.EnsureResources() { ((IZenithView)this).ReleaseResources(); - Background = new ImageBrush() { ImageSource = (surface = new(GraphicsContext, width, height)).WriteableBitmap }; + Background = new ImageBrush() { ImageSource = (surface = new(GraphicsContext, width, height)).Bitmap }; } } @@ -35,7 +35,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.FrameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Target)); } void IZenithView.Present() @@ -52,13 +52,13 @@ void IZenithView.ReleaseResources() internal unsafe class Surface : DisposableObject { - private readonly Texture color; - private readonly Texture depthStencil; - private readonly Buffer pixels; + private readonly byte[] pixels; public Surface(GraphicsContext context, uint width, uint height) { - color = context.CreateTexture(new() + pixels = new byte[width * height * 4]; + + Target = context.CreateTexture(new() { Type = TextureType.Texture2D, Format = ZenithViewHelper.ColorFormat, @@ -71,42 +71,16 @@ public Surface(GraphicsContext context, uint width, uint height) Flags = TextureUsageFlags.RenderTarget }); - depthStencil = context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.DepthStencilFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil - }); - - pixels = context.CreateBuffer(new() - { - SizeInBytes = ZenithHelper.Align(width * 4, GraphicsContext.TextureRowPitchAlignment) * height, - StrideInBytes = 4, - Flags = BufferUsageFlags.MapRead - }); - - FrameBuffer = context.CreateFrameBuffer(new() - { - ColorAttachments = [new() { Target = color }], - DepthStencilAttachment = new() { Target = depthStencil } - }); - - WriteableBitmap = new((int)width, (int)height); + Bitmap = new((int)width, (int)height); Context = context; Width = width; Height = height; } - public FrameBuffer FrameBuffer { get; } + public Texture Target { get; } - public WriteableBitmap WriteableBitmap { get; } + public WriteableBitmap Bitmap { get; } public GraphicsContext Context { get; } @@ -116,62 +90,40 @@ public Surface(GraphicsContext context, uint width, uint height) public void Present() { - CommandBuffer commandBuffer = Context.Graphics.CommandBuffer(); - commandBuffer.CopyTextureToBuffer(color, default, default, new() { Width = Width, Height = Height, Depth = 1 }, pixels, 0); - commandBuffer.Submit(true); - - uint rowPitchInBytes = ZenithHelper.Align(Width * 4, GraphicsContext.TextureRowPitchAlignment); + uint rowPitchInBytes = Width * 4; - using (Stream stream = WriteableBitmap.PixelBuffer.AsStream()) + fixed (byte* pPixels = pixels) { - MappedMemory mappedMemory = pixels.Map(); - - byte* pointer = (byte*)mappedMemory.Pointer; + Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() + { + Pointer = (nint)pPixels, + Layout = new() + { + SizeInBytes = (uint)pixels.Length, + RowPitchInBytes = rowPitchInBytes, + SlicePitchInBytes = (uint)pixels.Length + } + }); + } - switch (ZenithViewHelper.ColorFormat) + if (ZenithViewHelper.ColorFormat is PixelFormat.R8G8B8A8UNorm) + { + for (int i = 0; i < pixels.Length; i += 4) { - case PixelFormat.R8G8B8A8UNorm: - for (uint y = 0; y < Height; y++) - { - for (uint x = 0; x < Width; x++) - { - stream.WriteByte(pointer[(x * 4) + 2]); - stream.WriteByte(pointer[(x * 4) + 1]); - stream.WriteByte(pointer[(x * 4) + 0]); - stream.WriteByte(pointer[(x * 4) + 3]); - } - - pointer += rowPitchInBytes; - } - break; - - case PixelFormat.B8G8R8A8UNorm: - for (uint y = 0; y < Height; y++) - { - stream.Write([.. new ReadOnlySpan(pointer, (int)(Width * 4))]); - - pointer += rowPitchInBytes; - } - break; - - default: - throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported."); + (pixels[i], pixels[i + 2]) = (pixels[i + 2], pixels[i]); } - - pixels.Unmap(); } - WriteableBitmap.Invalidate(); + using Stream stream = Bitmap.PixelBuffer.AsStream(); + stream.Write(pixels); + + Bitmap.Invalidate(); } protected override void Destroy() { - WriteableBitmap.Dispose(); - FrameBuffer.Dispose(); - - pixels.Dispose(); - depthStencil.Dispose(); - color.Dispose(); + Bitmap.Dispose(); + Target.Dispose(); } } #endif \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 043804dc..2daa9f8b 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -33,8 +33,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat + ColorTargetFormat = ZenithViewHelper.ColorFormat }); this.As().SetSwapChain(texture.SwapChain); @@ -51,7 +50,7 @@ void IZenithView.Tick() texture.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.FrameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentColorTarget)); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views/RenderEventArgs.cs b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs index 960b3174..d7b7fffc 100644 --- a/sources/Views/Zenith.NET.Views/RenderEventArgs.cs +++ b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs @@ -1,10 +1,10 @@ namespace Zenith.NET.Views; -public class RenderEventArgs(double deltaSeconds, double totalSeconds, FrameBuffer frameBuffer) : EventArgs +public class RenderEventArgs(double deltaSeconds, double totalSeconds, Texture target) : EventArgs { public double DeltaSeconds { get; } = deltaSeconds; public double TotalSeconds { get; } = totalSeconds; - public FrameBuffer FrameBuffer { get; } = frameBuffer; + public Texture Target { get; } = target; } diff --git a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs index 0ba58af1..35a14cf1 100644 --- a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs +++ b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs @@ -3,13 +3,4 @@ public static class ZenithViewHelper { public static PixelFormat ColorFormat { get; } = OperatingSystem.IsAndroid() ? PixelFormat.R8G8B8A8UNorm : PixelFormat.B8G8R8A8UNorm; - - public static PixelFormat DepthStencilFormat { get; } = PixelFormat.D32FloatS8UInt; - - public static Output Output { get; } = new() - { - ColorAttachments = [ColorFormat], - DepthStencilAttachment = DepthStencilFormat, - SampleCount = SampleCount.Count1 - }; } diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index cdedcc74..eab0f893 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -21,7 +21,15 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, BottomLevel MappedMemory mappedMemory = TransformBuffer.Map(); - desc.Geometries.Select(static item => DXFormats.DirectX12(item.Triangles.Transform)).ToArray().CopyTo(new Span>((Matrix3X4*)mappedMemory.Pointer, (int)geometryCount)); + Matrix3X4[] transforms = desc.Geometries.Select(static item => DXFormats.DirectX12(item.Triangles.Transform)).ToArray(); + + if (geometryCount > 0) + { + unsafe + { + transforms.AsSpan().CopyTo(new Span>((void*)mappedMemory.Pointer, (int)geometryCount)); + } + } TransformBuffer.Unmap(); diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 5ed70956..a6956a61 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -25,7 +25,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc Heap = new(context, resourceDesc, DXFormats.DirectX12(desc.Flags).Type, HeapFlags.AllowOnlyBuffers); - context.Device.CreatePlacedResource(Heap.Heap, 0, &resourceDesc, States = DXFormats.DirectX12(desc.Flags).States, null, out Resource).Success(); + context.Device.CreatePlacedResource(Heap.Heap, 0, &resourceDesc, DXFormats.DirectX12(desc.Flags).States, null, out Resource).Success(); GPUVirtualAddress = Resource.GetGPUVirtualAddress(); @@ -42,8 +42,6 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc public DXBufferView View { get; } - public ResourceStates States { get; set; } - public override MappedMemory Map() { void* pointer; @@ -57,30 +55,6 @@ public override void Unmap() Resource.Unmap(0, (DxRange*)null); } - public void TransitionStates(DXCommandBuffer commandBuffer, ResourceStates newStates) - { - if (Desc.Flags.HasFlag(BufferUsageFlags.MapRead) || Desc.Flags.HasFlag(BufferUsageFlags.MapWrite) || !commandBuffer.CanTransitionResourceStates || States == newStates) - { - return; - } - - ResourceBarrier barrier = new() - { - Type = ResourceBarrierType.Transition, - Transition = new() - { - PResource = Resource, - Subresource = 0, - StateBefore = States, - StateAfter = newStates - } - }; - - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); - - States = newStates; - } - protected override void SetResourceName(string name) { Resource.SetName(name).Success(); diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 0716c6c2..f54522c9 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -7,8 +7,13 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXCommandBuffer : CommandBuffer { + private const uint TextureDataPitchAlignment = 256; + + private const uint TextureDataPlacementAlignment = 512; + private readonly DXDescriptorTable? cbvSrvUavTable; private readonly DXDescriptorTable? samplerTable; + private DXDescriptorToken[] currentRenderPassTokens = []; public ComPtr CommandAllocator; @@ -36,42 +41,26 @@ public DXCommandBuffer(DXGraphicsContext context, DXCommandQueue queue) : base(c { GraphicsCommandList6 = graphicsCommandList6; } - - CanTransitionResourceStates = queue.Type is not CommandQueueType.Copy; } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - public bool CanTransitionResourceStates { get; } - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) { - DXBuffer dxSrc = src.DirectX12(); - DXBuffer dxDest = dest.DirectX12(); - - ResourceStates srcOldStates = dxSrc.States; - ResourceStates destOldStates = dxDest.States; - - dxSrc.TransitionStates(this, ResourceStates.CopySource); - dxDest.TransitionStates(this, ResourceStates.CopyDest); - - GraphicsCommandList4.CopyBufferRegion(dxDest.Resource, destOffsetInBytes, dxSrc.Resource, srcOffsetInBytes, sizeInBytes); - - dxSrc.TransitionStates(this, srcOldStates); - dxDest.TransitionStates(this, destOldStates); + GraphicsCommandList4.CopyBufferRegion(dest.DirectX12().Resource, destOffsetInBytes, src.DirectX12().Resource, srcOffsetInBytes, sizeInBytes); } - protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + protected override void CopyBufferToTextureImpl(Buffer src, + uint srcOffsetInBytes, + TextureDataLayout srcLayout, + Texture dest, + TextureSubresource destSubresource, + Offset3D destOffset, + Extent3D destExtent) { DXBuffer dxSrc = src.DirectX12(); DXTexture dxDest = dest.DirectX12(); - ResourceStates srcOldStates = dxSrc.States; - ResourceStates destOldStates = dxDest.States[ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice)]; - - dxSrc.TransitionStates(this, ResourceStates.CopySource); - dxDest.TransitionStates(this, destSlice, ResourceStates.CopyDest); - (uint blockWidth, uint blockHeight, uint blocksWide, _) = ZenithHelper.BlockLayout(dxDest.Desc.Format, destExtent.Width, destExtent.Height); uint offsetX = ZenithHelper.Align(destOffset.X, blockWidth); @@ -79,55 +68,55 @@ protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInByte uint extentWidth = ZenithHelper.Align(destExtent.Width, blockWidth); uint extentHeight = ZenithHelper.Align(destExtent.Height, blockHeight); - TextureCopyLocation srcLocation = new() + for (uint i = 0; i < destExtent.Depth; i++) { - PResource = dxSrc.Resource, - Type = TextureCopyType.PlacedFootprint, - PlacedFootprint = new() + TextureCopyLocation srcLocation = new() { - Offset = srcOffsetInBytes, - Footprint = new() + PResource = dxSrc.Resource, + Type = TextureCopyType.PlacedFootprint, + PlacedFootprint = new() { - Format = DXFormats.DirectX12(dxDest.Desc.Format), - Width = extentWidth, - Height = extentHeight, - Depth = destExtent.Depth, - RowPitch = ZenithHelper.Align(ZenithHelper.SizeInBytes(dxDest.Desc.Format) * blocksWide, GraphicsContext.TextureRowPitchAlignment) + Offset = srcOffsetInBytes + (srcLayout.SlicePitchInBytes * i), + Footprint = new() + { + Format = DXFormats.DirectX12(dxDest.Desc.Format), + Width = extentWidth, + Height = extentHeight, + Depth = 1, + RowPitch = srcLayout.RowPitchInBytes + } } - } - }; + }; - Box srcBox = new(0, 0, 0, extentWidth, extentHeight, destExtent.Depth); + Box srcBox = new(0, 0, 0, extentWidth, extentHeight, 1); - TextureCopyLocation destLocation = new() - { - PResource = dxDest.Resource, - Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice) - }; - - GraphicsCommandList4.CopyTextureRegion(&destLocation, offsetX, offsetY, destOffset.Z, &srcLocation, &srcBox); + TextureCopyLocation destLocation = new() + { + PResource = dxDest.Resource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = dxDest.SubresourceIndex(destSubresource) + }; - dxSrc.TransitionStates(this, srcOldStates); - dxDest.TransitionStates(this, destSlice, destOldStates); + GraphicsCommandList4.CopyTextureRegion(&destLocation, offsetX, offsetY, destOffset.Z + i, &srcLocation, &srcBox); + } } - protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent) + protected override void CopyTextureImpl(Texture src, + TextureSubresource srcSubresource, + Offset3D srcOffset, + Texture dest, + TextureSubresource destSubresource, + Offset3D destOffset, + Extent3D extent) { DXTexture dxSrc = src.DirectX12(); DXTexture dxDest = dest.DirectX12(); - ResourceStates srcOldStates = dxSrc.States[ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice)]; - ResourceStates destOldStates = dxDest.States[ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice)]; - - dxSrc.TransitionStates(this, srcSlice, ResourceStates.CopySource); - dxDest.TransitionStates(this, destSlice, ResourceStates.CopyDest); - TextureCopyLocation srcLocation = new() { PResource = dxSrc.Resource, Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice) + SubresourceIndex = dxSrc.SubresourceIndex(srcSubresource) }; Box srcBox = new(srcOffset.X, srcOffset.Y, srcOffset.Z, srcOffset.X + extent.Width, srcOffset.Y + extent.Height, srcOffset.Z + extent.Depth); @@ -136,26 +125,23 @@ protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, Text { PResource = dxDest.Resource, Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice) + SubresourceIndex = dxDest.SubresourceIndex(destSubresource) }; GraphicsCommandList4.CopyTextureRegion(&destLocation, destOffset.X, destOffset.Y, destOffset.Z, &srcLocation, &srcBox); - - dxSrc.TransitionStates(this, srcSlice, srcOldStates); - dxDest.TransitionStates(this, destSlice, destOldStates); } - protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes) + protected override void CopyTextureToBufferImpl(Texture src, + TextureSubresource srcSubresource, + Offset3D srcOffset, + Extent3D srcExtent, + Buffer dest, + uint destOffsetInBytes, + TextureDataLayout destLayout) { DXTexture dxSrc = src.DirectX12(); DXBuffer dxDest = dest.DirectX12(); - ResourceStates srcOldStates = dxSrc.States[ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice)]; - ResourceStates destOldStates = dxDest.States; - - dxSrc.TransitionStates(this, srcSlice, ResourceStates.CopySource); - dxDest.TransitionStates(this, ResourceStates.CopyDest); - (uint blockWidth, uint blockHeight, uint blocksWide, _) = ZenithHelper.BlockLayout(dxSrc.Desc.Format, srcExtent.Width, srcExtent.Height); uint offsetX = ZenithHelper.Align(srcOffset.X, blockWidth); @@ -167,50 +153,61 @@ protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSli { PResource = dxSrc.Resource, Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice) + SubresourceIndex = dxSrc.SubresourceIndex(srcSubresource) }; Box srcBox = new(offsetX, offsetY, srcOffset.Z, offsetX + extentWidth, offsetY + extentHeight, srcOffset.Z + srcExtent.Depth); - TextureCopyLocation destLocation = new() + for (uint i = 0; i < srcExtent.Depth; i++) { - PResource = dxDest.Resource, - Type = TextureCopyType.PlacedFootprint, - PlacedFootprint = new() + Box sliceBox = new(offsetX, offsetY, srcOffset.Z + i, offsetX + extentWidth, offsetY + extentHeight, srcOffset.Z + i + 1); + + TextureCopyLocation destLocation = new() { - Offset = destOffsetInBytes, - Footprint = new() + PResource = dxDest.Resource, + Type = TextureCopyType.PlacedFootprint, + PlacedFootprint = new() { - Format = DXFormats.DirectX12(dxSrc.Desc.Format), - Width = extentWidth, - Height = extentHeight, - Depth = srcExtent.Depth, - RowPitch = ZenithHelper.Align(ZenithHelper.SizeInBytes(dxSrc.Desc.Format) * blocksWide, GraphicsContext.TextureRowPitchAlignment) + Offset = destOffsetInBytes + (destLayout.SlicePitchInBytes * i), + Footprint = new() + { + Format = DXFormats.DirectX12(dxSrc.Desc.Format), + Width = extentWidth, + Height = extentHeight, + Depth = 1, + RowPitch = destLayout.RowPitchInBytes + } } - } - }; - - GraphicsCommandList4.CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &srcBox); + }; - dxSrc.TransitionStates(this, srcSlice, srcOldStates); - dxDest.TransitionStates(this, destOldStates); + GraphicsCommandList4.CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &sliceBox); + } } - protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + protected override TextureDataLayout GetTextureCopyLayout(PixelFormat format, Extent3D extent) { - DXTexture dxSrc = src.DirectX12(); - DXTexture dxDest = dest.DirectX12(); + uint rowPitchInBytes = ZenithHelper.Align(ZenithHelper.RowPitchInBytes(format, extent.Width, extent.Height), TextureDataPitchAlignment); + (_, _, _, uint blocksHigh) = ZenithHelper.BlockLayout(format, extent.Width, extent.Height); - ResourceStates srcOldStates = dxSrc.States[ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice)]; - ResourceStates destOldStates = dxDest.States[ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice)]; + uint slicePitchInBytes = ZenithHelper.Align(rowPitchInBytes * blocksHigh, TextureDataPlacementAlignment); - dxSrc.TransitionStates(this, srcSlice, ResourceStates.CopySource); - dxDest.TransitionStates(this, destSlice, ResourceStates.CopyDest); + return new() + { + SizeInBytes = slicePitchInBytes * extent.Depth, + RowPitchInBytes = rowPitchInBytes, + SlicePitchInBytes = slicePitchInBytes + }; + } - GraphicsCommandList4.ResolveSubresource(dxDest.Resource, ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice), dxSrc.Resource, ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice), DXFormats.DirectX12(dxDest.Desc.Format)); + protected override void ResolveTextureImpl(Texture src, + TextureSubresource srcSubresource, + Texture dest, + TextureSubresource destSubresource) + { + DXTexture dxSrc = src.DirectX12(); + DXTexture dxDest = dest.DirectX12(); - dxSrc.TransitionStates(this, srcSlice, srcOldStates); - dxDest.TransitionStates(this, destSlice, destOldStates); + GraphicsCommandList4.ResolveSubresource(dxDest.Resource, dxDest.SubresourceIndex(destSubresource), dxSrc.Resource, dxSrc.SubresourceIndex(srcSubresource), DXFormats.DirectX12(dxDest.Desc.Format)); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) @@ -228,80 +225,155 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru accelerationStructure.DirectX12().Update(this, newDesc); } - protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue) + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment) { - DXFrameBuffer dxFrameBuffer = frameBuffer.DirectX12(); + ReleaseRenderPassTokens(); - dxFrameBuffer.PrepareAttachments(this); + using ZenithMarshal.Scope scope = new(); - bool clearColor = clearValue.Flags.HasFlag(ClearFlags.Color); - bool clearDepth = clearValue.Flags.HasFlag(ClearFlags.Depth); - bool clearStencil = clearValue.Flags.HasFlag(ClearFlags.Stencil); + uint colorAttachmentCount = (uint)colorAttachments.Length; + bool hasDepthStencilAttachment = depthStencilAttachment is not null; - for (int i = 0; i < dxFrameBuffer.ColorAttachmentCount; i++) - { - ref RenderPassRenderTargetDesc renderTarget = ref dxFrameBuffer.RenderTargets[i]; + RenderPassRenderTargetDesc* renderTargets = (RenderPassRenderTargetDesc*)ZenithMarshal.Allocate(scope, colorAttachmentCount); + RenderPassDepthStencilDesc* depthStencil = hasDepthStencilAttachment ? (RenderPassDepthStencilDesc*)ZenithMarshal.Allocate(scope, 1) : null; - renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Preserve; + currentRenderPassTokens = new DXDescriptorToken[colorAttachmentCount + (hasDepthStencilAttachment ? 1 : 0)]; - if (clearColor) - { - renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Clear; + for (uint i = 0; i < colorAttachmentCount; i++) + { + ColorAttachment attachment = colorAttachments[(int)i]; - fixed (float* colorPtr = renderTarget.BeginningAccess.Clear.ClearValue.Anonymous.Color) + renderTargets[i] = new() + { + CpuDescriptor = (currentRenderPassTokens[i] = attachment.Texture.DirectX12().CreateRtvToken(attachment.Subresource)).Handle, + BeginningAccess = new() { - clearValue.ColorValues[i].CopyTo(new Span(colorPtr, 4)); + Type = RenderPassBeginningAccessType.Preserve + }, + EndingAccess = new() + { + Type = attachment.StoreOp is StoreOp.Store ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.Discard } + }; + + ref RenderPassRenderTargetDesc renderTarget = ref renderTargets[i]; + + switch (attachment.LoadOp) + { + case LoadOp.Clear: + renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Clear; + renderTarget.BeginningAccess.Clear.ClearValue.Format = DXFormats.DirectX12(attachment.Texture.Desc.Format); + + fixed (float* colorPtr = renderTarget.BeginningAccess.Clear.ClearValue.Anonymous.Color) + { + attachment.ClearColor.CopyTo(new Span(colorPtr, 4)); + } + break; + + case LoadOp.DontCare: + renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Discard; + break; } } - if (dxFrameBuffer.HasDepthStencilAttachment) + if (hasDepthStencilAttachment) { - ref RenderPassDepthStencilDesc depthStencil = ref dxFrameBuffer.DepthStencil[0]; + DepthStencilAttachment attachment = depthStencilAttachment!.Value; + PixelFormat format = attachment.Texture.Desc.Format; + bool hasDepth = ZenithHelper.HasDepth(format); + bool hasStencil = ZenithHelper.HasStencil(format); - if (depthStencil.DepthBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) + depthStencil[0] = new() { - depthStencil.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Preserve; + CpuDescriptor = (currentRenderPassTokens[colorAttachmentCount] = attachment.Texture.DirectX12().CreateDsvToken(attachment.Subresource)).Handle, + DepthBeginningAccess = new() + { + Type = hasDepth ? RenderPassBeginningAccessType.Preserve : RenderPassBeginningAccessType.NoAccess + }, + StencilBeginningAccess = new() + { + Type = hasStencil ? RenderPassBeginningAccessType.Preserve : RenderPassBeginningAccessType.NoAccess + }, + DepthEndingAccess = new() + { + Type = hasDepth ? attachment.DepthStoreOp is StoreOp.Store ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.Discard : RenderPassEndingAccessType.NoAccess + }, + StencilEndingAccess = new() + { + Type = hasStencil ? attachment.StencilStoreOp is StoreOp.Store ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.Discard : RenderPassEndingAccessType.NoAccess + } + }; - if (clearDepth) + ref RenderPassDepthStencilDesc depthStencilDesc = ref depthStencil[0]; + + if (depthStencilDesc.DepthBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) + { + switch (attachment.DepthLoadOp) { - depthStencil.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Clear; - depthStencil.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth = clearValue.Depth; + case LoadOp.Clear: + depthStencilDesc.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Clear; + depthStencilDesc.DepthBeginningAccess.Clear.ClearValue.Format = DXFormats.DirectX12(format); + depthStencilDesc.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth = attachment.ClearDepth; + break; + + case LoadOp.DontCare: + depthStencilDesc.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Discard; + break; } } - if (depthStencil.StencilBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) + if (depthStencilDesc.StencilBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) { - depthStencil.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Preserve; - - if (clearStencil) + switch (attachment.StencilLoadOp) { - depthStencil.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Clear; - depthStencil.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = clearValue.Stencil; + case LoadOp.Clear: + depthStencilDesc.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Clear; + depthStencilDesc.StencilBeginningAccess.Clear.ClearValue.Format = DXFormats.DirectX12(format); + depthStencilDesc.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = attachment.ClearStencil; + break; + + case LoadOp.DontCare: + depthStencilDesc.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Discard; + break; } } } - GraphicsCommandList4.BeginRenderPass(dxFrameBuffer.ColorAttachmentCount, dxFrameBuffer.RenderTargets, dxFrameBuffer.DepthStencil, RenderPassFlags.None); + GraphicsCommandList4.BeginRenderPass(colorAttachmentCount, renderTargets, depthStencil, RenderPassFlags.None); } - protected override void EndRenderPassImpl(FrameBuffer frameBuffer) + protected override void EndRenderPassImpl() { GraphicsCommandList4.EndRenderPass(); - frameBuffer.DirectX12().PresentColorAttachments(this); + ReleaseRenderPassTokens(); } - protected override void SetScissorsImpl(Scissor[] scissors) + protected override void SetScissorsImpl(ReadOnlySpan scissors) { - Box2D[] dxScissors = [.. scissors.Select(static item => new Box2D(new(item.X, item.Y), new((int)(item.X + item.Width), (int)(item.Y + item.Height))))]; + Span> dxScissors = scissors.Length <= 8 ? stackalloc Box2D[scissors.Length] : new Box2D[scissors.Length]; + + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; + + dxScissors[i] = new(new(scissor.X, scissor.Y), new((int)(scissor.X + scissor.Width), (int)(scissor.Y + scissor.Height))); + } GraphicsCommandList4.RSSetScissorRects((uint)dxScissors.Length, ref dxScissors[0]); } - protected override void SetViewportsImpl(Viewport[] viewports) + protected override void SetViewportsImpl(ReadOnlySpan viewports) { - DxViewport[] dxViewports = [.. viewports.Select(static item => new DxViewport(item.X, item.Y, item.Width, item.Height, item.MinDepth, item.MaxDepth))]; + Span dxViewports = viewports.Length <= 8 ? stackalloc DxViewport[viewports.Length] : new DxViewport[viewports.Length]; + + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; + + dxViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); + } GraphicsCommandList4.RSSetViewports((uint)dxViewports.Length, ref dxViewports[0]); } @@ -512,6 +584,8 @@ protected override void EndImpl() protected override void ResetImpl() { + ReleaseRenderPassTokens(); + cbvSrvUavTable?.Reset(); samplerTable?.Reset(); @@ -526,6 +600,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + ReleaseRenderPassTokens(); + base.Destroy(); GraphicsCommandList6?.Dispose(); @@ -536,4 +612,14 @@ protected override void Destroy() samplerTable?.Dispose(); cbvSrvUavTable?.Dispose(); } + + private void ReleaseRenderPassTokens() + { + foreach (DXDescriptorToken token in currentRenderPassTokens) + { + token.Dispose(); + } + + currentRenderPassTokens = []; + } } diff --git a/sources/Zenith.NET.DirectX12/DXFrameBuffer.cs b/sources/Zenith.NET.DirectX12/DXFrameBuffer.cs deleted file mode 100644 index 7babe8dd..00000000 --- a/sources/Zenith.NET.DirectX12/DXFrameBuffer.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXFrameBuffer : FrameBuffer -{ - private readonly ZenithMarshal.Scope scope = new(); - - public RenderPassRenderTargetDesc* RenderTargets; - - public RenderPassDepthStencilDesc* DepthStencil; - - public DXFrameBuffer(DXGraphicsContext context, FrameBufferDesc desc) : base(context, desc) - { - ColorAttachmentCount = (uint)desc.ColorAttachments.Length; - HasDepthStencilAttachment = desc.DepthStencilAttachment is not null; - - RenderTargets = (RenderPassRenderTargetDesc*)ZenithMarshal.Allocate(scope, ColorAttachmentCount); - DepthStencil = HasDepthStencilAttachment ? (RenderPassDepthStencilDesc*)ZenithMarshal.Allocate(scope, 1) : null; - - Tokens = new DXDescriptorToken[ColorAttachmentCount + (HasDepthStencilAttachment ? 1 : 0)]; - - uint width = 0; - uint height = 0; - SampleCount sampleCount = SampleCount.Count1; - - for (uint i = 0; i < ColorAttachmentCount; i++) - { - FrameBufferAttachment attachment = desc.ColorAttachments[i]; - - if (i is 0) - { - ZenithHelper.MipDimensions(attachment.Target.Desc.Width, attachment.Target.Desc.Height, 0, attachment.Slice.MipLevel, out width, out height, out _); - - sampleCount = attachment.Target.Desc.SampleCount; - } - - RenderTargets[i] = new() - { - CpuDescriptor = (Tokens[i] = attachment.Target.DirectX12().CreateRtvToken(attachment.Slice)).Handle, - BeginningAccess = new() - { - Type = RenderPassBeginningAccessType.Preserve, - Clear = new() - { - ClearValue = new() - { - Format = DXFormats.DirectX12(attachment.Target.Desc.Format) - } - } - }, - EndingAccess = new() - { - Type = RenderPassEndingAccessType.Preserve - } - }; - } - - if (HasDepthStencilAttachment) - { - FrameBufferAttachment attachment = desc.DepthStencilAttachment!.Value; - - if (ColorAttachmentCount is 0) - { - ZenithHelper.MipDimensions(attachment.Target.Desc.Width, attachment.Target.Desc.Height, 0, attachment.Slice.MipLevel, out width, out height, out _); - - sampleCount = attachment.Target.Desc.SampleCount; - } - - bool hasDepth = ZenithHelper.HasDepth(attachment.Target.Desc.Format); - bool hasStencil = ZenithHelper.HasStencil(attachment.Target.Desc.Format); - - DepthStencil[0] = new() - { - CpuDescriptor = (Tokens[ColorAttachmentCount] = attachment.Target.DirectX12().CreateDsvToken(attachment.Slice)).Handle, - DepthBeginningAccess = new() - { - Type = hasDepth ? RenderPassBeginningAccessType.Preserve : RenderPassBeginningAccessType.NoAccess, - Clear = new() - { - ClearValue = new() - { - Format = DXFormats.DirectX12(attachment.Target.Desc.Format) - } - } - }, - StencilBeginningAccess = new() - { - Type = hasStencil ? RenderPassBeginningAccessType.Preserve : RenderPassBeginningAccessType.NoAccess, - Clear = new() - { - ClearValue = new() - { - Format = DXFormats.DirectX12(attachment.Target.Desc.Format) - } - } - }, - DepthEndingAccess = new() - { - Type = hasDepth ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.NoAccess - }, - StencilEndingAccess = new() - { - Type = hasStencil ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.NoAccess - } - }; - } - - Width = width; - Height = height; - Output = new() - { - ColorAttachments = [.. desc.ColorAttachments.Select(static item => item.Target.Desc.Format)], - DepthStencilAttachment = desc.DepthStencilAttachment?.Target.Desc.Format, - SampleCount = sampleCount - }; - } - - public override uint ColorAttachmentCount { get; } - - public override bool HasDepthStencilAttachment { get; } - - public override uint Width { get; } - - public override uint Height { get; } - - public override Output Output { get; } - - public DXDescriptorToken[] Tokens { get; } - - public void PrepareAttachments(DXCommandBuffer commandBuffer) - { - foreach (FrameBufferAttachment attachment in Desc.ColorAttachments) - { - attachment.Target.DirectX12().TransitionStates(commandBuffer, attachment.Slice, ResourceStates.RenderTarget); - } - - Desc.DepthStencilAttachment?.Target.DirectX12().TransitionStates(commandBuffer, Desc.DepthStencilAttachment.Value.Slice, ResourceStates.DepthWrite); - } - - public void PresentColorAttachments(DXCommandBuffer commandBuffer) - { - foreach (FrameBufferAttachment attachment in Desc.ColorAttachments) - { - if (attachment.Target.Desc.Flags.HasFlag(TextureUsageFlags.RenderTarget)) - { - attachment.Target.DirectX12().TransitionStates(commandBuffer, attachment.Slice, ResourceStates.Present); - } - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - foreach (DXDescriptorToken token in Tokens) - { - token.Dispose(); - } - - scope.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 07497505..a591de4b 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -6,6 +6,10 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(Backend.DirectX12, useValidationLayer) { + private const uint TextureDataPitchAlignment = 256; + + private const uint TextureDataPlacementAlignment = 512; + public const uint SwapChainBufferCount = 3; public const uint Shader4ComponentMapping = 0x1688; @@ -120,11 +124,6 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) return new DXSwapChain(this, desc); } - protected override FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc) - { - return new DXFrameBuffer(this, desc); - } - protected override Shader CreateShaderImpl(ShaderDesc desc) { return new DXShader(this, desc); diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index e9b391a9..c49e6bf3 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -31,7 +31,10 @@ protected override void GetResultsImpl(Span results, uint startIndex) { MappedMemory mappedMemory = Buffer.Map(); - new Span((void*)(mappedMemory.Pointer + (sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + unsafe + { + new ReadOnlySpan((void*)(mappedMemory.Pointer + (nint)(sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + } Buffer.Unmap(); } diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs index df1c0e41..d511c787 100644 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ b/sources/Zenith.NET.DirectX12/DXResourceTable.cs @@ -210,21 +210,6 @@ protected override void WriteImpl(uint binding, IBindableResource[] resources) } } - protected override void PreprocessImpl(CommandBuffer commandBuffer) - { - DXCommandBuffer dxCommandBuffer = commandBuffer.DirectX12(); - - foreach (DXTextureView? textureView in SrvTextureViews) - { - textureView?.TransitionStates(dxCommandBuffer, ResourceStates.AllShaderResource); - } - - foreach (DXTextureView? textureView in UavTextureViews) - { - textureView?.TransitionStates(dxCommandBuffer, ResourceStates.UnorderedAccess); - } - } - protected override void SetResourceName(string name) { } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 14fbca20..89a3e976 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -6,7 +6,7 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXSwapChain : SwapChain { private readonly DXFence fence; - private readonly DXSwapChainFrameBuffer swapChainFrameBuffer; + private readonly DXSwapChainTargets swapChainTargets; public ComPtr SwapChain3; @@ -15,14 +15,27 @@ internal unsafe class DXSwapChain : SwapChain public DXSwapChain(DXGraphicsContext context, SwapChainDesc desc) : base(context, desc) { fence = new(context); - swapChainFrameBuffer = new(context, this); + swapChainTargets = new(context, this); CreateSwapChain(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - public override FrameBuffer FrameBuffer => swapChainFrameBuffer[BufferIndex]; + public override uint Width => CurrentColorTarget.Desc.Width; + + public override uint Height => CurrentColorTarget.Desc.Height; + + public override Texture CurrentColorTarget => swapChainTargets[BufferIndex]; + + public override Texture? CurrentDepthStencilTarget => swapChainTargets.DepthStencilTarget; + + public override Output Output => new() + { + ColorAttachments = [Desc.ColorTargetFormat], + DepthStencilAttachment = Desc.DepthStencilTargetFormat, + SampleCount = SampleCount.Count1 + }; public override void Present() { @@ -42,7 +55,7 @@ protected override void ResizeImpl() { fence.Wait(Context.GraphicsQueue); - swapChainFrameBuffer.DestroyFrameBuffers(); + swapChainTargets.DestroyTargets(); SwapChain3.ResizeBuffers(DXGraphicsContext.SwapChainBufferCount, Desc.Surface.Width, @@ -50,7 +63,7 @@ protected override void ResizeImpl() DXFormats.DirectX12(Desc.ColorTargetFormat), (uint)SwapChainFlag.AllowTearing).Success(); - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, []); + swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, []); BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); } @@ -69,7 +82,7 @@ protected override void Destroy() { DestroySwapChain(); - swapChainFrameBuffer.Dispose(); + swapChainTargets.Dispose(); fence.Dispose(); } @@ -99,19 +112,19 @@ private void CreateSwapChain() (ComPtr)null, ref SwapChain3).Success(); - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, []); + swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, []); BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); } else { - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); + swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); } } private void DestroySwapChain() { - swapChainFrameBuffer.DestroyFrameBuffers(); + swapChainTargets.DestroyTargets(); SwapChain3.Dispose(); SwapChain3 = default; diff --git a/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs b/sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs similarity index 61% rename from sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs rename to sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs index 0757905c..cbfc3b61 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs @@ -4,17 +4,18 @@ namespace Zenith.NET.DirectX12; -internal unsafe class DXSwapChainFrameBuffer(DXGraphicsContext context, DXSwapChain swapChain) : GraphicsResource(context) +internal unsafe class DXSwapChainTargets(DXGraphicsContext context, DXSwapChain swapChain) : GraphicsResource(context) { private DXTexture? depthStencilTarget; private DXTexture[] colorTargets = []; - private DXFrameBuffer[] frameBuffers = []; public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - public DXFrameBuffer this[uint index] => frameBuffers[index]; + public DXTexture this[uint index] => colorTargets[index]; - public void CreateFrameBuffers(uint width, uint height, nint[] handles) + public DXTexture? DepthStencilTarget => depthStencilTarget; + + public void CreateTargets(uint width, uint height, nint[] handles) { if (swapChain.Desc.DepthStencilTargetFormat is not null) { @@ -50,40 +51,24 @@ public void CreateFrameBuffers(uint width, uint height, nint[] handles) using ZenithMarshal.Scope scope = new(); colorTargets = new DXTexture[DXGraphicsContext.SwapChainBufferCount]; - frameBuffers = new DXFrameBuffer[DXGraphicsContext.SwapChainBufferCount]; for (uint i = 0; i < DXGraphicsContext.SwapChainBufferCount; i++) { - frameBuffers[i] = new(context, new() - { - ColorAttachments = [new() { Target = colorTargets[i] = new(context, colorTargetDesc, swapChain.SwapChain3.GetBuffer(i)) }], - DepthStencilAttachment = depthStencilTarget is not null ? new() { Target = depthStencilTarget } : null - }); + colorTargets[i] = new(context, colorTargetDesc, swapChain.SwapChain3.GetBuffer(i)); } } else if (swapChain.Desc.Surface.Type is SurfaceType.D3D11Interop) { colorTargets = new DXTexture[1]; - frameBuffers = new DXFrameBuffer[1]; Context.Device.OpenSharedHandle((void*)handles[0], out ComPtr resource).Success(); - frameBuffers[0] = new(context, new() - { - ColorAttachments = [new() { Target = colorTargets[0] = new(context, colorTargetDesc, resource) }], - DepthStencilAttachment = depthStencilTarget is not null ? new() { Target = depthStencilTarget } : null - }); + colorTargets[0] = new(context, colorTargetDesc, resource); } } - public void DestroyFrameBuffers() + public void DestroyTargets() { - foreach (DXFrameBuffer frameBuffer in frameBuffers) - { - frameBuffer.Dispose(); - } - frameBuffers = []; - foreach (DXTexture texture in colorTargets) { texture.Dispose(); @@ -100,6 +85,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - DestroyFrameBuffers(); + DestroyTargets(); } } diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index fab413e8..119b413c 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -16,7 +16,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, de Dimension = DXFormats.DirectX12(desc.Type), Width = desc.Width, Height = desc.Height, - DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : ZenithHelper.FlattenArrayLayerCount(desc)), + DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : desc.ArrayLayers), MipLevels = (ushort)desc.MipLevels, Format = DXFormats.DirectX12(desc.Format), SampleDesc = DXFormats.DirectX12(desc.SampleCount), @@ -49,14 +49,16 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, de View = new(context, new() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = new() + { + BaseMipLevel = 0, + LevelCount = desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = desc.ArrayLayers + } }); - - States = new ResourceStates[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(States, DXFormats.DirectX12(desc.Flags).States); } public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr resource) : base(context, desc) @@ -66,14 +68,16 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr (DXGraphicsContext)base.Context; @@ -82,65 +86,12 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr (uavToken ??= CreateUavToken()).Handle; - public void TransitionStates(DXCommandBuffer commandBuffer, ResourceStates newStates) - { - Desc.Texture.DirectX12().TransitionStates(commandBuffer, - Desc.FirstMipLevel, - Desc.MipLevelCount, - Desc.FirstArrayLayer, - Desc.ArrayLayerCount, - 0, - ZenithHelper.FaceCount(Desc.Texture.Desc), - newStates); - } - protected override void SetResourceName(string name) { } @@ -37,30 +27,31 @@ protected override void Destroy() private DXDescriptorToken CreateSrvToken() { DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + TextureSubresourceRange range = Desc.Range; ShaderResourceViewDesc viewDesc = new() { - Format = Resolve(Desc.Texture.Desc.Format), + Format = Resolve(Desc.Format), Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping }; - switch (Desc.Texture.Desc.Type) + switch (Desc.Type) { case TextureType.Texture1D: { viewDesc.ViewDimension = SrvDimension.Texture1D; - viewDesc.Texture1D.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture1D.MipLevels = Desc.MipLevelCount; + viewDesc.Texture1D.MostDetailedMip = range.BaseMipLevel; + viewDesc.Texture1D.MipLevels = range.LevelCount; } break; case TextureType.Texture1DArray: { viewDesc.ViewDimension = SrvDimension.Texture1Darray; - viewDesc.Texture1DArray.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture1DArray.MipLevels = Desc.MipLevelCount; - viewDesc.Texture1DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture1DArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + viewDesc.Texture1DArray.MostDetailedMip = range.BaseMipLevel; + viewDesc.Texture1DArray.MipLevels = range.LevelCount; + viewDesc.Texture1DArray.FirstArraySlice = range.BaseArrayLayer; + viewDesc.Texture1DArray.ArraySize = range.LayerCount; } break; @@ -68,8 +59,8 @@ private DXDescriptorToken CreateSrvToken() if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = SrvDimension.Texture2D; - viewDesc.Texture2D.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture2D.MipLevels = Desc.MipLevelCount; + viewDesc.Texture2D.MostDetailedMip = range.BaseMipLevel; + viewDesc.Texture2D.MipLevels = range.LevelCount; } else { @@ -81,42 +72,42 @@ private DXDescriptorToken CreateSrvToken() if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = SrvDimension.Texture2Darray; - viewDesc.Texture2DArray.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture2DArray.MipLevels = Desc.MipLevelCount; - viewDesc.Texture2DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + viewDesc.Texture2DArray.MostDetailedMip = range.BaseMipLevel; + viewDesc.Texture2DArray.MipLevels = range.LevelCount; + viewDesc.Texture2DArray.FirstArraySlice = range.BaseArrayLayer; + viewDesc.Texture2DArray.ArraySize = range.LayerCount; } else { viewDesc.ViewDimension = SrvDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DMSArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + viewDesc.Texture2DMSArray.FirstArraySlice = range.BaseArrayLayer; + viewDesc.Texture2DMSArray.ArraySize = range.LayerCount; } break; case TextureType.Texture3D: { viewDesc.ViewDimension = SrvDimension.Texture3D; - viewDesc.Texture3D.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture3D.MipLevels = Desc.MipLevelCount; + viewDesc.Texture3D.MostDetailedMip = range.BaseMipLevel; + viewDesc.Texture3D.MipLevels = range.LevelCount; } break; case TextureType.TextureCube: { viewDesc.ViewDimension = SrvDimension.Texturecube; - viewDesc.TextureCube.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.TextureCube.MipLevels = Desc.MipLevelCount; + viewDesc.TextureCube.MostDetailedMip = range.BaseMipLevel; + viewDesc.TextureCube.MipLevels = range.LevelCount; } break; case TextureType.TextureCubeArray: { viewDesc.ViewDimension = SrvDimension.Texturecubearray; - viewDesc.TextureCubeArray.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.TextureCubeArray.MipLevels = Desc.MipLevelCount; - viewDesc.TextureCubeArray.First2DArrayFace = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.TextureCubeArray.NumCubes = Desc.ArrayLayerCount; + viewDesc.TextureCubeArray.MostDetailedMip = range.BaseMipLevel; + viewDesc.TextureCubeArray.MipLevels = range.LevelCount; + viewDesc.TextureCubeArray.First2DArrayFace = range.BaseArrayLayer; + viewDesc.TextureCubeArray.NumCubes = range.LayerCount / CubeMapFaceCount; } break; } @@ -129,24 +120,25 @@ private DXDescriptorToken CreateSrvToken() private DXDescriptorToken CreateUavToken() { DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + TextureSubresourceRange range = Desc.Range; - UnorderedAccessViewDesc viewDesc = new() { Format = Resolve(Desc.Texture.Desc.Format) }; + UnorderedAccessViewDesc viewDesc = new() { Format = Resolve(Desc.Format) }; - switch (Desc.Texture.Desc.Type) + switch (Desc.Type) { case TextureType.Texture1D: { viewDesc.ViewDimension = UavDimension.Texture1D; - viewDesc.Texture1D.MipSlice = Desc.FirstMipLevel; + viewDesc.Texture1D.MipSlice = range.BaseMipLevel; } break; case TextureType.Texture1DArray: { viewDesc.ViewDimension = UavDimension.Texture1Darray; - viewDesc.Texture1DArray.MipSlice = Desc.FirstMipLevel; - viewDesc.Texture1DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture1DArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + viewDesc.Texture1DArray.MipSlice = range.BaseMipLevel; + viewDesc.Texture1DArray.FirstArraySlice = range.BaseArrayLayer; + viewDesc.Texture1DArray.ArraySize = range.LayerCount; } break; @@ -154,7 +146,7 @@ private DXDescriptorToken CreateUavToken() if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = UavDimension.Texture2D; - viewDesc.Texture2D.MipSlice = Desc.FirstMipLevel; + viewDesc.Texture2D.MipSlice = range.BaseMipLevel; } else { @@ -168,22 +160,22 @@ private DXDescriptorToken CreateUavToken() if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = UavDimension.Texture2Darray; - viewDesc.Texture2DArray.MipSlice = Desc.FirstMipLevel; - viewDesc.Texture2DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + viewDesc.Texture2DArray.MipSlice = range.BaseMipLevel; + viewDesc.Texture2DArray.FirstArraySlice = range.BaseArrayLayer; + viewDesc.Texture2DArray.ArraySize = range.LayerCount; } else { viewDesc.ViewDimension = UavDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DMSArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + viewDesc.Texture2DMSArray.FirstArraySlice = range.BaseArrayLayer; + viewDesc.Texture2DMSArray.ArraySize = range.LayerCount; } break; case TextureType.Texture3D: { viewDesc.ViewDimension = UavDimension.Texture3D; - viewDesc.Texture3D.MipSlice = Desc.FirstMipLevel; + viewDesc.Texture3D.MipSlice = range.BaseMipLevel; viewDesc.Texture3D.WSize = Desc.Texture.Desc.Depth; } break; diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 826c954e..188c0b60 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -158,14 +158,6 @@ internal DXSwapChain DirectX12() } } - extension(FrameBuffer frameBuffer) - { - internal DXFrameBuffer DirectX12() - { - return (DXFrameBuffer)frameBuffer; - } - } - extension(Shader shader) { internal DXShader DirectX12() diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index e975c15b..f80f28d6 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -42,14 +42,6 @@ internal MTLSwapChain Metal() } } - extension(FrameBuffer frameBuffer) - { - internal MTLFrameBuffer Metal() - { - return (MTLFrameBuffer)frameBuffer; - } - } - extension(Shader shader) { internal MTLShader Metal() diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs index 03322d0f..810248e5 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -19,7 +19,15 @@ public unsafe MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, Bo MappedMemory mappedMemory = TransformBuffer.Map(); - desc.Geometries.Select(static item => MTLFormats.Metal(item.Triangles.Transform)).ToArray().CopyTo(new Span((MTLPackedFloat4x3*)mappedMemory.Pointer, (int)geometryCount)); + MTLPackedFloat4x3[] transforms = desc.Geometries.Select(static item => MTLFormats.Metal(item.Triangles.Transform)).ToArray(); + + if (geometryCount > 0) + { + unsafe + { + transforms.AsSpan().CopyTo(new Span((void*)mappedMemory.Pointer, (int)geometryCount)); + } + } TransformBuffer.Unmap(); diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 37146667..5f2e18e4 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -5,6 +5,8 @@ namespace Zenith.NET.Metal; internal unsafe class MTLCommandBuffer : CommandBuffer { + private MTL4RenderPassDescriptor? currentRenderPassDescriptor; + public MTL4CommandAllocator CommandAllocator; public MTL4CommandBuffer CommandBuffer; @@ -31,83 +33,105 @@ protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); } - protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + protected override void CopyBufferToTextureImpl(Buffer src, + uint srcOffsetInBytes, + TextureDataLayout srcLayout, + Texture dest, + TextureSubresource destSubresource, + Offset3D destOffset, + Extent3D destExtent) { MTLBuffer mtlSrc = src.Metal(); MTLTexture mtlDest = dest.Metal(); - (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(mtlDest.Desc.Format, destExtent.Width, destExtent.Height); - - uint formatSizeInBytes = ZenithHelper.SizeInBytes(mtlDest.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); - CommandEncoder.Compute?.Copy(mtlSrc.Buffer, srcOffsetInBytes, - sliceRowPitchInBytes, - sliceDepthPitchInBytes, + srcLayout.RowPitchInBytes, + srcLayout.SlicePitchInBytes, new(destExtent.Width, destExtent.Height, destExtent.Depth), mtlDest.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlDest.Desc, destSlice), - destSlice.MipLevel, + destSubresource.ArrayLayer, + destSubresource.MipLevel, new(destOffset.X, destOffset.Y, destOffset.Z)); CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); } - protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent) + protected override void CopyTextureImpl(Texture src, + TextureSubresource srcSubresource, + Offset3D srcOffset, + Texture dest, + TextureSubresource destSubresource, + Offset3D destOffset, + Extent3D extent) { MTLTexture mtlSrc = src.Metal(); MTLTexture mtlDest = dest.Metal(); CommandEncoder.Compute?.Copy(mtlSrc.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlSrc.Desc, srcSlice), - srcSlice.MipLevel, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, new(srcOffset.X, srcOffset.Y, srcOffset.Z), new(extent.Width, extent.Height, extent.Depth), mtlDest.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlDest.Desc, destSlice), - destSlice.MipLevel, + destSubresource.ArrayLayer, + destSubresource.MipLevel, new(destOffset.X, destOffset.Y, destOffset.Z)); CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); } - protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes) + protected override void CopyTextureToBufferImpl(Texture src, + TextureSubresource srcSubresource, + Offset3D srcOffset, + Extent3D srcExtent, + Buffer dest, + uint destOffsetInBytes, + TextureDataLayout destLayout) { MTLTexture mtlSrc = src.Metal(); MTLBuffer mtlDest = dest.Metal(); - (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(mtlSrc.Desc.Format, srcExtent.Width, srcExtent.Height); - - uint formatSizeInBytes = ZenithHelper.SizeInBytes(mtlSrc.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlSrc.Desc, srcSlice), - srcSlice.MipLevel, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, new(srcOffset.X, srcOffset.Y, srcOffset.Z), new(srcExtent.Width, srcExtent.Height, srcExtent.Depth), mtlDest.Buffer, destOffsetInBytes, - sliceRowPitchInBytes, - sliceDepthPitchInBytes); + destLayout.RowPitchInBytes, + destLayout.SlicePitchInBytes); CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); } - protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + protected override TextureDataLayout GetTextureCopyLayout(PixelFormat format, Extent3D extent) + { + uint rowPitchInBytes = ZenithHelper.RowPitchInBytes(format, extent.Width, extent.Height); + uint slicePitchInBytes = ZenithHelper.SlicePitchInBytes(format, extent.Width, extent.Height); + + return new() + { + SizeInBytes = slicePitchInBytes * extent.Depth, + RowPitchInBytes = rowPitchInBytes, + SlicePitchInBytes = slicePitchInBytes + }; + } + + protected override void ResolveTextureImpl(Texture src, + TextureSubresource srcSubresource, + Texture dest, + TextureSubresource destSubresource) { MTLTexture mtlSrc = src.Metal(); MTLTexture mtlDest = dest.Metal(); CommandEncoder.Compute?.Copy(mtlSrc.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlSrc.Desc, srcSlice), - srcSlice.MipLevel, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, mtlDest.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlDest.Desc, destSlice), - destSlice.MipLevel, + destSubresource.ArrayLayer, + destSubresource.MipLevel, 1, 1); @@ -129,25 +153,35 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru accelerationStructure.Metal().Update(this, newDesc); } - protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue) + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment) { - MTLFrameBuffer mtlFrameBuffer = frameBuffer.Metal(); + currentRenderPassDescriptor?.Dispose(); - bool clearColor = clearValue.Flags.HasFlag(ClearFlags.Color); - bool clearDepth = clearValue.Flags.HasFlag(ClearFlags.Depth); - bool clearStencil = clearValue.Flags.HasFlag(ClearFlags.Stencil); + MTL4RenderPassDescriptor descriptor = currentRenderPassDescriptor = new(); - for (uint i = 0; i < mtlFrameBuffer.ColorAttachmentCount; i++) - { - MTLRenderPassColorAttachmentDescriptor colorAttachment = mtlFrameBuffer.Descriptor.ColorAttachments[i]; - - colorAttachment.LoadAction = MTLLoadAction.Load; + uint colorAttachmentCount = (uint)colorAttachments.Length; - if (clearColor) + for (uint i = 0; i < colorAttachmentCount; i++) + { + ColorAttachment colorRenderTarget = colorAttachments[(int)i]; + MTLRenderPassColorAttachmentDescriptor colorAttachment = descriptor.ColorAttachments[i] = new() { - colorAttachment.LoadAction = MTLLoadAction.Clear; - - Vector4 color = clearValue.ColorValues[i]; + Texture = colorRenderTarget.Texture.Metal().Texture, + Level = colorRenderTarget.Subresource.MipLevel, + Slice = colorRenderTarget.Subresource.ArrayLayer, + LoadAction = colorRenderTarget.LoadOp switch + { + LoadOp.Clear => MTLLoadAction.Clear, + LoadOp.DontCare => MTLLoadAction.DontCare, + _ => MTLLoadAction.Load + }, + StoreAction = colorRenderTarget.StoreOp is StoreOp.Store ? MTLStoreAction.Store : MTLStoreAction.DontCare + }; + + if (colorRenderTarget.LoadOp is LoadOp.Clear) + { + Vector4 color = colorRenderTarget.ClearColor; colorAttachment.ClearColor = new() { @@ -159,49 +193,70 @@ protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue } } - if (mtlFrameBuffer.HasDepthStencilAttachment) + if (depthStencilAttachment is { } attachment) { - MTLRenderPassDepthAttachmentDescriptor depthAttachment = mtlFrameBuffer.Descriptor.DepthAttachment; - - if (!depthAttachment.Texture.IsNull) + if (ZenithHelper.HasDepth(attachment.Texture.Desc.Format)) { - depthAttachment.LoadAction = MTLLoadAction.Load; + MTLRenderPassDepthAttachmentDescriptor depthAttachment = descriptor.DepthAttachment = new() + { + Texture = attachment.Texture.Metal().Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = attachment.DepthLoadOp switch + { + LoadOp.Clear => MTLLoadAction.Clear, + LoadOp.DontCare => MTLLoadAction.DontCare, + _ => MTLLoadAction.Load + }, + StoreAction = attachment.DepthStoreOp is StoreOp.Store ? MTLStoreAction.Store : MTLStoreAction.DontCare + }; - if (clearDepth) + if (attachment.DepthLoadOp is LoadOp.Clear) { - depthAttachment.LoadAction = MTLLoadAction.Clear; - depthAttachment.ClearDepth = clearValue.Depth; + depthAttachment.ClearDepth = attachment.ClearDepth; } } - MTLRenderPassStencilAttachmentDescriptor stencilAttachment = mtlFrameBuffer.Descriptor.StencilAttachment; - - if (!stencilAttachment.Texture.IsNull) + if (ZenithHelper.HasStencil(attachment.Texture.Desc.Format)) { - stencilAttachment.LoadAction = MTLLoadAction.Load; + MTLRenderPassStencilAttachmentDescriptor stencilAttachment = descriptor.StencilAttachment = new() + { + Texture = attachment.Texture.Metal().Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = attachment.StencilLoadOp switch + { + LoadOp.Clear => MTLLoadAction.Clear, + LoadOp.DontCare => MTLLoadAction.DontCare, + _ => MTLLoadAction.Load + }, + StoreAction = attachment.StencilStoreOp is StoreOp.Store ? MTLStoreAction.Store : MTLStoreAction.DontCare + }; - if (clearStencil) + if (attachment.StencilLoadOp is LoadOp.Clear) { - stencilAttachment.LoadAction = MTLLoadAction.Clear; - stencilAttachment.ClearStencil = clearValue.Stencil; + stencilAttachment.ClearStencil = attachment.ClearStencil; } } } - CommandEncoder.BeginRenderPass(mtlFrameBuffer.Descriptor); + CommandEncoder.BeginRenderPass(descriptor); } - protected override void EndRenderPassImpl(FrameBuffer frameBuffer) + protected override void EndRenderPassImpl() { CommandEncoder.EndRenderPass(); + + currentRenderPassDescriptor?.Dispose(); + currentRenderPassDescriptor = null; } - protected override void SetScissorsImpl(Scissor[] scissors) + protected override void SetScissorsImpl(ReadOnlySpan scissors) { CommandEncoder.SetScissors(scissors); } - protected override void SetViewportsImpl(Viewport[] viewports) + protected override void SetViewportsImpl(ReadOnlySpan viewports) { CommandEncoder.SetViewports(viewports); } @@ -377,6 +432,9 @@ protected override void EndImpl() protected override void ResetImpl() { + currentRenderPassDescriptor?.Dispose(); + currentRenderPassDescriptor = null; + CommandAllocator.Reset(); } @@ -387,6 +445,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + currentRenderPassDescriptor?.Dispose(); + base.Destroy(); CommandEncoder.Dispose(); diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs index 3c6c245e..6958e800 100644 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs @@ -130,29 +130,43 @@ public void EndRenderPass() todoEndQueries.Clear(); } - public void SetScissors(Scissor[] scissors) + public void SetScissors(ReadOnlySpan scissors) { if (Render is null) { - todoScissors = [.. scissors]; + todoScissors = scissors.ToArray(); } else { - MTLScissorRect[] mtlScissors = [.. scissors.Select(static item => new MTLScissorRect((uint)item.X, (uint)item.Y, item.Width, item.Height))]; + MTLScissorRect[] mtlScissors = new MTLScissorRect[scissors.Length]; + + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; + + mtlScissors[i] = new((uint)scissor.X, (uint)scissor.Y, scissor.Width, scissor.Height); + } Render.SetScissorRects(mtlScissors); } } - public void SetViewports(Viewport[] viewports) + public void SetViewports(ReadOnlySpan viewports) { if (Render is null) { - todoViewports = [.. viewports]; + todoViewports = viewports.ToArray(); } else { - MTLViewport[] mtlViewports = [.. viewports.Select(static item => new MTLViewport(item.X, item.Y, item.Width, item.Height, item.MinDepth, item.MaxDepth))]; + MTLViewport[] mtlViewports = new MTLViewport[viewports.Length]; + + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; + + mtlViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); + } Render.SetViewports(mtlViewports); } diff --git a/sources/Zenith.NET.Metal/MTLFrameBuffer.cs b/sources/Zenith.NET.Metal/MTLFrameBuffer.cs deleted file mode 100644 index 547642f1..00000000 --- a/sources/Zenith.NET.Metal/MTLFrameBuffer.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLFrameBuffer : FrameBuffer -{ - public MTL4RenderPassDescriptor Descriptor; - - public MTLFrameBuffer(MTLGraphicsContext context, FrameBufferDesc desc) : base(context, desc) - { - ColorAttachmentCount = (uint)desc.ColorAttachments.Length; - HasDepthStencilAttachment = desc.DepthStencilAttachment is not null; - - Descriptor = new(); - - uint width = 0; - uint height = 0; - SampleCount sampleCount = SampleCount.Count1; - - for (uint i = 0; i < ColorAttachmentCount; i++) - { - FrameBufferAttachment attachment = desc.ColorAttachments[i]; - - if (i is 0) - { - ZenithHelper.MipDimensions(attachment.Target.Desc.Width, attachment.Target.Desc.Height, 0, attachment.Slice.MipLevel, out width, out height, out _); - - sampleCount = attachment.Target.Desc.SampleCount; - } - - Descriptor.ColorAttachments[i] = new() - { - Texture = attachment.Target.Metal().Texture, - Level = attachment.Slice.MipLevel, - Slice = ZenithHelper.FlattenArrayLayerIndex(attachment.Target.Desc, attachment.Slice), - LoadAction = MTLLoadAction.Load, - StoreAction = MTLStoreAction.Store - }; - } - - if (HasDepthStencilAttachment) - { - FrameBufferAttachment attachment = desc.DepthStencilAttachment!.Value; - - if (ColorAttachmentCount is 0) - { - ZenithHelper.MipDimensions(attachment.Target.Desc.Width, attachment.Target.Desc.Height, 0, attachment.Slice.MipLevel, out width, out height, out _); - - sampleCount = attachment.Target.Desc.SampleCount; - } - - if (ZenithHelper.HasDepth(attachment.Target.Desc.Format)) - { - Descriptor.DepthAttachment = new() - { - Texture = attachment.Target.Metal().Texture, - Level = attachment.Slice.MipLevel, - Slice = ZenithHelper.FlattenArrayLayerIndex(attachment.Target.Desc, attachment.Slice), - LoadAction = MTLLoadAction.Load, - StoreAction = MTLStoreAction.Store - }; - } - - if (ZenithHelper.HasStencil(attachment.Target.Desc.Format)) - { - Descriptor.StencilAttachment = new() - { - Texture = attachment.Target.Metal().Texture, - Level = attachment.Slice.MipLevel, - Slice = ZenithHelper.FlattenArrayLayerIndex(attachment.Target.Desc, attachment.Slice), - LoadAction = MTLLoadAction.Load, - StoreAction = MTLStoreAction.Store - }; - } - } - - Descriptor.RenderTargetWidth = width; - Descriptor.RenderTargetHeight = height; - - Width = width; - Height = height; - Output = new() - { - ColorAttachments = [.. desc.ColorAttachments.Select(static item => item.Target.Desc.Format)], - DepthStencilAttachment = desc.DepthStencilAttachment?.Target.Desc.Format, - SampleCount = sampleCount - }; - } - - public override uint ColorAttachmentCount { get; } - - public override bool HasDepthStencilAttachment { get; } - - public override uint Width { get; } - - public override uint Height { get; } - - public override Output Output { get; } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Descriptor.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 1dc8f358..6b4af04d 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -68,11 +68,6 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) return new MTLSwapChain(this, desc); } - protected override FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc) - { - return new MTLFrameBuffer(this, desc); - } - protected override Shader CreateShaderImpl(ShaderDesc desc) { return new MTLShader(this, desc); diff --git a/sources/Zenith.NET.Metal/MTLQueryHeap.cs b/sources/Zenith.NET.Metal/MTLQueryHeap.cs index 99edbc27..71c80ba6 100644 --- a/sources/Zenith.NET.Metal/MTLQueryHeap.cs +++ b/sources/Zenith.NET.Metal/MTLQueryHeap.cs @@ -31,7 +31,10 @@ protected override void GetResultsImpl(Span results, uint startIndex) { MappedMemory mappedMemory = Buffer.Map(); - new Span((void*)(mappedMemory.Pointer + (sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + unsafe + { + new ReadOnlySpan((void*)(mappedMemory.Pointer + (nint)(sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + } Buffer.Unmap(); } diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs index bddf976b..18790fdf 100644 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ b/sources/Zenith.NET.Metal/MTLResourceTable.cs @@ -117,10 +117,6 @@ protected override void WriteImpl(uint binding, IBindableResource[] resources) Bind(ArgumentTable, uint.MaxValue); } - protected override void PreprocessImpl(CommandBuffer commandBuffer) - { - } - protected override void SetResourceName(string name) { } diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 34e067ba..2a8846e7 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -4,7 +4,7 @@ namespace Zenith.NET.Metal; internal class MTLSwapChain : SwapChain { - private readonly MTLSwapChainFrameBuffer swapChainFrameBuffer; + private readonly MTLSwapChainTargets swapChainTargets; public CAMetalLayer Layer = CAMetalLayer.Null; @@ -12,14 +12,43 @@ internal class MTLSwapChain : SwapChain public MTLSwapChain(MTLGraphicsContext context, SwapChainDesc desc) : base(context, desc) { - swapChainFrameBuffer = new(context, this); + swapChainTargets = new(context, this); CreateSwapChain(); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - public override FrameBuffer FrameBuffer => swapChainFrameBuffer.Get(Desc.Surface.Width, Desc.Surface.Height, Drawable); + public override uint Width => CurrentColorTarget.Desc.Width; + + public override uint Height => CurrentColorTarget.Desc.Height; + + public override Texture CurrentColorTarget + { + get + { + swapChainTargets.EnsureTargets(Desc.Surface.Width, Desc.Surface.Height, Drawable); + + return swapChainTargets.ColorTarget; + } + } + + public override Texture? CurrentDepthStencilTarget + { + get + { + swapChainTargets.EnsureTargets(Desc.Surface.Width, Desc.Surface.Height, Drawable); + + return swapChainTargets.DepthStencilTarget; + } + } + + public override Output Output => new() + { + ColorAttachments = [Desc.ColorTargetFormat], + DepthStencilAttachment = Desc.DepthStencilTargetFormat, + SampleCount = SampleCount.Count1 + }; public override void Present() { @@ -32,6 +61,7 @@ public override void Present() protected override void ResizeImpl() { Drawable.Dispose(); + swapChainTargets.DestroyTargets(); Layer.DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height); @@ -51,7 +81,7 @@ protected override void Destroy() { DestroySwapChain(); - swapChainFrameBuffer.Dispose(); + swapChainTargets.Dispose(); } private void CreateSwapChain() diff --git a/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs b/sources/Zenith.NET.Metal/MTLSwapChainTargets.cs similarity index 60% rename from sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs rename to sources/Zenith.NET.Metal/MTLSwapChainTargets.cs index 2a2cf920..ac3e5f21 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChainTargets.cs @@ -2,13 +2,16 @@ namespace Zenith.NET.Metal; -internal class MTLSwapChainFrameBuffer(MTLGraphicsContext context, MTLSwapChain swapChain) : GraphicsResource(context) +internal class MTLSwapChainTargets(MTLGraphicsContext context, MTLSwapChain swapChain) : GraphicsResource(context) { private MTLTexture? colorTarget; private MTLTexture? depthStencilTarget; - private MTLFrameBuffer? frameBuffer; - public MTLFrameBuffer Get(uint width, uint height, CAMetalDrawable drawable) + public MTLTexture ColorTarget => colorTarget!; + + public MTLTexture? DepthStencilTarget => depthStencilTarget; + + public void EnsureTargets(uint width, uint height, CAMetalDrawable drawable) { colorTarget ??= new(context, new() { @@ -36,22 +39,16 @@ public MTLFrameBuffer Get(uint width, uint height, CAMetalDrawable drawable) Flags = TextureUsageFlags.DepthStencil }) : null; - frameBuffer ??= new(context, new() + if (colorTarget.Desc.Width != width || colorTarget.Desc.Height != height) { - ColorAttachments = [new() { Target = colorTarget }], - DepthStencilAttachment = depthStencilTarget is not null ? new() { Target = depthStencilTarget } : null - }); + DestroyTargets(); - if (frameBuffer.Width != width || frameBuffer.Height != height) - { - DestroyFrameBuffer(); + EnsureTargets(width, height, drawable); - return Get(width, height, drawable); + return; } - frameBuffer.Descriptor.ColorAttachments[0].Texture = colorTarget.Texture = drawable.Texture; - - return frameBuffer; + colorTarget.Texture = drawable.Texture; } protected override void SetResourceName(string name) @@ -60,14 +57,11 @@ protected override void SetResourceName(string name) protected override void Destroy() { - DestroyFrameBuffer(); + DestroyTargets(); } - private void DestroyFrameBuffer() + public void DestroyTargets() { - frameBuffer?.Dispose(); - frameBuffer = null; - depthStencilTarget?.Dispose(); depthStencilTarget = null; diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs index 8a6c578c..94d0572f 100644 --- a/sources/Zenith.NET.Metal/MTLTextureView.cs +++ b/sources/Zenith.NET.Metal/MTLTextureView.cs @@ -8,12 +8,14 @@ internal class MTLTextureView : TextureView public MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : base(context, desc) { + TextureSubresourceRange range = desc.Range; + MTLTextureViewDescriptor descriptor = new() { - PixelFormat = MTLFormats.Metal(desc.Texture.Desc.Format).PixelFormat, - TextureType = Resolve(desc), - LevelRange = new(desc.FirstMipLevel, desc.MipLevelCount), - SliceRange = new(ZenithHelper.FlattenArrayLayerRange(desc).FlattenArrayLayerIndex, ZenithHelper.FlattenArrayLayerRange(desc).FlattenArrayLayerCount) + PixelFormat = MTLFormats.Metal(desc.Format).PixelFormat, + TextureType = MTLFormats.Metal(desc.Type), + LevelRange = new(range.BaseMipLevel, range.LevelCount), + SliceRange = new(range.BaseArrayLayer, range.LayerCount) }; Texture = desc.Texture.Metal().Texture.MakeTextureView(descriptor); @@ -28,15 +30,4 @@ protected override void Destroy() { Texture.Dispose(); } - - private static MTLTextureType Resolve(TextureViewDesc desc) - { - return MTLFormats.Metal(desc.Texture.Desc.Type switch - { - TextureType.Texture1DArray when desc.ArrayLayerCount is 1 => TextureType.Texture1D, - TextureType.Texture2DArray when desc.ArrayLayerCount is 1 => TextureType.Texture2D, - TextureType.TextureCubeArray when desc.ArrayLayerCount is 1 => TextureType.TextureCube, - _ => desc.Texture.Desc.Type - }); - } } diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index baae72f3..65ea79f9 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -89,14 +89,6 @@ internal VKSwapChain Vulkan() } } - extension(FrameBuffer frameBuffer) - { - internal VKFrameBuffer Vulkan() - { - return (VKFrameBuffer)frameBuffer; - } - } - extension(Shader shader) { internal VKShader Vulkan() diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index b29a9464..c1a76ec7 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -23,7 +23,15 @@ public VKBottomLevelAccelerationStructure(VKGraphicsContext context, BottomLevel MappedMemory mappedMemory = TransformBuffer.Map(); - desc.Geometries.Select(static item => VKFormats.Vulkan(item.Triangles.Transform)).ToArray().CopyTo(new Span((TransformMatrixKHR*)mappedMemory.Pointer, (int)geometryCount)); + TransformMatrixKHR[] transforms = desc.Geometries.Select(static item => VKFormats.Vulkan(item.Triangles.Transform)).ToArray(); + + if (geometryCount > 0) + { + unsafe + { + transforms.AsSpan().CopyTo(new Span((void*)mappedMemory.Pointer, (int)geometryCount)); + } + } TransformBuffer.Unmap(); diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 6e85dfd5..fc653a1f 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -5,6 +5,8 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKCommandBuffer : CommandBuffer { + private readonly List renderPassImageViews = []; + public CommandPool CommandPool; public VkCommandBuffer CommandBuffer; @@ -57,31 +59,31 @@ protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer Context.Vk.CmdPipelineBarrier(CommandBuffer, PipelineStageFlags.TransferBit, PipelineStageFlags.TransferBit, 0, 1, &barrier, 0, null, 0, null); } - protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + protected override void CopyBufferToTextureImpl(Buffer src, + uint srcOffsetInBytes, + TextureDataLayout srcLayout, + Texture dest, + TextureSubresource destSubresource, + Offset3D destOffset, + Extent3D destExtent) { VKBuffer vkSrc = src.Vulkan(); VKTexture vkDest = dest.Vulkan(); - ImageLayout destOldLayout = vkDest.Layouts[ZenithHelper.SubresourceIndex(vkDest.Desc, destSlice)]; - - vkDest.TransitionLayout(this, destSlice, ImageLayout.TransferDstOptimal); - - (uint blockWidth, uint blockHeight, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(vkDest.Desc.Format, destExtent.Width, destExtent.Height); + (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkDest.Desc.Format, destExtent.Width, destExtent.Height); uint formatSizeInBytes = ZenithHelper.SizeInBytes(vkDest.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); BufferImageCopy bufferImageCopy = new() { BufferOffset = srcOffsetInBytes, - BufferRowLength = sliceRowPitchInBytes / formatSizeInBytes * blockWidth, - BufferImageHeight = sliceDepthPitchInBytes / sliceRowPitchInBytes * blockHeight, + BufferRowLength = srcLayout.RowPitchInBytes / formatSizeInBytes * blockWidth, + BufferImageHeight = srcLayout.SlicePitchInBytes / srcLayout.RowPitchInBytes * blockHeight, ImageSubresource = new() { AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkDest.Desc, destSlice), + MipLevel = destSubresource.MipLevel, + BaseArrayLayer = destSubresource.ArrayLayer, LayerCount = 1 }, ImageOffset = new() @@ -99,28 +101,26 @@ protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInByte }; Context.Vk.CmdCopyBufferToImage(CommandBuffer, vkSrc.Buffer, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &bufferImageCopy); - - vkDest.TransitionLayout(this, destSlice, destOldLayout); } - protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent) + protected override void CopyTextureImpl(Texture src, + TextureSubresource srcSubresource, + Offset3D srcOffset, + Texture dest, + TextureSubresource destSubresource, + Offset3D destOffset, + Extent3D extent) { VKTexture vkSrc = src.Vulkan(); VKTexture vkDest = dest.Vulkan(); - ImageLayout srcOldLayout = vkSrc.Layouts[ZenithHelper.SubresourceIndex(vkSrc.Desc, srcSlice)]; - ImageLayout destOldLayout = vkDest.Layouts[ZenithHelper.SubresourceIndex(vkDest.Desc, destSlice)]; - - vkSrc.TransitionLayout(this, srcSlice, ImageLayout.TransferSrcOptimal); - vkDest.TransitionLayout(this, destSlice, ImageLayout.TransferDstOptimal); - ImageCopy imageCopy = new() { SrcSubresource = new() { AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkSrc.Desc, srcSlice), + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, LayerCount = 1 }, SrcOffset = new() @@ -132,8 +132,8 @@ protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, Text DstSubresource = new() { AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkDest.Desc, destSlice), + MipLevel = destSubresource.MipLevel, + BaseArrayLayer = destSubresource.ArrayLayer, LayerCount = 1 }, DstOffset = new() @@ -151,35 +151,32 @@ protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, Text }; Context.Vk.CmdCopyImage(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &imageCopy); - - vkSrc.TransitionLayout(this, srcSlice, srcOldLayout); - vkDest.TransitionLayout(this, destSlice, destOldLayout); } - protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes) + protected override void CopyTextureToBufferImpl(Texture src, + TextureSubresource srcSubresource, + Offset3D srcOffset, + Extent3D srcExtent, + Buffer dest, + uint destOffsetInBytes, + TextureDataLayout destLayout) { VKTexture vkSrc = src.Vulkan(); - ImageLayout srcOldLayout = vkSrc.Layouts[ZenithHelper.SubresourceIndex(vkSrc.Desc, srcSlice)]; - - vkSrc.TransitionLayout(this, srcSlice, ImageLayout.TransferSrcOptimal); - - (uint blockWidth, uint blockHeight, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(vkSrc.Desc.Format, srcExtent.Width, srcExtent.Height); + (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkSrc.Desc.Format, srcExtent.Width, srcExtent.Height); uint formatSizeInBytes = ZenithHelper.SizeInBytes(vkSrc.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); BufferImageCopy bufferImageCopy = new() { BufferOffset = destOffsetInBytes, - BufferRowLength = sliceRowPitchInBytes / formatSizeInBytes * blockWidth, - BufferImageHeight = sliceDepthPitchInBytes / sliceRowPitchInBytes * blockHeight, + BufferRowLength = destLayout.RowPitchInBytes / formatSizeInBytes * blockWidth, + BufferImageHeight = destLayout.SlicePitchInBytes / destLayout.RowPitchInBytes * blockHeight, ImageSubresource = new() { AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkSrc.Desc, srcSlice), + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, LayerCount = 1 }, ImageOffset = new() @@ -197,37 +194,45 @@ protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSli }; Context.Vk.CmdCopyImageToBuffer(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, dest.Vulkan().Buffer, 1, &bufferImageCopy); + } - vkSrc.TransitionLayout(this, srcSlice, srcOldLayout); + protected override TextureDataLayout GetTextureCopyLayout(PixelFormat format, Extent3D extent) + { + uint rowPitchInBytes = ZenithHelper.RowPitchInBytes(format, extent.Width, extent.Height); + uint slicePitchInBytes = ZenithHelper.SlicePitchInBytes(format, extent.Width, extent.Height); + + return new() + { + SizeInBytes = slicePitchInBytes * extent.Depth, + RowPitchInBytes = rowPitchInBytes, + SlicePitchInBytes = slicePitchInBytes + }; } - protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + protected override void ResolveTextureImpl(Texture src, + TextureSubresource srcSubresource, + Texture dest, + TextureSubresource destSubresource) { VKTexture vkSrc = src.Vulkan(); VKTexture vkDest = dest.Vulkan(); - ImageLayout srcOldLayout = vkSrc.Layouts[ZenithHelper.SubresourceIndex(vkSrc.Desc, srcSlice)]; - ImageLayout destOldLayout = vkDest.Layouts[ZenithHelper.SubresourceIndex(vkDest.Desc, destSlice)]; - - vkSrc.TransitionLayout(this, srcSlice, ImageLayout.TransferSrcOptimal); - vkDest.TransitionLayout(this, destSlice, ImageLayout.TransferDstOptimal); - - ZenithHelper.MipDimensions(vkDest.Desc.Width, vkDest.Desc.Height, vkDest.Desc.Depth, destSlice.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); + ZenithHelper.MipDimensions(vkDest.Desc.Width, vkDest.Desc.Height, vkDest.Desc.Depth, destSubresource.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); ImageResolve imageResolve = new() { SrcSubresource = new() { AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkSrc.Desc, srcSlice), + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, LayerCount = 1 }, DstSubresource = new() { AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkDest.Desc, destSlice), + MipLevel = destSubresource.MipLevel, + BaseArrayLayer = destSubresource.ArrayLayer, LayerCount = 1 }, Extent = new() @@ -239,9 +244,6 @@ protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, T }; Context.Vk.CmdResolveImage(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &imageResolve); - - vkSrc.TransitionLayout(this, srcSlice, srcOldLayout); - vkDest.TransitionLayout(this, destSlice, destOldLayout); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) @@ -259,29 +261,54 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru accelerationStructure.Vulkan().Update(this, newDesc); } - protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue) + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, + DepthStencilAttachment? depthStencilAttachment) { - VKFrameBuffer vkFrameBuffer = frameBuffer.Vulkan(); + using ZenithMarshal.Scope scope = new(); - vkFrameBuffer.PrepareAttachments(this); + uint colorAttachmentCount = (uint)colorAttachments.Length; + RenderingAttachmentInfo* colorAttachmentInfos = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - bool clearColor = clearValue.Flags.HasFlag(ClearFlags.Color); - bool clearDepth = clearValue.Flags.HasFlag(ClearFlags.Depth); - bool clearStencil = clearValue.Flags.HasFlag(ClearFlags.Stencil); + uint width = 0; + uint height = 0; - for (int i = 0; i < vkFrameBuffer.ColorAttachmentCount; i++) + for (uint i = 0; i < colorAttachmentCount; i++) { - ref RenderingAttachmentInfo colorAttachment = ref vkFrameBuffer.ColorAttachments[i]; + ColorAttachment attachment = colorAttachments[(int)i]; - colorAttachment.LoadOp = AttachmentLoadOp.Load; - - if (clearColor) + if (i is 0) { - colorAttachment.LoadOp = AttachmentLoadOp.Clear; + ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, + attachment.Texture.Desc.Height, + 0, + attachment.Subresource.MipLevel, + out width, + out height, + out _); + } - Vector4 color = clearValue.ColorValues[i]; + ImageView imageView = attachment.Texture.Vulkan().CreateAttachmentView(attachment.Subresource); + renderPassImageViews.Add(imageView); - colorAttachment.ClearValue.Color = new() + colorAttachmentInfos[i] = new() + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = imageView, + ImageLayout = ImageLayout.AttachmentOptimal, + LoadOp = attachment.LoadOp switch + { + LoadOp.Clear => AttachmentLoadOp.Clear, + LoadOp.DontCare => AttachmentLoadOp.DontCare, + _ => AttachmentLoadOp.Load + }, + StoreOp = attachment.StoreOp is StoreOp.Store ? AttachmentStoreOp.Store : AttachmentStoreOp.DontCare + }; + + if (attachment.LoadOp is LoadOp.Clear) + { + Vector4 color = attachment.ClearColor; + + colorAttachmentInfos[i].ClearValue.Color = new() { Float32_0 = color.X, Float32_1 = color.Y, @@ -291,55 +318,115 @@ protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue } } - if (vkFrameBuffer.HasDepthStencilAttachment) + RenderingAttachmentInfo* depthAttachment = null; + RenderingAttachmentInfo* stencilAttachment = null; + + if (depthStencilAttachment is { } depthStencilRenderTarget) { - if (vkFrameBuffer.DepthAttachment is not null) + if (colorAttachmentCount is 0) { - ref RenderingAttachmentInfo depthAttachment = ref vkFrameBuffer.DepthAttachment[0]; + ZenithHelper.MipDimensions(depthStencilRenderTarget.Texture.Desc.Width, + depthStencilRenderTarget.Texture.Desc.Height, + 0, + depthStencilRenderTarget.Subresource.MipLevel, + out width, + out height, + out _); + } - depthAttachment.LoadOp = AttachmentLoadOp.Load; + ImageView imageView = depthStencilRenderTarget.Texture.Vulkan().CreateAttachmentView(depthStencilRenderTarget.Subresource); + renderPassImageViews.Add(imageView); - if (clearDepth) + if (ZenithHelper.HasDepth(depthStencilRenderTarget.Texture.Desc.Format)) + { + depthAttachment = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, 1); + depthAttachment[0] = new() { - depthAttachment.LoadOp = AttachmentLoadOp.Clear; - depthAttachment.ClearValue.DepthStencil.Depth = clearValue.Depth; + SType = StructureType.RenderingAttachmentInfo, + ImageView = imageView, + ImageLayout = ImageLayout.AttachmentOptimal, + LoadOp = depthStencilRenderTarget.DepthLoadOp switch + { + LoadOp.Clear => AttachmentLoadOp.Clear, + LoadOp.DontCare => AttachmentLoadOp.DontCare, + _ => AttachmentLoadOp.Load + }, + StoreOp = depthStencilRenderTarget.DepthStoreOp is StoreOp.Store ? AttachmentStoreOp.Store : AttachmentStoreOp.DontCare + }; + + if (depthStencilRenderTarget.DepthLoadOp is LoadOp.Clear) + { + depthAttachment[0].ClearValue.DepthStencil.Depth = depthStencilRenderTarget.ClearDepth; } } - if (vkFrameBuffer.StencilAttachment is not null) + if (ZenithHelper.HasStencil(depthStencilRenderTarget.Texture.Desc.Format)) { - ref RenderingAttachmentInfo stencilAttachment = ref vkFrameBuffer.StencilAttachment[0]; - - stencilAttachment.LoadOp = AttachmentLoadOp.Load; + stencilAttachment = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, 1); + stencilAttachment[0] = new() + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = imageView, + ImageLayout = ImageLayout.AttachmentOptimal, + LoadOp = depthStencilRenderTarget.StencilLoadOp switch + { + LoadOp.Clear => AttachmentLoadOp.Clear, + LoadOp.DontCare => AttachmentLoadOp.DontCare, + _ => AttachmentLoadOp.Load + }, + StoreOp = depthStencilRenderTarget.StencilStoreOp is StoreOp.Store ? AttachmentStoreOp.Store : AttachmentStoreOp.DontCare + }; - if (clearStencil) + if (depthStencilRenderTarget.StencilLoadOp is LoadOp.Clear) { - stencilAttachment.LoadOp = AttachmentLoadOp.Clear; - stencilAttachment.ClearValue.DepthStencil.Stencil = clearValue.Stencil; + stencilAttachment[0].ClearValue.DepthStencil.Stencil = depthStencilRenderTarget.ClearStencil; } } } - Context.Vk.CmdBeginRendering(CommandBuffer, ref vkFrameBuffer.RenderingInfo); + RenderingInfo renderingInfo = new() + { + SType = StructureType.RenderingInfo, + RenderArea = new() { Extent = new() { Width = width, Height = height } }, + LayerCount = 1, + ColorAttachmentCount = colorAttachmentCount, + PColorAttachments = colorAttachmentInfos, + PDepthAttachment = depthAttachment, + PStencilAttachment = stencilAttachment + }; + + Context.Vk.CmdBeginRendering(CommandBuffer, ref renderingInfo); } - protected override void EndRenderPassImpl(FrameBuffer frameBuffer) + protected override void EndRenderPassImpl() { Context.Vk.CmdEndRendering(CommandBuffer); - - frameBuffer.Vulkan().PresentColorAttachments(this); } - protected override void SetScissorsImpl(Scissor[] scissors) + protected override void SetScissorsImpl(ReadOnlySpan scissors) { - Rect2D[] vkScissors = [.. scissors.Select(static item => new Rect2D(new(item.X, item.Y), new(item.Width, item.Height)))]; + Span vkScissors = scissors.Length <= 8 ? stackalloc Rect2D[scissors.Length] : new Rect2D[scissors.Length]; + + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; + + vkScissors[i] = new(new(scissor.X, scissor.Y), new(scissor.Width, scissor.Height)); + } Context.Vk.CmdSetScissor(CommandBuffer, 0, (uint)vkScissors.Length, ref vkScissors[0]); } - protected override void SetViewportsImpl(Viewport[] viewports) + protected override void SetViewportsImpl(ReadOnlySpan viewports) { - VkViewport[] vkViewports = [.. viewports.Select(static item => new VkViewport(item.X, item.Y + item.Height, item.Width, -item.Height, item.MinDepth, item.MaxDepth))]; + Span vkViewports = viewports.Length <= 8 ? stackalloc VkViewport[viewports.Length] : new VkViewport[viewports.Length]; + + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; + + vkViewports[i] = new(viewport.X, viewport.Y + viewport.Height, viewport.Width, -viewport.Height, viewport.MinDepth, viewport.MaxDepth); + } Context.Vk.CmdSetViewport(CommandBuffer, 0, (uint)vkViewports.Length, ref vkViewports[0]); } @@ -493,6 +580,8 @@ protected override void EndImpl() protected override void ResetImpl() { + ReleaseRenderPassImageViews(); + Context.Vk.ResetCommandBuffer(CommandBuffer, CommandBufferResetFlags.None).Success(); } @@ -513,8 +602,20 @@ protected override void SetResourceName(string name) protected override void Destroy() { + ReleaseRenderPassImageViews(); + base.Destroy(); Context.Vk.DestroyCommandPool(Context.Device, CommandPool, null); } + + private void ReleaseRenderPassImageViews() + { + foreach (ImageView imageView in renderPassImageViews) + { + Context.Vk.DestroyImageView(Context.Device, imageView, null); + } + + renderPassImageViews.Clear(); + } } diff --git a/sources/Zenith.NET.Vulkan/VKFrameBuffer.cs b/sources/Zenith.NET.Vulkan/VKFrameBuffer.cs deleted file mode 100644 index e904c56d..00000000 --- a/sources/Zenith.NET.Vulkan/VKFrameBuffer.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKFrameBuffer : FrameBuffer -{ - private readonly ZenithMarshal.Scope scope = new(); - - public RenderingAttachmentInfo* ColorAttachments; - - public RenderingAttachmentInfo* DepthAttachment; - - public RenderingAttachmentInfo* StencilAttachment; - - public RenderingInfo RenderingInfo; - - public VKFrameBuffer(VKGraphicsContext context, FrameBufferDesc desc) : base(context, desc) - { - ColorAttachmentCount = (uint)desc.ColorAttachments.Length; - HasDepthStencilAttachment = desc.DepthStencilAttachment is not null; - - ColorAttachments = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, ColorAttachmentCount); - - ImageViews = new ImageView[ColorAttachmentCount + (HasDepthStencilAttachment ? 1 : 0)]; - - uint width = 0; - uint height = 0; - SampleCount sampleCount = SampleCount.Count1; - - for (uint i = 0; i < ColorAttachmentCount; i++) - { - FrameBufferAttachment attachment = desc.ColorAttachments[i]; - - if (i is 0) - { - ZenithHelper.MipDimensions(attachment.Target.Desc.Width, attachment.Target.Desc.Height, 0, attachment.Slice.MipLevel, out width, out height, out _); - - sampleCount = attachment.Target.Desc.SampleCount; - } - - ColorAttachments[i] = new() - { - SType = StructureType.RenderingAttachmentInfo, - ImageView = ImageViews[i] = attachment.Target.Vulkan().CreateAttachmentView(attachment.Slice), - ImageLayout = ImageLayout.AttachmentOptimal, - LoadOp = AttachmentLoadOp.Load, - StoreOp = AttachmentStoreOp.Store - }; - } - - if (HasDepthStencilAttachment) - { - FrameBufferAttachment attachment = desc.DepthStencilAttachment!.Value; - - if (ColorAttachmentCount is 0) - { - ZenithHelper.MipDimensions(attachment.Target.Desc.Width, attachment.Target.Desc.Height, 0, attachment.Slice.MipLevel, out width, out height, out _); - - sampleCount = attachment.Target.Desc.SampleCount; - } - - ImageViews[ColorAttachmentCount] = attachment.Target.Vulkan().CreateAttachmentView(attachment.Slice); - - if (ZenithHelper.HasDepth(attachment.Target.Desc.Format)) - { - *(DepthAttachment = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, 1)) = new() - { - SType = StructureType.RenderingAttachmentInfo, - ImageView = ImageViews[ColorAttachmentCount], - ImageLayout = ImageLayout.AttachmentOptimal, - LoadOp = AttachmentLoadOp.Load, - StoreOp = AttachmentStoreOp.Store - }; - } - - if (ZenithHelper.HasStencil(attachment.Target.Desc.Format)) - { - *(StencilAttachment = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, 1)) = new() - { - SType = StructureType.RenderingAttachmentInfo, - ImageView = ImageViews[ColorAttachmentCount], - ImageLayout = ImageLayout.AttachmentOptimal, - LoadOp = AttachmentLoadOp.Load, - StoreOp = AttachmentStoreOp.Store - }; - } - } - - RenderingInfo = new() - { - SType = StructureType.RenderingInfo, - RenderArea = new() { Extent = new() { Width = width, Height = height } }, - LayerCount = 1, - ColorAttachmentCount = ColorAttachmentCount, - PColorAttachments = ColorAttachments, - PDepthAttachment = DepthAttachment, - PStencilAttachment = StencilAttachment - }; - - Width = width; - Height = height; - Output = new() - { - ColorAttachments = [.. desc.ColorAttachments.Select(static item => item.Target.Desc.Format)], - DepthStencilAttachment = desc.DepthStencilAttachment?.Target.Desc.Format, - SampleCount = sampleCount - }; - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public override uint ColorAttachmentCount { get; } - - public override bool HasDepthStencilAttachment { get; } - - public override uint Width { get; } - - public override uint Height { get; } - - public override Output Output { get; } - - public ImageView[] ImageViews { get; } - - public void PrepareAttachments(VKCommandBuffer commandBuffer) - { - foreach (FrameBufferAttachment attachment in Desc.ColorAttachments) - { - attachment.Target.Vulkan().TransitionLayout(commandBuffer, attachment.Slice, ImageLayout.ColorAttachmentOptimal); - } - - Desc.DepthStencilAttachment?.Target.Vulkan().TransitionLayout(commandBuffer, Desc.DepthStencilAttachment.Value.Slice, ImageLayout.DepthStencilAttachmentOptimal); - } - - public void PresentColorAttachments(VKCommandBuffer commandBuffer) - { - foreach (FrameBufferAttachment attachment in Desc.ColorAttachments) - { - if (attachment.Target.Desc.Flags.HasFlag(TextureUsageFlags.RenderTarget)) - { - attachment.Target.Vulkan().TransitionLayout(commandBuffer, attachment.Slice, ImageLayout.PresentSrcKhr); - } - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - foreach (ImageView imageView in ImageViews) - { - Context.Vk.DestroyImageView(Context.Device, imageView, null); - } - - scope.Dispose(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 0a3970a6..5243b720 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -443,11 +443,6 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) return new VKSwapChain(this, desc); } - protected override FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc) - { - return new VKFrameBuffer(this, desc); - } - protected override Shader CreateShaderImpl(ShaderDesc desc) { return new VKShader(this, desc); diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs index 3db410b3..eb7b0f7e 100644 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ b/sources/Zenith.NET.Vulkan/VKResourceTable.cs @@ -158,21 +158,6 @@ protected override void WriteImpl(uint binding, IBindableResource[] resources) } } - protected override void PreprocessImpl(CommandBuffer commandBuffer) - { - VKCommandBuffer vkCommandBuffer = commandBuffer.Vulkan(); - - foreach (VKTextureView? textureView in SrvTextureViews) - { - textureView?.TransitionLayout(vkCommandBuffer, ImageLayout.ShaderReadOnlyOptimal); - } - - foreach (VKTextureView? textureView in UavTextureViews) - { - textureView?.TransitionLayout(vkCommandBuffer, ImageLayout.General); - } - } - protected override void SetResourceName(string name) { } diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index 02005114..8d02a159 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -6,7 +6,7 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKSwapChain : SwapChain { private readonly VKFence fence; - private readonly VKSwapChainFrameBuffer swapChainFrameBuffer; + private readonly VKSwapChainTargets swapChainTargets; public SurfaceKHR Surface; @@ -17,7 +17,7 @@ internal unsafe class VKSwapChain : SwapChain public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context, desc) { fence = new(context); - swapChainFrameBuffer = new(context, this); + swapChainTargets = new(context, this); CreateSurface(); CreateSwapChain(); @@ -25,7 +25,20 @@ public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public override FrameBuffer FrameBuffer => swapChainFrameBuffer[ImageIndex]; + public override uint Width => CurrentColorTarget.Desc.Width; + + public override uint Height => CurrentColorTarget.Desc.Height; + + public override Texture CurrentColorTarget => swapChainTargets[ImageIndex]; + + public override Texture? CurrentDepthStencilTarget => swapChainTargets.DepthStencilTarget; + + public override Output Output => new() + { + ColorAttachments = [Desc.ColorTargetFormat], + DepthStencilAttachment = Desc.DepthStencilTargetFormat, + SampleCount = SampleCount.Count1 + }; public override void Present() { @@ -85,7 +98,7 @@ protected override void Destroy() DestroySwapChain(); DestroySurface(); - swapChainFrameBuffer.Dispose(); + swapChainTargets.Dispose(); fence.Dispose(); } @@ -265,19 +278,19 @@ private void CreateSwapChain() Context.Swapchain?.CreateSwapchain(Context.Device, &createInfo, null, out Swapchain).Success(); - swapChainFrameBuffer.CreateFrameBuffers(createInfo.ImageExtent.Width, createInfo.ImageExtent.Height, []); + swapChainTargets.CreateTargets(createInfo.ImageExtent.Width, createInfo.ImageExtent.Height, []); AcquireNextImage(); } else { - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); + swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); } } private void DestroySwapChain() { - swapChainFrameBuffer.DestroyFrameBuffers(); + swapChainTargets.DestroyTargets(); if (Swapchain.Handle is not 0) { diff --git a/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs b/sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs similarity index 59% rename from sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs rename to sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs index 01de96cf..fcbc8c7b 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs @@ -2,15 +2,16 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKSwapChainFrameBuffer(VKGraphicsContext context, VKSwapChain swapChain) : GraphicsResource(context) +internal unsafe class VKSwapChainTargets(VKGraphicsContext context, VKSwapChain swapChain) : GraphicsResource(context) { private VKTexture? depthStencilTarget; private VKTexture[] colorTargets = []; - private VKFrameBuffer[] frameBuffers = []; - public VKFrameBuffer this[uint index] => frameBuffers[index]; + public VKTexture this[uint index] => colorTargets[index]; - public void CreateFrameBuffers(uint width, uint height, nint[] handles) + public VKTexture? DepthStencilTarget => depthStencilTarget; + + public void CreateTargets(uint width, uint height, nint[] handles) { if (swapChain.Desc.DepthStencilTargetFormat is not null) { @@ -52,44 +53,22 @@ public void CreateFrameBuffers(uint width, uint height, nint[] handles) context.Swapchain?.GetSwapchainImages(context.Device, swapChain.Swapchain, &swapchainImageCount, swapchainImages).Success(); colorTargets = new VKTexture[swapchainImageCount]; - frameBuffers = new VKFrameBuffer[swapchainImageCount]; - - CommandBuffer commandBuffer = context.Graphics.CommandBuffer(); for (uint i = 0; i < swapchainImageCount; i++) { - frameBuffers[i] = new(context, new() - { - ColorAttachments = [new() { Target = colorTargets[i] = new(context, colorTargetDesc, swapchainImages[i]) }], - DepthStencilAttachment = depthStencilTarget is not null ? new() { Target = depthStencilTarget } : null - }); - - colorTargets[i].TransitionLayout(commandBuffer.Vulkan(), default, ImageLayout.PresentSrcKhr); + colorTargets[i] = new(context, colorTargetDesc, swapchainImages[i]); } - - commandBuffer.Submit(true); } else if (swapChain.Desc.Surface.Type is SurfaceType.D3D11Interop) { colorTargets = new VKTexture[1]; - frameBuffers = new VKFrameBuffer[1]; - frameBuffers[0] = new(context, new() - { - ColorAttachments = [new() { Target = colorTargets[0] = new(context, colorTargetDesc, ExternalMemoryHandleTypeFlags.D3D11TextureBit, handles[0]) }], - DepthStencilAttachment = depthStencilTarget is not null ? new() { Target = depthStencilTarget } : null - }); + colorTargets[0] = new(context, colorTargetDesc, ExternalMemoryHandleTypeFlags.D3D11TextureBit, handles[0]); } } - public void DestroyFrameBuffers() + public void DestroyTargets() { - foreach (VKFrameBuffer frameBuffer in frameBuffers) - { - frameBuffer.Dispose(); - } - frameBuffers = []; - foreach (VKTexture texture in colorTargets) { texture.Dispose(); @@ -106,6 +85,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - DestroyFrameBuffers(); + DestroyTargets(); } } diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index e2f16161..3d4893c0 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -25,7 +25,7 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, de Depth = desc.Type is TextureType.Texture3D ? desc.Depth : 1 }, MipLevels = desc.MipLevels, - ArrayLayers = ZenithHelper.FlattenArrayLayerCount(desc), + ArrayLayers = desc.ArrayLayers, Samples = VKFormats.Vulkan(desc.SampleCount), Usage = VKFormats.Vulkan(desc.Format, desc.Flags).UsageFlags, SharingMode = sharingMode, @@ -40,14 +40,16 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, de View = new(context, new() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = new() + { + BaseMipLevel = 0, + LevelCount = desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = desc.ArrayLayers + } }); - - Layouts = new ImageLayout[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(Layouts, ImageLayout.Undefined); } public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image) : base(context, desc) @@ -57,14 +59,16 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image) : bas View = new(context, new() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = new() + { + BaseMipLevel = 0, + LevelCount = desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = desc.ArrayLayers + } }); - - Layouts = new ImageLayout[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(Layouts, ImageLayout.Undefined); } public VKTexture(VKGraphicsContext context, TextureDesc desc, ExternalMemoryHandleTypeFlags handleTypes, nint handle) : base(context, desc) @@ -86,7 +90,7 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc, ExternalMemoryHand Depth = desc.Type is TextureType.Texture3D ? desc.Depth : 1 }, MipLevels = desc.MipLevels, - ArrayLayers = ZenithHelper.FlattenArrayLayerCount(desc), + ArrayLayers = desc.ArrayLayers, Samples = VKFormats.Vulkan(desc.SampleCount), Usage = VKFormats.Vulkan(desc.Format, desc.Flags).UsageFlags, SharingMode = sharingMode, @@ -104,14 +108,16 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc, ExternalMemoryHand View = new(context, new() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = new() + { + BaseMipLevel = 0, + LevelCount = desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = desc.ArrayLayers + } }); - - Layouts = new ImageLayout[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(Layouts, ImageLayout.Undefined); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; @@ -120,163 +126,12 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc, ExternalMemoryHand public VKTextureView View { get; } - public ImageLayout[] Layouts { get; } - - public void TransitionLayout(VKCommandBuffer commandBuffer, - uint firstMipLevel, - uint mipLevelCount, - uint firstArrayLayer, - uint arrayLayerCount, - uint firstFace, - uint faceCount, - ImageLayout newLayout) - { - if (newLayout is ImageLayout.Undefined) - { - return; - } - - for (uint i = 0; i < mipLevelCount; i++) - { - for (uint j = 0; j < arrayLayerCount; j++) - { - for (uint k = 0; k < faceCount; k++) - { - TextureSlice slice = new() { MipLevel = firstMipLevel + i, ArrayLayer = firstArrayLayer + j, Face = firstFace + k }; - - uint index = ZenithHelper.SubresourceIndex(Desc, slice); - - ImageLayout oldLayout = Layouts[index]; - - if (oldLayout == newLayout) - { - continue; - } - - AccessFlags srcAccessMask = AccessFlags.None; - PipelineStageFlags srcStageMask = PipelineStageFlags.None; - - if (oldLayout is ImageLayout.Undefined or ImageLayout.Preinitialized) - { - srcAccessMask = AccessFlags.None; - srcStageMask = PipelineStageFlags.TopOfPipeBit; - } - else if (oldLayout == ImageLayout.General) - { - srcAccessMask = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit; - srcStageMask = PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit; - } - else if (oldLayout == ImageLayout.ColorAttachmentOptimal) - { - srcAccessMask = AccessFlags.ColorAttachmentWriteBit; - srcStageMask = PipelineStageFlags.ColorAttachmentOutputBit; - } - else if (oldLayout == ImageLayout.DepthStencilAttachmentOptimal) - { - srcAccessMask = AccessFlags.DepthStencilAttachmentWriteBit; - srcStageMask = PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit; - } - else if (oldLayout == ImageLayout.ShaderReadOnlyOptimal) - { - srcAccessMask = AccessFlags.ShaderReadBit; - srcStageMask = PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit; - } - else if (oldLayout == ImageLayout.TransferSrcOptimal) - { - srcAccessMask = AccessFlags.TransferReadBit; - srcStageMask = PipelineStageFlags.TransferBit; - } - else if (oldLayout == ImageLayout.TransferDstOptimal) - { - srcAccessMask = AccessFlags.TransferWriteBit; - srcStageMask = PipelineStageFlags.TransferBit; - } - else if (oldLayout == ImageLayout.PresentSrcKhr) - { - srcAccessMask = AccessFlags.MemoryReadBit; - srcStageMask = PipelineStageFlags.BottomOfPipeBit; - } - - AccessFlags dstAccessMask = AccessFlags.None; - PipelineStageFlags dstStageMask = PipelineStageFlags.None; - - if (newLayout is ImageLayout.General) - { - dstAccessMask = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit; - dstStageMask = PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit; - } - else if (newLayout == ImageLayout.ColorAttachmentOptimal) - { - dstAccessMask = AccessFlags.ColorAttachmentWriteBit; - dstStageMask = PipelineStageFlags.ColorAttachmentOutputBit; - } - else if (newLayout == ImageLayout.DepthStencilAttachmentOptimal) - { - dstAccessMask = AccessFlags.DepthStencilAttachmentWriteBit; - dstStageMask = PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit; - } - else if (newLayout == ImageLayout.ShaderReadOnlyOptimal) - { - dstAccessMask = AccessFlags.ShaderReadBit; - dstStageMask = PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit; - } - else if (newLayout == ImageLayout.TransferSrcOptimal) - { - dstAccessMask = AccessFlags.TransferReadBit; - dstStageMask = PipelineStageFlags.TransferBit; - } - else if (newLayout == ImageLayout.TransferDstOptimal) - { - dstAccessMask = AccessFlags.TransferWriteBit; - dstStageMask = PipelineStageFlags.TransferBit; - } - else if (newLayout == ImageLayout.PresentSrcKhr) - { - dstAccessMask = AccessFlags.MemoryReadBit; - dstStageMask = PipelineStageFlags.BottomOfPipeBit; - } - - ImageMemoryBarrier imageMemoryBarrier = new() - { - SType = StructureType.ImageMemoryBarrier, - SrcAccessMask = srcAccessMask, - DstAccessMask = dstAccessMask, - OldLayout = oldLayout, - NewLayout = newLayout, - Image = Image, - SubresourceRange = new() - { - AspectMask = VKFormats.Vulkan(Desc.Format, Desc.Flags).AspectFlags, - BaseMipLevel = slice.MipLevel, - LevelCount = 1, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(Desc, slice), - LayerCount = 1 - } - }; - - Context.Vk.CmdPipelineBarrier(commandBuffer.CommandBuffer, - srcStageMask, - dstStageMask, - DependencyFlags.None, - 0, - null, - 0, - null, - 1, - &imageMemoryBarrier); - - Layouts[index] = newLayout; - } - } - } - } - - public void TransitionLayout(VKCommandBuffer commandBuffer, TextureSlice slice, ImageLayout newLayout) + public uint SubresourceIndex(TextureSubresource subresource) { - TransitionLayout(commandBuffer, slice.MipLevel, 1, slice.ArrayLayer, 1, slice.Face, 1, newLayout); + return (subresource.ArrayLayer * Desc.MipLevels) + subresource.MipLevel; } - public ImageView CreateAttachmentView(TextureSlice slice) + public ImageView CreateAttachmentView(TextureSubresource subresource) { ImageViewCreateInfo createInfo = new() { @@ -287,9 +142,9 @@ public ImageView CreateAttachmentView(TextureSlice slice) SubresourceRange = new() { AspectMask = VKFormats.Vulkan(Desc.Format, Desc.Flags).AspectFlags, - BaseMipLevel = slice.MipLevel, + BaseMipLevel = subresource.MipLevel, LevelCount = 1, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(Desc, slice), + BaseArrayLayer = subresource.ArrayLayer, LayerCount = 1 } }; diff --git a/sources/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Zenith.NET.Vulkan/VKTextureView.cs index 111f180f..c31b41ee 100644 --- a/sources/Zenith.NET.Vulkan/VKTextureView.cs +++ b/sources/Zenith.NET.Vulkan/VKTextureView.cs @@ -8,19 +8,21 @@ internal unsafe class VKTextureView : TextureView public VKTextureView(VKGraphicsContext context, TextureViewDesc desc) : base(context, desc) { + TextureSubresourceRange range = desc.Range; + ImageViewCreateInfo createInfo = new() { SType = StructureType.ImageViewCreateInfo, Image = desc.Texture.Vulkan().Image, - ViewType = Resolve(desc), - Format = VKFormats.Vulkan(desc.Texture.Desc.Format), + ViewType = VKFormats.Vulkan(desc.Type).ViewType, + Format = VKFormats.Vulkan(desc.Format), SubresourceRange = new() { - AspectMask = VKFormats.Vulkan(desc.Texture.Desc.Format, desc.Texture.Desc.Flags).AspectFlags & ~ImageAspectFlags.StencilBit, - BaseMipLevel = desc.FirstMipLevel, - LevelCount = desc.MipLevelCount, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerRange(desc).FlattenArrayLayerIndex, - LayerCount = ZenithHelper.FlattenArrayLayerRange(desc).FlattenArrayLayerCount + AspectMask = VKFormats.Vulkan(desc.Format, desc.Texture.Desc.Flags).AspectFlags & ~ImageAspectFlags.StencilBit, + BaseMipLevel = range.BaseMipLevel, + LevelCount = range.LevelCount, + BaseArrayLayer = range.BaseArrayLayer, + LayerCount = range.LayerCount } }; @@ -45,18 +47,6 @@ public VKTextureView(VKGraphicsContext context, TextureViewDesc desc) : base(con public DescriptorImageInfo UavImageInfo { get; } - public void TransitionLayout(VKCommandBuffer commandBuffer, ImageLayout newLayout) - { - Desc.Texture.Vulkan().TransitionLayout(commandBuffer, - Desc.FirstMipLevel, - Desc.MipLevelCount, - Desc.FirstArrayLayer, - Desc.ArrayLayerCount, - 0, - ZenithHelper.FaceCount(Desc.Texture.Desc), - newLayout); - } - protected override void SetResourceName(string name) { using ZenithMarshal.Scope scope = new(); @@ -76,15 +66,4 @@ protected override void Destroy() { Context.Vk.DestroyImageView(Context.Device, ImageView, null); } - - private static ImageViewType Resolve(TextureViewDesc desc) - { - return VKFormats.Vulkan(desc.Texture.Desc.Type switch - { - TextureType.Texture1DArray when desc.ArrayLayerCount is 1 => TextureType.Texture1D, - TextureType.Texture2DArray when desc.ArrayLayerCount is 1 => TextureType.Texture2D, - TextureType.TextureCubeArray when desc.ArrayLayerCount is 1 => TextureType.TextureCube, - _ => desc.Texture.Desc.Type - }).ViewType; - } } diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 61fc302a..52845eaa 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context), IBindableResource +public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) { private BufferDesc desc = desc; @@ -10,20 +10,37 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public abstract void Unmap(); - public void Upload(ReadOnlySpan data, uint offsetInBytes) where T : unmanaged + public void Upload(uint offsetInBytes, BufferData data) { - if (data.Length is 0) + if (desc.Flags.HasFlag(BufferUsageFlags.MapRead) || desc.Flags.HasFlag(BufferUsageFlags.MapWrite)) + { + MappedMemory mappedMemory = Map(); + + unsafe + { + new ReadOnlySpan((void*)data.Pointer, (int)data.SizeInBytes).CopyTo(new((void*)(mappedMemory.Pointer + offsetInBytes), (int)data.SizeInBytes)); + } + + Unmap(); + } + else { - return; + CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + + commandBuffer.Upload(this, offsetInBytes, data); + commandBuffer.Submit(true); } + } + public void Download(uint offsetInBytes, BufferData data) + { if (desc.Flags.HasFlag(BufferUsageFlags.MapRead) || desc.Flags.HasFlag(BufferUsageFlags.MapWrite)) { MappedMemory mappedMemory = Map(); unsafe { - data.CopyTo(new((void*)(mappedMemory.Pointer + offsetInBytes), data.Length)); + new ReadOnlySpan((void*)(mappedMemory.Pointer + offsetInBytes), (int)data.SizeInBytes).CopyTo(new((void*)data.Pointer, (int)data.SizeInBytes)); } Unmap(); @@ -32,7 +49,7 @@ public void Upload(ReadOnlySpan data, uint offsetInBytes) where T : unmana { CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); - commandBuffer.Upload(this, offsetInBytes, data); + commandBuffer.Download(this, offsetInBytes, data); commandBuffer.Submit(true); } } diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs deleted file mode 100644 index c2882bf7..00000000 --- a/sources/Zenith.NET/BufferView.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : GraphicsResource(context), IBindableResource -{ - private BufferViewDesc desc = desc; - - public ref readonly BufferViewDesc Desc => ref desc; -} diff --git a/sources/Zenith.NET/ClearValues.cs b/sources/Zenith.NET/ClearValues.cs deleted file mode 100644 index 60a576d3..00000000 --- a/sources/Zenith.NET/ClearValues.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Numerics; - -namespace Zenith.NET; - -public static class ClearValues -{ - public static readonly ClearValue Default = new() - { - ColorValues = [.. Enumerable.Repeat(new(0, 0, 0, 1), 8)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }; - - public static readonly ClearValue ColorOnly = Default with - { - Flags = ClearFlags.Color - }; - - public static readonly ClearValue DepthOnly = Default with - { - Flags = ClearFlags.Depth - }; - - public static readonly ClearValue StencilOnly = Default with - { - Flags = ClearFlags.Stencil - }; - - public static readonly ClearValue None = Default with - { - Flags = ClearFlags.None - }; -} diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index ad18c953..7fadd0f2 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -1,11 +1,8 @@ -using System.Runtime.InteropServices; - -namespace Zenith.NET; +namespace Zenith.NET; public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { private Pipeline? currentPipeline; - private FrameBuffer? currentFrameBuffer; public void Submit(bool waitForCompletion = false) { @@ -17,61 +14,30 @@ public void Submit(bool waitForCompletion = false) } } - public void Upload(Buffer buffer, uint offsetInBytes, ReadOnlySpan data) where T : unmanaged + public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) { - if (data.Length is 0) - { - return; - } + Buffer temporary = Context.Uploader.Buffer(this, data.SizeInBytes); + temporary.Upload(0, data); - ReadOnlySpan byteData = MemoryMarshal.AsBytes(data); - - Buffer temporary = Context.Uploader.Buffer(this, (uint)byteData.Length); - temporary.Upload(byteData, 0); - - CopyBuffer(temporary, 0, buffer, offsetInBytes, (uint)byteData.Length); + CopyBuffer(temporary, 0, buffer, offsetInBytes, data.SizeInBytes); } - public void Upload(Texture texture, TextureSlice slice, TextureOffset offset, TextureExtent extent, ReadOnlySpan data) where T : unmanaged + public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { - if (data.Length is 0) - { - return; - } - - ReadOnlySpan byteData = MemoryMarshal.AsBytes(data); - - uint formatSizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format); - (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(texture.Desc.Format, extent.Width, extent.Height); - - uint sliceRowPitchInBytes = formatSizeInBytes * blocksWide; - uint sliceDepthPitchInBytes = sliceRowPitchInBytes * blocksHigh; - - uint sliceRowPitchAlignInBytes = ZenithHelper.Align(sliceRowPitchInBytes, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchAlignInBytes = ZenithHelper.Align(sliceRowPitchAlignInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); - - TextureExtent sliceExtent = extent with { Depth = 1 }; - - for (uint i = 0; i < extent.Depth; i++) - { - Buffer temporary = Context.Uploader.Buffer(this, sliceDepthPitchAlignInBytes); - - MappedMemory mappedMemory = temporary.Map(); - - unsafe - { - for (uint j = 0; j < blocksHigh; j++) - { - byteData.Slice((int)((sliceDepthPitchInBytes * i) + (sliceRowPitchInBytes * j)), (int)sliceRowPitchInBytes).CopyTo(new((void*)(mappedMemory.Pointer + (sliceRowPitchAlignInBytes * j)), (int)sliceRowPitchInBytes)); - } - } + throw new NotImplementedException(); + } - temporary.Unmap(); + public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + Buffer temporary = Context.Uploader.Buffer(this, data.Layout.SizeInBytes); + temporary.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); - CopyBufferToTexture(temporary, 0, texture, slice, offset, sliceExtent); + CopyBufferToTexture(temporary, 0, data.Layout, texture, subresource, offset, extent); + } - offset.Z++; - } + public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + throw new NotImplementedException(); } public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) @@ -79,24 +45,24 @@ public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint dest CopyBufferImpl(src, srcOffsetInBytes, dest, destOffsetInBytes, sizeInBytes); } - public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent) { - CopyBufferToTextureImpl(src, srcOffsetInBytes, dest, destSlice, destOffset, destExtent); + CopyBufferToTextureImpl(src, srcOffsetInBytes, srcLayout, dest, destSubresource, destOffset, destExtent); } - public void CopyTexture(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent) + public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent) { - CopyTextureImpl(src, srcSlice, srcOffset, dest, destSlice, destOffset, extent); + CopyTextureImpl(src, srcSubresource, srcOffset, dest, destSubresource, destOffset, extent); } - public void CopyTextureToBuffer(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes) + public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout) { - CopyTextureToBufferImpl(src, srcSlice, srcOffset, srcExtent, dest, destOffsetInBytes); + CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dest, destOffsetInBytes, destLayout); } - public void ResolveTexture(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dest, TextureSubresource destSubresource) { - ResolveTextureImpl(src, srcSlice, dest, destSlice); + ResolveTextureImpl(src, srcSubresource, dest, destSubresource); } public BottomLevelAccelerationStructure BuildAccelerationStructure(BottomLevelAccelerationStructureDesc desc) @@ -122,56 +88,58 @@ public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerati accelerationStructure.Refresh(newDesc); } - public void BeginRenderPass(FrameBuffer frameBuffer, ClearValue clearValue, params IEnumerable preprocessResourceTables) + public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { - Scissor[] scissors = new Scissor[Math.Max(frameBuffer.ColorAttachmentCount, 1)]; - Viewport[] viewports = new Viewport[Math.Max(frameBuffer.ColorAttachmentCount, 1)]; - - Array.Fill(scissors, new() { Width = frameBuffer.Width, Height = frameBuffer.Height }); - Array.Fill(viewports, new() { Width = frameBuffer.Width, Height = frameBuffer.Height, MaxDepth = 1 }); + Span scissors = stackalloc Scissor[8]; + Span viewports = stackalloc Viewport[8]; - SetScissorsImpl(scissors); - SetViewportsImpl(viewports); - - foreach (ResourceTable resourceTable in preprocessResourceTables) + if (colorAttachments.Length > 0) { - resourceTable.Preprocess(this); + for (int i = 0; i < colorAttachments.Length; i++) + { + ZenithHelper.MipDimensions(colorAttachments[i].Texture.Desc.Width, + colorAttachments[i].Texture.Desc.Height, + 0, + colorAttachments[i].Subresource.MipLevel, + out uint width, + out uint height, + out _); + + scissors[i] = new() { Width = width, Height = height }; + viewports[i] = new() { Width = width, Height = height, MaxDepth = 1.0f }; + } + } + else if (depthStencilAttachment is { } attachment) + { + ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, + attachment.Texture.Desc.Height, + 0, + attachment.Subresource.MipLevel, + out uint width, + out uint height, + out _); + + scissors[0] = new() { Width = width, Height = height }; + viewports[0] = new() { Width = width, Height = height, MaxDepth = 1.0f }; } - BeginRenderPassImpl(frameBuffer, clearValue); - - currentFrameBuffer = frameBuffer; + SetScissorsImpl(scissors); + SetViewportsImpl(viewports); + BeginRenderPassImpl(colorAttachments, depthStencilAttachment); } public void EndRenderPass() { - if (currentFrameBuffer is null) - { - return; - } - - EndRenderPassImpl(currentFrameBuffer); - - currentFrameBuffer = null; + EndRenderPassImpl(); } - public void SetScissors(Scissor[] scissors) + public void SetScissors(ReadOnlySpan scissors) { - if (scissors.Length is 0) - { - return; - } - SetScissorsImpl(scissors); } - public void SetViewports(Viewport[] viewports) + public void SetViewports(ReadOnlySpan viewports) { - if (viewports.Length is 0) - { - return; - } - SetViewportsImpl(viewports); } @@ -206,14 +174,14 @@ public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint index) SetVertexBufferImpl(pipeline, buffer, offsetInBytes, index); } - public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat format) + public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { if (currentPipeline is not GraphicsPipeline pipeline) { return; } - SetIndexBufferImpl(pipeline, buffer, offsetInBytes, format); + SetIndexBufferImpl(pipeline, buffer, offsetInBytes, indexFormat); } public void PushResourceTable(ResourceTable resourceTable) @@ -224,11 +192,6 @@ public void PushResourceTable(ResourceTable resourceTable) } PushResourceTableImpl(currentPipeline, resourceTable); - - if (currentFrameBuffer is null) - { - resourceTable.Preprocess(this); - } } public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) @@ -383,7 +346,6 @@ internal void Reset() Context.Uploader.Release(this); currentPipeline = null; - currentFrameBuffer = null; } protected override void Destroy() @@ -391,18 +353,17 @@ protected override void Destroy() Context.Uploader.Release(this); currentPipeline = null; - currentFrameBuffer = null; } protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); - protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent); + protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); - protected abstract void CopyTextureImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent); + protected abstract void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent); - protected abstract void CopyTextureToBufferImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes); + protected abstract void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout); - protected abstract void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice); + protected abstract void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dest, TextureSubresource destSubresource); protected abstract BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc); @@ -410,13 +371,13 @@ protected override void Destroy() protected abstract void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc); - protected abstract void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue); + protected abstract void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); - protected abstract void EndRenderPassImpl(FrameBuffer frameBuffer); + protected abstract void EndRenderPassImpl(); - protected abstract void SetScissorsImpl(Scissor[] scissors); + protected abstract void SetScissorsImpl(ReadOnlySpan scissors); - protected abstract void SetViewportsImpl(Viewport[] viewports); + protected abstract void SetViewportsImpl(ReadOnlySpan viewports); protected abstract void SetPipelineImpl(GraphicsPipeline pipeline); @@ -426,7 +387,7 @@ protected override void Destroy() protected abstract void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index); - protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format); + protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); protected abstract void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable); diff --git a/sources/Zenith.NET/Enums/ClearFlags.cs b/sources/Zenith.NET/Enums/ClearFlags.cs deleted file mode 100644 index 4192b04d..00000000 --- a/sources/Zenith.NET/Enums/ClearFlags.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum ClearFlags -{ - None = 0, - - Color = 1 << 0, - - Depth = 1 << 1, - - Stencil = 1 << 2, - - All = Color | Depth | Stencil -} diff --git a/sources/Zenith.NET/Enums/LoadOp.cs b/sources/Zenith.NET/Enums/LoadOp.cs new file mode 100644 index 00000000..ccf14d19 --- /dev/null +++ b/sources/Zenith.NET/Enums/LoadOp.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public enum LoadOp +{ + Load, + + Clear, + + DontCare +} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/NativeObjectType.cs b/sources/Zenith.NET/Enums/NativeObjectType.cs new file mode 100644 index 00000000..1872e9d6 --- /dev/null +++ b/sources/Zenith.NET/Enums/NativeObjectType.cs @@ -0,0 +1,5 @@ +namespace Zenith.NET; + +public enum NativeObjectType +{ +} diff --git a/sources/Zenith.NET/Enums/StoreOp.cs b/sources/Zenith.NET/Enums/StoreOp.cs new file mode 100644 index 00000000..55a8eb45 --- /dev/null +++ b/sources/Zenith.NET/Enums/StoreOp.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public enum StoreOp +{ + Store, + + DontCare +} \ No newline at end of file diff --git a/sources/Zenith.NET/FrameBuffer.cs b/sources/Zenith.NET/FrameBuffer.cs deleted file mode 100644 index cda69a71..00000000 --- a/sources/Zenith.NET/FrameBuffer.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Zenith.NET; - -public abstract class FrameBuffer(GraphicsContext context, FrameBufferDesc desc) : GraphicsResource(context) -{ - private FrameBufferDesc desc = desc; - - public ref readonly FrameBufferDesc Desc => ref desc; - - public abstract uint ColorAttachmentCount { get; } - - public abstract bool HasDepthStencilAttachment { get; } - - public abstract uint Width { get; } - - public abstract uint Height { get; } - - public abstract Output Output { get; } -} diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 86289626..22118810 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -1,13 +1,9 @@ namespace Zenith.NET; -public abstract class GraphicsContext : DisposableObject +public abstract class GraphicsContext : DisposableObject, INativeObject { public const uint ConstantBufferAlignment = 256; - public const uint TextureRowPitchAlignment = 256; - - public const uint TextureDepthPitchAlignment = 512; - protected GraphicsContext(Backend backend, bool useValidationLayer) { Backend = backend; @@ -51,13 +47,6 @@ public SwapChain CreateSwapChain(SwapChainDesc desc) return CreateSwapChainImpl(desc); } - public FrameBuffer CreateFrameBuffer(FrameBufferDesc desc) - { - ValidationLayer?.ValidateDesc(desc); - - return CreateFrameBufferImpl(desc); - } - public Shader CreateShader(ShaderDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -72,13 +61,6 @@ public Buffer CreateBuffer(BufferDesc desc) return CreateBufferImpl(desc); } - public BufferView CreateBufferView(BufferViewDesc desc) - { - ValidationLayer?.ValidateDesc(desc); - - return CreateBufferViewImpl(desc); - } - public Texture CreateTexture(TextureDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -135,6 +117,8 @@ public QueryHeap CreateQueryHeap(QueryHeapDesc desc) return CreateQueryHeapImpl(desc); } + public abstract nint GetNativeObject(NativeObjectType type); + protected override void Destroy() { Graphics.Dispose(); @@ -154,14 +138,10 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract SwapChain CreateSwapChainImpl(SwapChainDesc desc); - protected abstract FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc); - protected abstract Shader CreateShaderImpl(ShaderDesc desc); protected abstract Buffer CreateBufferImpl(BufferDesc desc); - protected abstract BufferView CreateBufferViewImpl(BufferViewDesc desc); - protected abstract Texture CreateTextureImpl(TextureDesc desc); protected abstract TextureView CreateTextureViewImpl(TextureViewDesc desc); diff --git a/sources/Zenith.NET/GraphicsResource.cs b/sources/Zenith.NET/GraphicsResource.cs index 8dcac064..5590d806 100644 --- a/sources/Zenith.NET/GraphicsResource.cs +++ b/sources/Zenith.NET/GraphicsResource.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public abstract class GraphicsResource(GraphicsContext context) : DisposableObject +public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject { public string Name { @@ -21,5 +21,7 @@ public string Name protected GraphicsContext Context => context; + public abstract nint GetNativeObject(NativeObjectType type); + protected abstract void SetResourceName(string name); } diff --git a/sources/Zenith.NET/Interfaces/IBindableResource.cs b/sources/Zenith.NET/Interfaces/IBindableResource.cs deleted file mode 100644 index 97c7ce20..00000000 --- a/sources/Zenith.NET/Interfaces/IBindableResource.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Zenith.NET; - -public interface IBindableResource : IDisposableObject; diff --git a/sources/Zenith.NET/Interfaces/INativeObject.cs b/sources/Zenith.NET/Interfaces/INativeObject.cs new file mode 100644 index 00000000..8f223fec --- /dev/null +++ b/sources/Zenith.NET/Interfaces/INativeObject.cs @@ -0,0 +1,6 @@ +namespace Zenith.NET; + +public interface INativeObject +{ + nint GetNativeObject(NativeObjectType type); +} diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs index 5b89d401..a817ff0c 100644 --- a/sources/Zenith.NET/ResourceTable.cs +++ b/sources/Zenith.NET/ResourceTable.cs @@ -6,76 +6,27 @@ public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc d public ref readonly ResourceTableDesc Desc => ref desc; - public void Write(uint binding, params IBindableResource[] resources) - { - if (binding >= desc.Bindings.Length || resources.Length is 0 || resources.Length > desc.Bindings[binding].Count) - { - return; - } + public abstract void Write(uint binding, Buffer buffer); - foreach (IBindableResource resource in resources) - { - if (resource?.IsDisposed is not false) - { - return; - } - } + public abstract void Write(uint binding, BufferRange bufferRange); - switch (desc.Bindings[binding].Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - foreach (IBindableResource resource in resources) - { - if (resource is not Buffer and not BufferView) - { - return; - } - } - break; + public abstract void Write(uint binding, Texture texture); - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - foreach (IBindableResource resource in resources) - { - if (resource is not Texture and not TextureView) - { - return; - } - } - break; + public abstract void Write(uint binding, TextureView textureView); - case ResourceType.Sampler: - foreach (IBindableResource resource in resources) - { - if (resource is not Sampler) - { - return; - } - } - break; + public abstract void Write(uint binding, Sampler sampler); - case ResourceType.AccelerationStructure: - foreach (IBindableResource resource in resources) - { - if (resource is not TopLevelAccelerationStructure) - { - return; - } - } - break; - } + public abstract void Write(uint binding, TopLevelAccelerationStructure topLevelAccelerationStructure); - WriteImpl(binding, resources); - } + public abstract void Write(uint binding, ReadOnlySpan buffers); - internal void Preprocess(CommandBuffer commandBuffer) - { - PreprocessImpl(commandBuffer); - } + public abstract void Write(uint binding, ReadOnlySpan bufferRanges); - protected abstract void WriteImpl(uint binding, IBindableResource[] resources); + public abstract void Write(uint binding, ReadOnlySpan textures); - protected abstract void PreprocessImpl(CommandBuffer commandBuffer); + public abstract void Write(uint binding, ReadOnlySpan textureViews); + + public abstract void Write(uint binding, ReadOnlySpan samplers); + + public abstract void Write(uint binding, ReadOnlySpan topLevelAccelerationStructures); } diff --git a/sources/Zenith.NET/Sampler.cs b/sources/Zenith.NET/Sampler.cs index 7569c729..7c699dcd 100644 --- a/sources/Zenith.NET/Sampler.cs +++ b/sources/Zenith.NET/Sampler.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context), IBindableResource +public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) { private SamplerDesc desc = desc; diff --git a/sources/Zenith.NET/Structs/BufferData.cs b/sources/Zenith.NET/Structs/BufferData.cs new file mode 100644 index 00000000..91f2d623 --- /dev/null +++ b/sources/Zenith.NET/Structs/BufferData.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public record struct BufferData +{ + public nint Pointer; + + public uint SizeInBytes; +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Zenith.NET/Structs/BufferRange.cs similarity index 80% rename from sources/Zenith.NET/Structs/BufferViewDesc.cs rename to sources/Zenith.NET/Structs/BufferRange.cs index 472c771a..84cdc030 100644 --- a/sources/Zenith.NET/Structs/BufferViewDesc.cs +++ b/sources/Zenith.NET/Structs/BufferRange.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BufferViewDesc +public record struct BufferRange { public Buffer Buffer; diff --git a/sources/Zenith.NET/Structs/ClearValue.cs b/sources/Zenith.NET/Structs/ClearValue.cs deleted file mode 100644 index a078c3e2..00000000 --- a/sources/Zenith.NET/Structs/ClearValue.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Numerics; - -namespace Zenith.NET; - -public record struct ClearValue -{ - public Vector4[] ColorValues; - - public float Depth; - - public byte Stencil; - - public ClearFlags Flags; -} diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs new file mode 100644 index 00000000..8a690199 --- /dev/null +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -0,0 +1,20 @@ +using System.Numerics; + +namespace Zenith.NET; + +public record struct ColorAttachment +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public Texture? ResolveTexture; + + public TextureSubresource ResolveSubresource; + + public LoadOp LoadOp; + + public StoreOp StoreOp; + + public Vector4 ClearColor; +} diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs new file mode 100644 index 00000000..b0a9bf19 --- /dev/null +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -0,0 +1,20 @@ +namespace Zenith.NET; + +public record struct DepthStencilAttachment +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public LoadOp DepthLoadOp; + + public StoreOp DepthStoreOp; + + public LoadOp StencilLoadOp; + + public StoreOp StencilStoreOp; + + public float ClearDepth; + + public byte ClearStencil; +} diff --git a/sources/Zenith.NET/Structs/TextureExtent.cs b/sources/Zenith.NET/Structs/Extent3D.cs similarity index 74% rename from sources/Zenith.NET/Structs/TextureExtent.cs rename to sources/Zenith.NET/Structs/Extent3D.cs index 2ac2620a..ebc8e55a 100644 --- a/sources/Zenith.NET/Structs/TextureExtent.cs +++ b/sources/Zenith.NET/Structs/Extent3D.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureExtent +public record struct Extent3D { public uint Width; diff --git a/sources/Zenith.NET/Structs/FrameBufferAttachment.cs b/sources/Zenith.NET/Structs/FrameBufferAttachment.cs deleted file mode 100644 index b92e8a16..00000000 --- a/sources/Zenith.NET/Structs/FrameBufferAttachment.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public record struct FrameBufferAttachment -{ - public Texture Target; - - public TextureSlice Slice; -} diff --git a/sources/Zenith.NET/Structs/FrameBufferDesc.cs b/sources/Zenith.NET/Structs/FrameBufferDesc.cs deleted file mode 100644 index f4b35cd0..00000000 --- a/sources/Zenith.NET/Structs/FrameBufferDesc.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public record struct FrameBufferDesc -{ - public FrameBufferAttachment[] ColorAttachments; - - public FrameBufferAttachment? DepthStencilAttachment; -} diff --git a/sources/Zenith.NET/Structs/TextureOffset.cs b/sources/Zenith.NET/Structs/Offset3D.cs similarity index 70% rename from sources/Zenith.NET/Structs/TextureOffset.cs rename to sources/Zenith.NET/Structs/Offset3D.cs index d1c1640e..bf1c8b4a 100644 --- a/sources/Zenith.NET/Structs/TextureOffset.cs +++ b/sources/Zenith.NET/Structs/Offset3D.cs @@ -1,10 +1,10 @@ namespace Zenith.NET; -public record struct TextureOffset +public record struct Offset3D { public uint X; public uint Y; public uint Z; -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/TextureData.cs b/sources/Zenith.NET/Structs/TextureData.cs new file mode 100644 index 00000000..6d74d0fa --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureData.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public record struct TextureData +{ + public nint Pointer; + + public TextureDataLayout Layout; +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/TextureDataLayout.cs b/sources/Zenith.NET/Structs/TextureDataLayout.cs new file mode 100644 index 00000000..6bf72b7f --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureDataLayout.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public record struct TextureDataLayout +{ + public uint SizeInBytes; + + public uint RowPitchInBytes; + + public uint SlicePitchInBytes; +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/TextureSlice.cs b/sources/Zenith.NET/Structs/TextureSubresource.cs similarity index 59% rename from sources/Zenith.NET/Structs/TextureSlice.cs rename to sources/Zenith.NET/Structs/TextureSubresource.cs index f1d62cd5..d82cd079 100644 --- a/sources/Zenith.NET/Structs/TextureSlice.cs +++ b/sources/Zenith.NET/Structs/TextureSubresource.cs @@ -1,10 +1,8 @@ namespace Zenith.NET; -public record struct TextureSlice +public record struct TextureSubresource { public uint MipLevel; public uint ArrayLayer; - - public uint Face; } diff --git a/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs b/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs new file mode 100644 index 00000000..a0f00926 --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public record struct TextureSubresourceLayers +{ + public uint MipLevel; + + public uint BaseArrayLayer; + + public uint LayerCount; +} diff --git a/sources/Zenith.NET/Structs/TextureSubresourceRange.cs b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs new file mode 100644 index 00000000..542e8bf6 --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs @@ -0,0 +1,12 @@ +namespace Zenith.NET; + +public record struct TextureSubresourceRange +{ + public uint BaseMipLevel; + + public uint LevelCount; + + public uint BaseArrayLayer; + + public uint LayerCount; +} diff --git a/sources/Zenith.NET/Structs/TextureViewDesc.cs b/sources/Zenith.NET/Structs/TextureViewDesc.cs index 187a3a69..2455e653 100644 --- a/sources/Zenith.NET/Structs/TextureViewDesc.cs +++ b/sources/Zenith.NET/Structs/TextureViewDesc.cs @@ -4,11 +4,9 @@ public record struct TextureViewDesc { public Texture Texture; - public uint FirstMipLevel; + public TextureType Type; - public uint MipLevelCount; + public PixelFormat Format; - public uint FirstArrayLayer; - - public uint ArrayLayerCount; + public TextureSubresourceRange Range; } diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index f505168c..3af2e8ee 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -6,7 +6,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public ref readonly SwapChainDesc Desc => ref desc; - public abstract FrameBuffer FrameBuffer { get; } + public abstract Texture CurrentColorTarget { get; } + + public abstract Texture? CurrentDepthStencilTarget { get; } public abstract void Present(); diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index b0debb5e..f42b8c0a 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -1,21 +1,24 @@ namespace Zenith.NET; -public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context), IBindableResource +public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) { private TextureDesc desc = desc; public ref readonly TextureDesc Desc => ref desc; - public void Upload(ReadOnlySpan data, TextureSlice slice, TextureOffset offset, TextureExtent extent) where T : unmanaged + public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - if (data.Length is 0) - { - return; - } + CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + commandBuffer.Upload(this, subresource, offset, extent, data); + commandBuffer.Submit(true); + } + + public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); - commandBuffer.Upload(this, slice, offset, extent, data); + commandBuffer.Download(this, subresource, offset, extent, data); commandBuffer.Submit(true); } } diff --git a/sources/Zenith.NET/TextureView.cs b/sources/Zenith.NET/TextureView.cs index 0bd089ff..b8137c72 100644 --- a/sources/Zenith.NET/TextureView.cs +++ b/sources/Zenith.NET/TextureView.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) : GraphicsResource(context), IBindableResource +public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) : GraphicsResource(context) { private TextureViewDesc desc = desc; diff --git a/sources/Zenith.NET/TopLevelAccelerationStructure.cs b/sources/Zenith.NET/TopLevelAccelerationStructure.cs index 8efa2898..4c9c53ca 100644 --- a/sources/Zenith.NET/TopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET/TopLevelAccelerationStructure.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public abstract class TopLevelAccelerationStructure(GraphicsContext context, TopLevelAccelerationStructureDesc desc) : GraphicsResource(context), IBindableResource +public abstract class TopLevelAccelerationStructure(GraphicsContext context, TopLevelAccelerationStructureDesc desc) : GraphicsResource(context) { private TopLevelAccelerationStructureDesc desc = desc; diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 82747a43..fc160549 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -99,63 +99,6 @@ internal void ValidateDesc(SwapChainDesc desc) } } - internal void ValidateDesc(FrameBufferDesc desc) - { - if (desc.ColorAttachments is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "FrameBufferDesc.ColorAttachments")); - - return; - } - - for (int i = 0; i < desc.ColorAttachments.Length; i++) - { - CheckFrameBufferAttachment($"FrameBufferDesc.ColorAttachments[{i}]", desc.ColorAttachments[i]); - } - - if (desc.DepthStencilAttachment is not null) - { - CheckFrameBufferAttachment("FrameBufferDesc.DepthStencilAttachment", desc.DepthStencilAttachment.Value); - } - - if (desc.ColorAttachments.Length is 0 && desc.DepthStencilAttachment is null) - { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.HasNoAttachments, "FrameBufferDesc")); - } - - void CheckFrameBufferAttachment(string name, FrameBufferAttachment frameBufferAttachment) - { - if (frameBufferAttachment.Target is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.Target")); - - return; - } - - if (frameBufferAttachment.Target.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, $"{name}.Target")); - - return; - } - - if (frameBufferAttachment.Slice.MipLevel >= frameBufferAttachment.Target.Desc.MipLevels) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, $"{name}.Slice.MipLevel", "the number of mip levels in the texture")); - } - - if (frameBufferAttachment.Slice.ArrayLayer >= frameBufferAttachment.Target.Desc.ArrayLayers) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, $"{name}.Slice.ArrayLayer", "the number of array layers in the texture")); - } - - if (frameBufferAttachment.Slice.Face >= ValidationConstants.CubeMapFaceCount) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, $"{name}.Slice.Face", ValidationConstants.CubeMapFaceCount)); - } - } - } - internal void ValidateDesc(ShaderDesc desc) { if (desc.ShaderBytes is null || desc.ShaderBytes.Length is 0) @@ -192,38 +135,6 @@ internal void ValidateDesc(BufferDesc desc) } } - internal void ValidateDesc(BufferViewDesc desc) - { - if (desc.Buffer is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "BufferViewDesc.Buffer")); - - return; - } - - if (desc.Buffer.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "BufferViewDesc.Buffer")); - - return; - } - - if (desc.OffsetInBytes >= desc.Buffer.Desc.SizeInBytes) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "BufferViewDesc.OffsetInBytes", "the size of the buffer")); - } - - if (desc.SizeInBytes is 0 || desc.OffsetInBytes + desc.SizeInBytes > desc.Buffer.Desc.SizeInBytes) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "BufferViewDesc.SizeInBytes", "the buffer")); - } - - if (desc.StrideInBytes is 0) - { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "buffer views")); - } - } - internal void ValidateDesc(TextureDesc desc) { if (!Enum.IsDefined(desc.Type)) @@ -251,6 +162,21 @@ internal void ValidateDesc(TextureDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc.ArrayLayers")); } + if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); + } + + if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + } + if (!Enum.IsDefined(desc.SampleCount)) { ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureDesc.SampleCount", desc.SampleCount)); @@ -278,24 +204,60 @@ internal void ValidateDesc(TextureViewDesc desc) return; } - if (desc.FirstMipLevel >= desc.Texture.Desc.MipLevels) + if (!Enum.IsDefined(desc.Type)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.FirstMipLevel", "the number of mip levels in the texture")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureViewDesc.Type", desc.Type)); } - if (desc.MipLevelCount is 0 || desc.FirstMipLevel + desc.MipLevelCount > desc.Texture.Desc.MipLevels) + if (!Enum.IsDefined(desc.Format)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.MipLevelCount", "the texture mip levels")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureViewDesc.Format", desc.Format)); } - if (desc.FirstArrayLayer >= desc.Texture.Desc.ArrayLayers) + if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.FirstArrayLayer", "the number of array layers in the texture")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); } - if (desc.ArrayLayerCount is 0 || desc.FirstArrayLayer + desc.ArrayLayerCount > desc.Texture.Desc.ArrayLayers) + if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.ArrayLayerCount", "the texture array layers")); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); + } + + if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); + } + + if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); + } + + if (desc.Type is TextureType.TextureCube) + { + if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); + } + } + + if (desc.Type is TextureType.TextureCubeArray) + { + if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); + } } } @@ -909,6 +871,12 @@ file static class ValidationMessages public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; + public const string MustBeEqualTo = "{0} must be equal to {1}."; + + public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; + + public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; + public const string MustBeOneOf = "{0} must be one of: {1}."; public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; diff --git a/sources/Zenith.NET/ZenithHelper.cs b/sources/Zenith.NET/ZenithHelper.cs index d72ee11a..d5555351 100644 --- a/sources/Zenith.NET/ZenithHelper.cs +++ b/sources/Zenith.NET/ZenithHelper.cs @@ -31,9 +31,9 @@ public static bool HasStencil(PixelFormat pixelFormat) return pixelFormat is PixelFormat.D24UNormS8UInt or PixelFormat.D32FloatS8UInt; } - public static (uint BlockWidth, uint BlockHeight, uint BlocksWide, uint BlocksHigh) BlockLayout(PixelFormat format, uint width, uint height) + public static (uint BlockWidth, uint BlockHeight, uint BlocksWide, uint BlocksHigh) BlockLayout(PixelFormat pixelFormat, uint width, uint height) { - (uint blockWidth, uint blockHeight) = format switch + (uint blockWidth, uint blockHeight) = pixelFormat switch { PixelFormat.BC4UNorm or PixelFormat.BC4SNorm or @@ -81,9 +81,9 @@ PixelFormat.ASTC12x12SRgb or return (blockWidth, blockHeight, (width + blockWidth - 1) / blockWidth, (height + blockHeight - 1) / blockHeight); } - public static uint SizeInBytes(PixelFormat format) + public static uint SizeInBytes(PixelFormat pixelFormat) { - return format switch + return pixelFormat switch { PixelFormat.R8UNorm or PixelFormat.R8SNorm or @@ -186,16 +186,30 @@ PixelFormat.ASTC12x12SRgb or }; } - public static uint SizeInBytes(PixelFormat format, uint width, uint height) + public static uint SizeInBytes(PixelFormat pixelFormat, uint width, uint height) { - (_, _, uint blocksWide, uint blocksHigh) = BlockLayout(format, width, height); + (_, _, uint blocksWide, uint blocksHigh) = BlockLayout(pixelFormat, width, height); - return blocksWide * blocksHigh * SizeInBytes(format); + return blocksWide * blocksHigh * SizeInBytes(pixelFormat); } - public static uint SizeInBytes(ElementFormat format) + public static uint RowPitchInBytes(PixelFormat pixelFormat, uint width, uint height) { - return format switch + (_, _, uint blocksWide, _) = BlockLayout(pixelFormat, width, height); + + return SizeInBytes(pixelFormat) * blocksWide; + } + + public static uint SlicePitchInBytes(PixelFormat pixelFormat, uint width, uint height) + { + (_, _, _, uint blocksHigh) = BlockLayout(pixelFormat, width, height); + + return RowPitchInBytes(pixelFormat, width, height) * blocksHigh; + } + + public static uint SizeInBytes(ElementFormat elementFormat) + { + return elementFormat switch { ElementFormat.UByte1 or ElementFormat.Byte1 or @@ -245,46 +259,4 @@ ElementFormat.UInt4 or _ => 0 }; } - - public static uint FaceCount(TextureDesc desc) - { - return desc.Type is TextureType.TextureCube or TextureType.TextureCubeArray ? 6u : 1u; - } - - public static uint FaceIndex(TextureDesc desc, TextureSlice slice) - { - return desc.Type is TextureType.TextureCube or TextureType.TextureCubeArray ? slice.Face : 0u; - } - - public static uint FlattenArrayLayerCount(TextureDesc desc) - { - return desc.ArrayLayers * FaceCount(desc); - } - - public static uint FlattenArrayLayerIndex(TextureDesc desc, TextureSlice slice) - { - return (slice.ArrayLayer * FaceCount(desc)) + FaceIndex(desc, slice); - } - - public static (uint FlattenArrayLayerIndex, uint FlattenArrayLayerCount) FlattenArrayLayerRange(TextureViewDesc desc) - { - return (desc.FirstArrayLayer * FaceCount(desc.Texture.Desc), desc.ArrayLayerCount * FaceCount(desc.Texture.Desc)); - } - - public static uint SubresourceCount(TextureDesc desc) - { - return desc.MipLevels * desc.ArrayLayers * FaceCount(desc); - } - - public static uint SubresourceIndex(TextureDesc desc, TextureSlice slice) - { - return (slice.MipLevel * desc.ArrayLayers * FaceCount(desc)) + (slice.ArrayLayer * FaceCount(desc)) + FaceIndex(desc, slice); - } - - public static uint SubresourceSizeInBytes(TextureDesc desc, TextureSlice slice) - { - MipDimensions(desc.Width, desc.Height, desc.Depth, slice.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); - - return SizeInBytes(desc.Format, mipWidth, mipHeight) * mipDepth; - } } From b0612f9b8a6f01c3e0a5f2fe1c45bdb662d86dd1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 24 Apr 2026 20:23:39 +0800 Subject: [PATCH 036/466] Update RHI redesign drafts: API rewrite, sync EN/zh versions Comprehensively rewrite and reorganize both English and Chinese RHI redesign drafts to match the current Zenith.NET, Extensions, and Views codebases. Key changes include clarified conventions, explicit resource object lifetimes, descriptor-based CPU data paths, flat resource binding, inline render pass handling, explicit texture transitions, timeline-based synchronization, and a completed NativeObjectType enum. Appendices updated to reflect new API flows. Both language versions are now fully synchronized. --- rhi-redesign.draft.md | 775 ++++++++++++++++++++------------------- rhi-redesign.zh.draft.md | 775 ++++++++++++++++++++------------------- 2 files changed, 805 insertions(+), 745 deletions(-) diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md index e0b75ffe..5473298d 100644 --- a/rhi-redesign.draft.md +++ b/rhi-redesign.draft.md @@ -1,31 +1,58 @@ -# RHI Redesign — Requirements Specification +# RHI Redesign — Working Draft -> Working spec for the redesigned public surface. Not for commit. Mirrors `rhi-redesign.zh.draft.md`. -> The document is organized as nine numbered requirements; shared types live in §0. +> Working spec for the next public surface. Not for commit. Mirrors `rhi-redesign.zh.draft.md`. +> Rebuilt against the current `Zenith.NET`, `Extensions`, and `Views` folders. Validation-layer design is intentionally deferred. +> The document stays organized as nine numbered requirements; shared conventions live in §0. ## 0. Common Conventions ### 0.1 Naming and Code Style - Every public value type is a `record struct` with public mutable fields. -- Multi-element parameters use `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, no `IEnumerable`. -- All method bodies use `{ ... }`. **Exception:** `ref` / `ref readonly` returning properties keep `=> ref _field;`. -- Every byte-denominated parameter or field carries the `*InBytes` suffix. -- Backend hooks use the `*Core` suffix only when they share a name with a non-`Core` wrapper (e.g. `Wait` / `WaitCore`); otherwise they keep their natural name. -- Every abstract member is plain `protected abstract`; same-assembly callers go through an `internal` wrapper. +- Multi-element inputs use `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, `IEnumerable`, or `IReadOnlyList` on the low-level surface. +- Every byte-denominated field or parameter uses the `*InBytes` suffix. +- Backend hooks use plain `protected abstract` members; wrappers above them are only for validation or convenience. +- Public convenience wrappers may remain when they do not obscure the low-level model, for example `Submit(bool waitForCompletion = false)` on top of the timeline submit path. ### 0.2 Public Surface Boundaries -- No global-memory-barrier shape beyond the resource-less `Barrier(...)` form. -- No residency primitives (`MakeResident` / `Evict`) on the public surface. -- No long-lived `View` objects; view information is a call-site value. -- No long-lived `FrameBuffer` object. -- The only public synchronization result type is `CommandSubmission`. -- Texture image-layout (DX12 / VK) is fully internal to the backend; it is computed from `BarrierAccess` plus RenderPass attachment metadata. +- `BufferView` stays removed. Buffer subranges are expressed as `BufferRange`. +- `TextureView` stays a long-lived `GraphicsResource` created by `GraphicsContext.CreateTextureView(...)`. +- `ResourceTable` stays a long-lived `GraphicsResource` with explicit typed `Write(...)` overloads. +- There is no public `FrameBuffer` / `RenderPassInfo` object. Render passes are inline. +- CPU upload and readback are described with `BufferData`, `TextureData`, and `TextureDataLayout`. +- The view layer renders into a `Texture target`; the platform view decides whether that target is a swapchain backbuffer or a CPU-readback texture. +- This round only adds the missing low-level synchronization pieces: explicit texture layout transitions, a lightweight `PipelineBarrier` for read-write hazards, queue timelines, and a completed `NativeObjectType`. +- Residency, heap management, and validation-layer redesign remain out of scope. -### 0.3 Resource Handle Bases +### 0.3 Core Object Skeleton ```csharp +public abstract class GraphicsContext : DisposableObject, INativeObject +{ + public Backend Backend { get; } + + public Capabilities Capabilities { get; } + + public CommandQueue Graphics { get; } + + public CommandQueue Compute { get; } + + public CommandQueue Copy { get; } + + public SwapChain CreateSwapChain(SwapChainDesc desc); + + public Buffer CreateBuffer(BufferDesc desc); + + public Texture CreateTexture(TextureDesc desc); + + public TextureView CreateTextureView(TextureViewDesc desc); + + public ResourceTable CreateResourceTable(ResourceTableDesc desc); + + public abstract nint GetNativeObject(NativeObjectType type); +} + public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject { public GraphicsContext Context { get; } = context; @@ -35,190 +62,122 @@ public abstract class GraphicsResource(GraphicsContext context) : DisposableObje public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) { - private BufferDesc desc = desc; - public ref readonly BufferDesc Desc => ref desc; - /// - /// Maps the buffer into a CPU-visible region. Only valid when Desc.Flags contains - /// or ; - /// must be paired with . - /// public abstract MappedMemory Map(); public abstract void Unmap(); + + public void Upload(uint offsetInBytes, BufferData data); + + public void Download(uint offsetInBytes, BufferData data); } public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) { - private TextureDesc desc = desc; - public ref readonly TextureDesc Desc => ref desc; - public static implicit operator TextureSubresourceRange(Texture texture) - { - return new() - { - BaseMipLevel = 0, - LevelCount = texture.Desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = texture.Desc.ArrayLayers - }; - } -} - -public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) -{ - private SamplerDesc desc = desc; + public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - public ref readonly SamplerDesc Desc => ref desc; + public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); } ``` -### 0.4 BufferDesc / Capability Flags - -Follows the existing framework design; no `MemoryAccess` enum is introduced. CPU visibility is expressed within the capability set via `BufferUsageFlags.MapRead` / `MapWrite`. +### 0.4 Explicit CPU Data Descriptors ```csharp -[Flags] -public enum BufferUsageFlags +public record struct BufferData { - None = 0, - Vertex = 1 << 0, - Index = 1 << 1, - Indirect = 1 << 2, - AccelerationStructure = 1 << 3, - Constant = 1 << 4, - ShaderResource = 1 << 5, - UnorderedAccess = 1 << 6, - MapRead = 1 << 7, - MapWrite = 1 << 8 + public nint Pointer; + + public uint SizeInBytes; } -public record struct BufferDesc +public record struct TextureData +{ + public nint Pointer; + + public TextureDataLayout Layout; +} + +public record struct TextureDataLayout { public uint SizeInBytes; - public uint StrideInBytes; + public uint RowPitchInBytes; - public BufferUsageFlags Flags; + public uint SlicePitchInBytes; } ``` -`BufferUsageFlags` is a **capability set**, immutable for the lifetime of the resource. It is layered against §6 `BarrierAccess`: `Flags` declares "which accesses are permitted", `BarrierAccess` describes "which access is happening right now". A buffer declared `Vertex | ShaderResource` can be bound directly via `SetVertexBuffer` — there is no "type conversion" step; barriers only carry visibility between previous and next access. - -CPU upload paths split two ways: -- A buffer that declares `MapRead` / `MapWrite` is written application-side via paired `Map()` / `Unmap()`. -- Otherwise, writes flow through `CommandBuffer.Upload` / `CopyBuffer` against a staging buffer. +Design points: +- `TextureDataLayout` is a pure layout descriptor. It does not carry offsets. +- Byte offsets stay on copy entry points such as `CopyBufferToTexture(..., uint srcOffsetInBytes, TextureDataLayout srcLayout, ...)`. +- Tight layouts can be computed with `ZenithHelper`, but callers remain responsible for the final `SizeInBytes` / `RowPitchInBytes` / `SlicePitchInBytes` they pass. +- Backend-specific copy alignment remains backend-local; it is not lifted back into `GraphicsContext`. --- -## 1. ReadOnlySpan over Arrays +## 1. ReadOnlySpan at Multi-element Boundaries -Requirement: every multi-element input parameter on the public API uses `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, `IList`, `IEnumerable`, or `IReadOnlyList` appears in public signatures. +Requirement: every multi-element public input keeps using `ReadOnlySpan` / `params ReadOnlySpan`. -Rationale: -- Pairs naturally with .NET 10's `params ReadOnlySpan` and collection expressions `[ ... ]`, giving zero-allocation call sites. -- The same signature accepts stack-allocated, array-backed, or single-element inputs uniformly. -- Backends can copy directly into native arrays without enumerating. +Representative surfaces: -Surfaces (non-exhaustive): +| Surface | Shape | +|---|---| +| Render pass color attachments | `ReadOnlySpan` | +| Viewports / scissors | `ReadOnlySpan` / `ReadOnlySpan` | +| ResourceTable array writes | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | +| Timeline waits | `params ReadOnlySpan` | +| Batch transitions | `ReadOnlySpan` | -| Parameter | Old | New | -|---|---|---| -| Submit waits | `Fence[]` | `ReadOnlySpan` | -| Vertex buffer slots | `Buffer[]` + `ulong[]` | `ReadOnlySpan` + `ReadOnlySpan` | -| Viewports / scissors | `Viewport[]` | `ReadOnlySpan` | -| RenderPass color attachments | `ColorAttachment[]` | `ReadOnlySpan` | -| ResourceTable array bindings | `IBindableResource[]` | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | -| Upload data | `byte[]` + offset/size | `ReadOnlySpan where T : unmanaged` | +Rationale: +- Accepts stack, array, and single-element inputs uniformly. +- Matches the current `BeginRenderPass`, `SetScissors`, `SetViewports`, and `ResourceTable.Write` family. +- Keeps the future timeline and transition APIs allocation-free at call sites. -Exception: returning multiple elements as `ReadOnlySpan` requires a backing owner that is more expensive than the convenience is worth. Single-element returns keep their concrete type; bulk returns keep `T[]` or expose indexed accessors. +Exception: +- Returning multiple elements as `ReadOnlySpan` still implies an owner. Concrete return types remain acceptable when they avoid hidden lifetime coupling. --- -## 2. Cross-queue Synchronization +## 2. Explicit Upload / Download / Copy Model -Requirement: cross-queue dependencies are expressed through a single value type `CommandSubmission`, modelled after the timeline primitive in all three backends. +Requirement: CPU data movement remains descriptor-based. Public texture upload does not move back to `ReadOnlySpan`-shaped APIs. ```csharp -public enum CommandQueueType -{ - Graphics, - Compute, - Copy -} - -/// -/// One point on a queue's completion timeline, produced by Submit / Present. -/// default is the well-defined empty value: backends filter it out of any waits, -/// and is a no-op on it. -/// -public readonly struct CommandSubmission(CommandQueue? queue, ulong value) -{ - public CommandQueue? Queue = queue; - - public ulong Value = value; - - public void Wait() - { - Queue?.Wait(Value); - } -} - -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { - public CommandQueueType Type { get; } = type; + public void Upload(Buffer buffer, uint offsetInBytes, BufferData data); - /// Acquires a recording-ready CommandBuffer, recycling a retired one when available. - public CommandBuffer CommandBuffer(); + public void Download(Buffer buffer, uint offsetInBytes, BufferData data); - /// Waits until every submission issued on this queue has completed. Idempotent. - public void WaitForIdle(); + public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - /// Waits until this queue's timeline reaches . - public void Wait(ulong value); - - protected abstract ulong GetCompletedValue(); - - protected abstract void WaitCore(ulong value); + public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - protected abstract void SubmitCore(CommandBuffer commandBuffer, - ReadOnlySpan waits, - ulong signalValue); + public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); - protected abstract CommandBuffer CreateCommandBuffer(); -} + public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public CommandQueue Queue { get; } = queue; + public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent); - /// Closes recording and enqueues this buffer on its owning queue. - public CommandSubmission Submit(params ReadOnlySpan waits); + public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout); } ``` Design points: -- Each queue owns one monotonic completion timeline. Each `Submit` advances `Value` and exposes the new point as `CommandSubmission(queue, value)`. -- Cross-queue dependency = pass an upstream `CommandSubmission` into a downstream `Submit(waits)`. -- `GetCompletedValue()` is a method, not a property: all three backends are call-shaped. -- CommandBuffer pooling is a queue-internal detail; the public surface only offers `CommandBuffer()` + `Submit(...)`. -- `GraphicsContext.Graphics` / `Compute` / `Copy` are the three queues; `Present` always runs on `Graphics`. - -Backend timeline mapping: - -| Backend | Timeline object | API | -|---|---|---| -| Metal 4 | one `MTLSharedEvent` per queue | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` | -| Vulkan 1.4 | one timeline `VkSemaphore` per queue | `vkQueueSubmit2` `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` | -| DX12 | one `ID3D12Fence` per queue | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` | +- `Buffer.Upload` / `Buffer.Download` short-circuit to `Map()` / `Unmap()` when the buffer is CPU-visible; otherwise they stage through `Context.Copy`. +- `Texture.Upload` / `Texture.Download` always go through `CommandBuffer`, which keeps the copy path explicit and backend-controlled. +- Offsets and pitches are intentionally separate. `TextureDataLayout` describes memory layout; `offsetInBytes` chooses where inside the staging or destination buffer the copy starts. +- `Extensions.ImageSharp` and `Views.Avalonia.Surface` already follow this model today. --- -## 3. Subresource / Offset / Range as Value Types +## 3. Subresource, Offset, Extent, and Buffer Range Values -Requirement: replace the legacy `TextureSlice` / `TextureOffset` / `TextureExtent` with `TextureSubresource` / `TextureSubresourceLayers` / `TextureSubresourceRange` / `Offset3D` / `Extent3D`. The triplet expresses single-point / single-mip-multiple-layers / multi-mip-multi-layer ranges respectively. +Requirement: subresource and range inputs remain small value types, aligned with the current code and explicit copy/render surfaces. ```csharp public record struct TextureSubresource @@ -265,27 +224,7 @@ public record struct Extent3D public uint Depth; } -``` - -| Type | Shape | Used by | -|---|---|---| -| `TextureSubresource` | one mip × one layer | RTV / DSV / Resolve endpoints | -| `TextureSubresourceLayers` | one mip × contiguous layer range | Copy / Upload | -| `TextureSubresourceRange` | contiguous mip range × contiguous layer range | Barrier / `TextureView.Range` | -Conventions: -- The public surface does not carry an aspect field; backends derive aspect (color / depth / stencil) from `Texture.Format`. -- Cube faces are addressed as `ArrayLayer = cubeIndex * 6 + face`; there is no separate face axis. -- `Texture` implicitly converts to `TextureSubresourceRange` (full coverage) so common single-resource calls fit on one line. -- Suffix naming follows VK: `Subresource` (point) / `SubresourceLayers` (slab) / `SubresourceRange` (volume). - ---- - -## 4. Drop BufferView, unify on BufferRange - -Requirement: remove the `BufferView` type and any `CreateBufferView` entry point. Buffer sub-ranges are expressed as a call-site `BufferRange` value. - -```csharp public record struct BufferRange { public Buffer Buffer; @@ -309,152 +248,136 @@ public record struct BufferRange } ``` -Rationale: -- Buffers do not need cross-format reinterpretation; offset/size/stride is the full set required to describe a sub-range. -- DX12 `D3D12_*_VIEW_DESC`, VK `VkDescriptorBufferInfo`, Metal `setBuffer:offset:` all take call-site offset/size; a long-lived view object is unnecessary. -- Shader-side interpretation (CBV / structured-SRV / byteaddress-SRV / typed-SRV / UAV) is decided by the slot's `ResourceLayout`, not by the buffer. -- The implicit conversion keeps single-resource calls like `Write(0, vbo)` and `SetVertexBuffer(0, vbo, 0)` on a single line. +Conventions: +- The public surface carries no explicit aspect flag. Backends derive color / depth / stencil from `Texture.Desc.Format`. +- Cube faces remain linearized through `ArrayLayer = cubeIndex * 6 + face`. +- `BufferRange` is the only public buffer subrange shape. No `BufferView` factory comes back. --- -## 5. TextureView Refactor (Format / ViewType) +## 4. TextureView Remains a Long-lived Resource -Requirement: redefine `TextureView` as a call-site value. Drop swizzle (5-tuple → 4-tuple) and add `Format` / `ViewType` for compatible-family reinterpretation and dimensionality switches. +Requirement: keep `TextureView` as an owned resource object. The old "call-site value only" direction is not aligned with the current binding model or extension usage. ```csharp -public enum TextureViewType -{ - Texture1D, - Texture2D, - Texture3D, - TextureCube, - Texture1DArray, - Texture2DArray, - TextureCubeArray -} - -public record struct TextureView +public record struct TextureViewDesc { public Texture Texture; - public TextureSubresourceRange Range; + public TextureType Type; - /// null derives from Texture.Desc + Range. - public TextureViewType? ViewType; + public PixelFormat Format; - /// null uses Texture.Desc.Format; otherwise reinterprets within the same family. - public PixelFormat? Format; + public TextureSubresourceRange Range; +} - public static implicit operator TextureView(Texture texture) - { - return new() - { - Texture = texture, - Range = texture, - ViewType = null, - Format = null - }; - } +public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) : GraphicsResource(context) +{ + public ref readonly TextureViewDesc Desc => ref desc; } ``` -- Mirrors VK `VkImageViewCreateInfo` and Metal `newTextureViewWithPixelFormat:textureType:levels:slices:`. -- Backends lazily cache native views keyed by `(Texture, Range, ViewType, Format)`; that cache is an implementation detail. -- Channel swizzle is intentionally not on the public surface: single-channel → grayscale and similar patterns are handled in shader; BGRA ↔ RGBA is covered by `Format` reinterpretation within the same compatibility family. If a real need surfaces later, an optional `ComponentMapping?` field can be added back without breaking existing call sites. -- `Texture` implicitly converts to `TextureView` (full range, original format, original dimensionality) so common single-resource bindings fit on one line. +Design points: +- `GraphicsContext.CreateTextureView(...)` stays on the public surface. +- Binding a plain `Texture` remains the default whole-resource path. +- Binding a `TextureView` is reserved for explicit subresource, dimensionality, or format reinterpretation. +- This matches the current `ImGui` renderer, which keeps view objects alive and binds them through `ResourceTable`. +- Backends are free to let `Texture` carry a default native view internally, but the explicit `TextureView` object remains the public escape hatch. --- -## 6. Barrier — 1:1 Synchronization Primitive +## 5. Flat Binding Model: ResourceBinding[] + ResourceTable -Requirement: remove every implicit layout transition. All visibility / execution dependency / layout switches are stated by the caller through a `(stage, access)` pair, mirroring Metal 4 / VK 1.4 / DX12 Enhanced Barriers. +Requirement: the binding model remains flat and concrete: `ResourceBinding[]` describes slots, `ResourceTable` holds values, and `Write(...)` stays typed by resource kind. ```csharp -[Flags] -public enum BarrierStage : uint -{ - None = 0, - All = ~0u, - Draw = 1u << 0, - VertexInput = 1u << 1, - VertexShader = 1u << 2, - PixelShader = 1u << 3, - EarlyDepthStencil = 1u << 4, - LateDepthStencil = 1u << 5, - RenderTarget = 1u << 6, - ComputeShader = 1u << 7, - RayTracing = 1u << 8, - Copy = 1u << 9, - Resolve = 1u << 10, - IndirectArgument = 1u << 11, - AccelerationStructureBuild = 1u << 12, -} - -[Flags] -public enum BarrierAccess : uint -{ - None = 0, - VertexBuffer = 1u << 0, - IndexBuffer = 1u << 1, - ConstantBuffer = 1u << 2, - ShaderRead = 1u << 3, - UnorderedAccessRead = 1u << 4, - UnorderedAccessWrite = 1u << 5, - RenderTarget = 1u << 6, - DepthStencilRead = 1u << 7, - DepthStencilWrite = 1u << 8, - CopySource = 1u << 9, - CopyDestination = 1u << 10, - ResolveSource = 1u << 11, - ResolveDestination = 1u << 12, - IndirectArgument = 1u << 13, - Present = 1u << 14, - AccelerationStructureRead = 1u << 15, - AccelerationStructureWrite = 1u << 16, +public enum ResourceType +{ + ConstantBuffer, + StructuredBuffer, + StructuredBufferReadWrite, + Texture, + TextureReadWrite, + Sampler, + AccelerationStructure +} + +public record struct ResourceBinding +{ + public ResourceType Type; + + public uint Count; +} + +public record struct ResourceTableDesc +{ + public ResourceBinding[] Bindings; +} + +public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc desc) : GraphicsResource(context) +{ + public ref readonly ResourceTableDesc Desc => ref desc; + + public abstract void Write(uint binding, Buffer buffer); + + public abstract void Write(uint binding, BufferRange bufferRange); + + public abstract void Write(uint binding, Texture texture); + + public abstract void Write(uint binding, TextureView textureView); + + public abstract void Write(uint binding, Sampler sampler); + + public abstract void Write(uint binding, TopLevelAccelerationStructure topLevelAccelerationStructure); + + public abstract void Write(uint binding, ReadOnlySpan buffers); + + public abstract void Write(uint binding, ReadOnlySpan bufferRanges); + + public abstract void Write(uint binding, ReadOnlySpan textures); + + public abstract void Write(uint binding, ReadOnlySpan textureViews); + + public abstract void Write(uint binding, ReadOnlySpan samplers); + + public abstract void Write(uint binding, ReadOnlySpan topLevelAccelerationStructures); } ``` -The CommandBuffer barrier entry points come in three shapes: +CommandBuffer-side entry points stay simple: ```csharp -public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); +public void SetPipeline(GraphicsPipeline pipeline); + +public void SetPipeline(ComputePipeline pipeline); -public void BufferBarrier(BufferRange range, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); +public void SetPipeline(MeshShadingPipeline pipeline); -public void TextureBarrier(TextureView view, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); +public void PushResourceTable(ResourceTable resourceTable); ``` Design points: -- `BarrierStage` describes **when** in the pipeline an access happens; `BarrierAccess` describes **what kind** of access it is. They mirror Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`, VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`, DX12 `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS`. -- All three forms share the same parameter order: `(afterStages, afterAccess) → (beforeStages, beforeAccess)`. -- Texture image-layout: maintained internally by the backend, derived from `BarrierAccess` plus RenderPass attachment metadata. There is no public layout enum. -- The public surface does **not** offer simplified shortcuts; the caller must supply explicit stage + access. -- Adjacent same-stage / same-resource barriers are folded to no-ops by the backend; the caller is not required to deduplicate. +- There is no separate `ResourceLayout` object. +- There is no `IBindableResource` polymorphic entry. +- `ResourceBindings` on pipeline descriptors and `Bindings` on `ResourceTableDesc` intentionally share the same flat shape. +- `Texture` and `TextureView` both remain first-class write targets because "default whole-resource binding" and "explicit view binding" are both used in current code. +- Record all `Write(...)` operations before `PushResourceTable(...)`; the backend binds the table's current contents against the current pipeline. +- This maps cleanly to DX12 descriptor tables, Vulkan push descriptors, and Metal encoder-side resource table binding. --- -## 7. Drop FrameBuffer — Inline RenderPass + SwapChain Targets +## 6. Inline RenderPass, Output, SwapChain Targets, and View Integration -Requirement: remove `FrameBuffer` / `RenderPassInfo` long-lived objects. RenderPass is expressed inline via `BeginRenderPass` / `EndRenderPass`; `SwapChain` directly exposes `CurrentColorTarget` / `CurrentDepthStencilTarget`, so backbuffers are shaped like ordinary `Texture` objects. +Requirement: render targets stay inline, and the view layer keeps the "render into a `Texture target`" contract. ```csharp -public enum LoadAction +public record struct Output { - Load, - Clear, - DontCare -} + public PixelFormat[] ColorAttachments; -public enum StoreAction -{ - Store, - DontCare, - Resolve + public PixelFormat? DepthStencilAttachment; + + public SampleCount SampleCount; } public record struct ColorAttachment @@ -493,204 +416,310 @@ public record struct DepthStencilAttachment public byte ClearStencil; } -// CommandBuffer -public void BeginRenderPass(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment); - -public void EndRenderPass(); -``` - -```csharp public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) { - private SwapChainDesc desc = desc; - public ref readonly SwapChainDesc Desc => ref desc; - public abstract uint Width { get; } - - public abstract uint Height { get; } - public abstract Texture CurrentColorTarget { get; } public abstract Texture? CurrentDepthStencilTarget { get; } - /// - /// Submits a present on the GraphicsQueue, waiting for ; - /// the returned is the next-backbuffer-writable point. - /// - public abstract CommandSubmission Present(params ReadOnlySpan waits); - public void Resize(uint width, uint height); public void Refresh(Surface surface); } + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); + + public void EndRenderPass(); + + public void SetScissors(ReadOnlySpan scissors); + + public void SetViewports(ReadOnlySpan viewports); +} ``` Design points: -- Backbuffers are exposed as ordinary `Texture` instances (color + optional depth-stencil); they participate in `BeginRenderPass`, `TextureBarrier`, `CopyTexture`, `Write(...)` like any other texture. -- `BeginRenderPass` takes an attachment span; pass `null` explicitly when there is no depth target. The backend defaults `SetViewports` / `SetScissors` from attachment dimensions; the caller can override with a follow-up call. -- `Present(...)` is symmetric with `Submit(...)`: takes waits, returns a `CommandSubmission`. -- Current image index, acquire / present synchronization primitives are backend-internal. +- No `FrameBuffer` object comes back. +- `Output` continues to live on pipeline descriptors and describes attachment compatibility, not a runtime render target object. +- `BeginRenderPass(...)` seeds default scissors / viewports from the attachment size, and callers can override them afterward. +- `SwapChain.CurrentColorTarget` and `CurrentDepthStencilTarget` behave like ordinary `Texture` instances. +- `RenderEventArgs` remains `Texture`-centric: view code passes a target texture into user rendering rather than leaking platform swapchain details into the callback. +- The view layer supports both patterns that already exist in the repo: + - swapchain-backed targets in WinForms / WPF / WinUI-style views + - offscreen target + `Texture.Download(...)` present paths such as Avalonia --- -## 8. ResourceTable Overloads + Drop IBindableResource +## 7. Explicit Texture Layout Transitions + Lightweight PipelineBarrier -Requirement: `ResourceTable.Write` is overloaded by resource type (`BufferRange` / `TextureView` / `Sampler`); the public surface no longer routes through an `IBindableResource` polymorphic entry. +Requirement: add the missing explicit texture layout transition interface and a minimal pipeline barrier for read-write shader hazards. Do not bring back the old full `(stage, access)` matrix. ```csharp -public abstract class ResourceTable(GraphicsContext context, ResourceLayout layout) : GraphicsResource(context) +public enum TextureLayout { - public ResourceLayout Layout { get; } = layout; + Undefined, + Common, + ShaderResource, + UnorderedAccess, + RenderTarget, + DepthStencilRead, + DepthStencilWrite, + CopySource, + CopyDestination, + ResolveSource, + ResolveDestination, + Present +} - public void Write(uint binding, BufferRange range); +public record struct TextureTransition +{ + public Texture Texture; - public void Write(uint binding, TextureView view); + public TextureSubresourceRange Range; - public void Write(uint binding, Sampler sampler); + public TextureLayout Before; - public void Write(uint binding, ReadOnlySpan ranges); + public TextureLayout After; +} - public void Write(uint binding, ReadOnlySpan views); +public enum PipelineBarrierScope +{ + All, + Draw, + Compute, + MeshShading, + RayTracing, + Copy, + AccelerationStructureBuild +} - public void Write(uint binding, ReadOnlySpan samplers); +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public void Transition(Texture texture, TextureSubresourceRange range, TextureLayout before, TextureLayout after); - protected abstract void WriteCore(uint binding, ReadOnlySpan ranges); + public void Transition(TextureView textureView, TextureLayout before, TextureLayout after); - protected abstract void WriteCore(uint binding, ReadOnlySpan views); + public void Transition(ReadOnlySpan transitions); - protected abstract void WriteCore(uint binding, ReadOnlySpan samplers); + public void PipelineBarrier(PipelineBarrierScope before, PipelineBarrierScope after); } ``` -CommandBuffer-side entry points: +Design points: +- `Transition(...)` is the explicit texture state / layout interface. No hidden layout changes remain in copy, sampling, render-pass, or present flows. +- `TextureView` overloads reuse `textureView.Desc.Range`; `textureView.Desc.Format` does not affect layout selection. +- `PipelineBarrier(...)` is the intentionally small UAV-style ordering primitive for cases like "dispatch A writes `RWTexture` / `RWBuffer`, dispatch B touches it again". It does not change layouts and it does not cross queues. +- No public `BufferBarrier` is added in this round. Buffer copy / bind ordering continues to follow command semantics, and explicit user-authored synchronization is reserved for layout transitions and shader read-write hazard isolation. +- Backend mapping is straightforward: + - DX12: resource-state transitions plus UAV barriers + - Vulkan: image memory barriers plus lightweight memory barriers + - Metal: texture usage-state transitions plus encoder memory barriers / fences as needed + +--- + +## 8. Timeline-based Cross-queue Synchronization + +Requirement: add a canonical timeline submission model without removing the current convenience wrappers. ```csharp -public void SetPipeline(Pipeline pipeline); +public enum CommandQueueType +{ + Graphics, + Compute, + Copy +} + +public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) +{ + public bool IsEmpty => Queue is null; + + public void Wait() + { + Queue?.Wait(Value); + } +} + +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +{ + public CommandQueueType Type { get; } = type; + + public CommandBuffer CommandBuffer(); + + public void WaitIdle(); + + public void Wait(ulong value); + + public abstract ulong GetCompletedValue(); +} + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandQueue Queue { get; } = queue; + + public CommandSubmission Submit(params ReadOnlySpan waits); -public void PushResourceTable(ResourceTable table); + public void Submit(bool waitForCompletion = false); +} + +public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) +{ + public abstract CommandSubmission Present(params ReadOnlySpan waits); + + public abstract void Present(); +} ``` Design points: -- The §4 / §5 implicit conversions on `Buffer` / `Texture` keep single-resource bindings on one line: `table.Write(0, vbo)` / `table.Write(1, tex)`. -- Buffer interpretation (CBV / SRV-structured / SRV-byteaddress / UAV / typed) is decided by the slot's `ResourceLayout`, not by the supplied `BufferRange`. -- One table per pipeline (aligned with the Metal 4 argument-table model). -- `PushResourceTable` does not take a stages parameter: stages live on each binding inside the layout. -- **Push-snapshot semantics**: `PushResourceTable` snapshots the table's contents into the command buffer at the call site; later `Write`s on the same `ResourceTable` do not affect already-pushed bindings, so a single `ResourceTable` can be `Write` + `Push`-ed repeatedly within a frame. -- Validation: UAV slots require the corresponding `UnorderedAccess` capability bit; shape / layout / format-family mismatches are reported at `Write` time. - -Why no `IBindableResource`: -- The three resource kinds have entirely different native write paths (descriptor / argument-buffer slot / sampler heap); a polymorphic interface forces a runtime dispatch that overloads avoid at compile time. -- Overloads let the right `Write` route be selected at compile time, with no boxing or runtime type tests. -- Implicit `Buffer → BufferRange` / `Texture → TextureView` conversions already cover "I just want to pass the handle" ergonomics. +- The timeline path is the canonical low-level model: `Submit(waits)` and `Present(waits)` both return a `CommandSubmission`. +- `Submit(bool waitForCompletion = false)` and parameterless `Present()` stay as convenience wrappers for the current one-queue, one-frame-loop usage already present in `Extensions` and `Views`. +- Each queue owns one monotonic completion timeline. +- `default` / empty submissions are ignored in wait sets. +- `Present(waits)` returns the point at which the presented backbuffer becomes writable again. +- Backend mapping follows the natural primitive on each API: + - DX12: one `ID3D12Fence` per queue + - Vulkan: one timeline `VkSemaphore` per queue + - Metal 4: one `MTLSharedEvent` per queue --- -## 9. INativeObject — Unified Native Handle Entry +## 9. Unified Native Handles and a Completed NativeObjectType -Requirement: every public type that wraps a native object implements `INativeObject` and exposes the underlying handle through `GetNativeObject(NativeObjectType)`. The public surface carries no platform-conditional properties. +Requirement: every public object that wraps a native object keeps using `INativeObject`, and `NativeObjectType` is completed around stable native roles rather than backend-specific cast helpers. ```csharp public enum NativeObjectType { - // DirectX 12 + // DirectX 12 / DXGI DxgiFactory, DxgiAdapter, + DxgiSwapChain, D3D12Device, D3D12CommandQueue, + D3D12Fence, D3D12GraphicsCommandList, + D3D12PipelineState, + D3D12RootSignature, D3D12Resource, - D3D12CpuDescriptorHandleSampler, + D3D12DescriptorHeap, D3D12CpuDescriptorHandleRtv, D3D12CpuDescriptorHandleDsv, D3D12CpuDescriptorHandleSrv, D3D12CpuDescriptorHandleUav, + D3D12CpuDescriptorHandleSampler, + D3D12QueryHeap, - // Metal 4 + // Metal / Metal 4 MtlDevice, Mtl4CommandQueue, Mtl4CommandBuffer, + MtlSharedEvent, MtlTexture, MtlBuffer, MtlSamplerState, + MtlRenderPipelineState, + MtlComputePipelineState, + MtlDepthStencilState, + MtlHeap, + MtlAccelerationStructure, + CaMetalLayer, + CaMetalDrawable, // Vulkan VkInstance, VkPhysicalDevice, + VkSurfaceKHR, + VkSwapchainKHR, VkDevice, VkQueue, VkQueueFamilyIndex, + VkSemaphore, VkCommandBuffer, + VkPipeline, + VkPipelineLayout, + VkShaderModule, VkImage, VkImageView, VkBuffer, - VkSampler + VkSampler, + VkDescriptorSet, + VkQueryPool, + VkAccelerationStructureKHR } public interface INativeObject { - /// Returns 0 when the requested type does not match this object or the active backend. + /// Returns 0 when the requested native role does not match this object or the active backend. nint GetNativeObject(NativeObjectType type); } - -public abstract class GraphicsContext : DisposableObject, INativeObject -{ - public abstract nint GetNativeObject(NativeObjectType type); -} - -// GraphicsResource already implements INativeObject in §0.3. ``` Design points: -- Naming follows native API prefixes: DX12 splits into `Dxgi` / `D3D12`; Metal 4 disambiguates from legacy Metal via `Mtl` / `Mtl4`; Vulkan uniformly uses `Vk`. -- `D3D12CpuDescriptorHandle*` is split per view kind to keep the entry point unambiguous. -- Non-handle scalars (e.g. `VkQueueFamilyIndex`) flow through the same entry, packed into `nint`. -- Every `GraphicsResource` subclass implements the interface; subclasses that have nothing to expose return 0 for every requested type. -- Callers are responsible for handling the "returned 0" degraded path; the public surface does not throw. +- The enum is organized around stable native roles that map cleanly to current Zenith object categories. +- One public object may expose several native roles. For example, a DX12 pipeline may expose both `D3D12PipelineState` and `D3D12RootSignature`. +- Several Zenith object kinds may expose the same native role. For example, DX12 buffers, textures, and acceleration structures all surface `D3D12Resource`. +- Non-pointer scalars such as `VkQueueFamilyIndex` still flow through the same `nint` entry point. +- `GetNativeObject(...)` does not add reference counts or retain ownership. Returned handles are only valid while the Zenith object is alive. +- Returning `0` is the only degradation path; unsupported combinations do not throw. --- -## Appendix A: Typical Frame Loop (combines §2 / §6 / §7) +## Appendix A: Current Upload Path ```csharp -CommandSubmission imageReady = default; +CommandBuffer commandBuffer = context.Copy.CommandBuffer(); + +commandBuffer.Upload(texture, + default, + default, + new() { Width = width, Height = height, Depth = 1 }, + new() + { + Pointer = pixels, + Layout = new() + { + SizeInBytes = sizeInBytes, + RowPitchInBytes = rowPitchInBytes, + SlicePitchInBytes = slicePitchInBytes + } + }); + +commandBuffer.Submit(true); +``` -while (running) -{ - CommandBuffer cmd = ctx.Graphics.CommandBuffer(); +This matches the current `ImageSharp` extension and the current offscreen-view presentation path. - ReadOnlySpan colors = - [ - new() - { - Texture = swapChain.CurrentColorTarget, - Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, - LoadAction = LoadAction.Clear, - StoreAction = StoreAction.Store, - ClearColor = new(0, 0, 0, 1) - } - ]; - - DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds - ? new() - { - Texture = ds, - Subresource = new(), - DepthLoadAction = LoadAction.Clear, - DepthStoreAction = StoreAction.DontCare, - ClearDepth = 1.0f - } - : null; +## Appendix B: Current Binding Path - cmd.BeginRenderPass(colors, depth); - // draws - cmd.EndRenderPass(); +```csharp +ResourceBinding[] bindings = +[ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } +]; + +ResourceTable table = context.CreateResourceTable(new() { Bindings = bindings }); +table.Write(0, constants); +table.Write(1, textureView); +table.Write(2, sampler); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.PushResourceTable(table); +``` - CommandSubmission frame = cmd.Submit(imageReady); +This matches the current `ImGui` extension: long-lived `ResourceTable`, explicit `Write(...)`, then `PushResourceTable(...)` under the current pipeline. - imageReady = swapChain.Present(frame); -} +## Appendix C: Timeline-shaped Multi-queue Flow + +```csharp +CommandSubmission copyDone = copyCommandBuffer.Submit(); + +CommandSubmission graphicsDone = graphicsCommandBuffer.Submit(copyDone); + +CommandSubmission presentDone = swapChain.Present(graphicsDone); ``` -Each step has the same shape: take a set of `CommandSubmission` waits, return one `CommandSubmission`. `Submit` and `Present` are symmetric on the public surface. +The existing `Submit(true)` / `Present()` convenience calls remain valid for the simple single-queue path; `CommandSubmission` only becomes necessary once work crosses queues or frames. \ No newline at end of file diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md index 4c252439..a4c07380 100644 --- a/rhi-redesign.zh.draft.md +++ b/rhi-redesign.zh.draft.md @@ -1,31 +1,58 @@ -# RHI 重设计草案 — 需求规范(中文临时版) +# RHI 重设计草案 — 工作稿 -> 仅用于本轮设计讨论,不进入提交。最终英文版同步在 `rhi-redesign.draft.md`。 -> 本文档以 9 条需求为骨架;公共类型与 §0 公共约定共享。 +> 下一版公共面的工作草案,不进入提交。英文同步版见 `rhi-redesign.draft.md`。 +> 本稿依据当前 `Zenith.NET`、`Extensions` 与 `Views` 目录重新整理。验证层设计明确延后处理。 +> 文档仍按 9 条需求组织,共享约定放在 §0。 ## 0. 公共约定 ### 0.1 命名与代码风格 -- 所有公共值类型一律 `record struct`,公共字段可写 -- 多元素入参一律 `ReadOnlySpan` / `params ReadOnlySpan`,不使用 `T[]` / `IEnumerable` -- 方法体一律 `{ ... }`;**例外**:`ref` / `ref readonly` 返回的属性保留 `=> ref _field;` -- 字节单位字段与参数加 `*InBytes` 后缀 -- 抽象后端钩子:仅当与同名非 `Core` 包装共存时才用 `*Core` 后缀(如 `Wait` / `WaitCore`);其余一律自然名 -- 抽象成员一律 `protected abstract`,同程序集内部调用走 `internal` 包装 +- 所有公共值类型一律为 `record struct`,公共字段可写。 +- 多元素输入使用 `ReadOnlySpan` / `params ReadOnlySpan`。低层公共面不使用 `T[]`、`IEnumerable`、`IReadOnlyList`。 +- 所有字节单位字段与参数统一使用 `*InBytes` 后缀。 +- 后端钩子保持普通 `protected abstract` 形式;其上的包装层只负责校验或便利调用。 +- 只要不遮蔽低层模型,就可以保留便利包装,例如时间线提交接口之上的 `Submit(bool waitForCompletion = false)`。 ### 0.2 公共面边界 -- 公共面**无**全局内存屏障(VK pipeline barrier without resource)以外的特殊形态 -- 公共面**无**驻留集(residency / `MakeResident` / `Evict`)相关原语 -- 公共面**无**长生命周期 `View` 对象;view 信息为调用现场值 -- 公共面**无**长生命周期 `FrameBuffer` 对象 -- 公共同步结果只有一种:`CommandSubmission` -- Texture 的 image-layout 完全在后端内部维护,由 `BarrierAccess` + RenderPass attachment 元数据推导 +- `BufferView` 继续移除,buffer 子范围统一使用 `BufferRange` 表达。 +- `TextureView` 继续作为长生命周期 `GraphicsResource`,由 `GraphicsContext.CreateTextureView(...)` 创建。 +- `ResourceTable` 继续作为长生命周期 `GraphicsResource`,通过显式类型化 `Write(...)` 重载写入资源。 +- 公共面没有 `FrameBuffer` / `RenderPassInfo` 对象;RenderPass 保持内联表达。 +- CPU 上传与回读统一由 `BufferData`、`TextureData`、`TextureDataLayout` 描述。 +- Views 层统一向上层暴露 `Texture target`;具体平台 view 自己决定该 target 是 swapchain backbuffer 还是 CPU 回读纹理。 +- 本轮仅补齐缺失的低层同步能力:显式 texture layout 转换、面向读写 hazard 的轻量 `PipelineBarrier`、queue 时间线,以及完整的 `NativeObjectType`。 +- Residency、heap 管理、验证层重设计继续留在范围之外。 -### 0.3 资源句柄基类 +### 0.3 核心对象骨架 ```csharp +public abstract class GraphicsContext : DisposableObject, INativeObject +{ + public Backend Backend { get; } + + public Capabilities Capabilities { get; } + + public CommandQueue Graphics { get; } + + public CommandQueue Compute { get; } + + public CommandQueue Copy { get; } + + public SwapChain CreateSwapChain(SwapChainDesc desc); + + public Buffer CreateBuffer(BufferDesc desc); + + public Texture CreateTexture(TextureDesc desc); + + public TextureView CreateTextureView(TextureViewDesc desc); + + public ResourceTable CreateResourceTable(ResourceTableDesc desc); + + public abstract nint GetNativeObject(NativeObjectType type); +} + public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject { public GraphicsContext Context { get; } = context; @@ -35,188 +62,122 @@ public abstract class GraphicsResource(GraphicsContext context) : DisposableObje public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) { - private BufferDesc desc = desc; - public ref readonly BufferDesc Desc => ref desc; - /// - /// 映射为 CPU 可访问区间。仅在 Desc.Flags - /// 或 时合法,必须与 配对。 - /// public abstract MappedMemory Map(); public abstract void Unmap(); + + public void Upload(uint offsetInBytes, BufferData data); + + public void Download(uint offsetInBytes, BufferData data); } public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) { - private TextureDesc desc = desc; - public ref readonly TextureDesc Desc => ref desc; - public static implicit operator TextureSubresourceRange(Texture texture) - { - return new() - { - BaseMipLevel = 0, - LevelCount = texture.Desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = texture.Desc.ArrayLayers - }; - } -} - -public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : GraphicsResource(context) -{ - private SamplerDesc desc = desc; + public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - public ref readonly SamplerDesc Desc => ref desc; + public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); } ``` -### 0.4 BufferDesc / 能力位 - -沿用现框架设计,不引入 `MemoryAccess` 枚举;CPU 可见性由 `BufferUsageFlags.MapRead` / `MapWrite` 在能力位集合里表达。 +### 0.4 显式 CPU 数据描述 ```csharp -[Flags] -public enum BufferUsageFlags +public record struct BufferData { - None = 0, - Vertex = 1 << 0, - Index = 1 << 1, - Indirect = 1 << 2, - AccelerationStructure = 1 << 3, - Constant = 1 << 4, - ShaderResource = 1 << 5, - UnorderedAccess = 1 << 6, - MapRead = 1 << 7, - MapWrite = 1 << 8 + public nint Pointer; + + public uint SizeInBytes; } -public record struct BufferDesc +public record struct TextureData +{ + public nint Pointer; + + public TextureDataLayout Layout; +} + +public record struct TextureDataLayout { public uint SizeInBytes; - public uint StrideInBytes; + public uint RowPitchInBytes; - public BufferUsageFlags Flags; + public uint SlicePitchInBytes; } ``` -`BufferUsageFlags` 是**能力位集合**,资源生命周期内不可变;它与 §6 的 `BarrierAccess` 是两层:`Flags` 决定"允许的访问集合",`BarrierAccess` 描述"现在以哪种方式访问"。同一块 buffer 同时声明 `Vertex | ShaderResource` 完全合法,用作顶点输入直接 `SetVertexBuffer` 即可,无任何"格式转换"动作;barrier 只负责"上个用法 → 下个用法"的可见性。 - -CPU 上传路径二选一: -- buffer 声明 `MapRead` / `MapWrite` 时,应用侧 `Map()` / `Unmap()` 直接写 -- 否则沉入 `CommandBuffer.Upload` / `CopyBuffer` 走 staging +设计要点: +- `TextureDataLayout` 是纯布局描述,不携带 offset。 +- 字节偏移保留在 copy 接口上,例如 `CopyBufferToTexture(..., uint srcOffsetInBytes, TextureDataLayout srcLayout, ...)`。 +- 紧凑布局可以通过 `ZenithHelper` 计算,但最终传入的 `SizeInBytes` / `RowPitchInBytes` / `SlicePitchInBytes` 仍由调用方负责。 +- 后端特有的 copy 对齐继续下沉到各后端内部,不再抬回 `GraphicsContext`。 --- -## 1. ReadOnlySpan 替代数组 +## 1. `ReadOnlySpan` 作为多元素边界 -需求:所有"多元素入参"在公共 API 上一律改为 `ReadOnlySpan` / `params ReadOnlySpan`,不出现 `T[]`、`IList`、`IEnumerable`、`IReadOnlyList`。 +需求:所有多元素公共输入继续统一使用 `ReadOnlySpan` / `params ReadOnlySpan`。 -理由: -- 接 .NET 10 的 `params ReadOnlySpan` 与集合表达式 `[ ... ]`,调用现场零托管分配 -- 同一签名同时接受栈分配 / 数组 / 单元素,调用方写法统一 -- 后端可直接拷贝到原生数组,无需先 enumerate +代表性接口: -适用面(不完全列表): +| 接口面 | 形状 | +|---|---| +| RenderPass 颜色附件 | `ReadOnlySpan` | +| Viewport / Scissor | `ReadOnlySpan` / `ReadOnlySpan` | +| ResourceTable 数组写入 | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | +| 时间线等待集 | `params ReadOnlySpan` | +| 批量转换 | `ReadOnlySpan` | -| 入参 | 旧形态 | 新形态 | -|---|---|---| -| Submit 等待集 | `Fence[]` | `ReadOnlySpan` | -| 顶点缓冲槽 | `Buffer[]` + `ulong[]` | `ReadOnlySpan` + `ReadOnlySpan` | -| 视口 / 裁剪 | `Viewport[]` | `ReadOnlySpan` | -| RenderPass 颜色附件 | `ColorAttachment[]` | `ReadOnlySpan` | -| ResourceTable 数组绑定 | `IBindableResource[]` | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | -| 上传数据 | `byte[]` + offset/size | `ReadOnlySpan where T : unmanaged` | +理由: +- 同时接受栈上、数组以及单元素输入,调用形态统一。 +- 与当前 `BeginRenderPass`、`SetScissors`、`SetViewports`、`ResourceTable.Write` 家族保持一致。 +- 让未来的时间线接口和转换接口在调用现场保持零托管分配。 -例外:返回多个元素时使用 `ReadOnlySpan` 的成本(必须有持有者)大于价值 —— 单元素返回保留具体类型;批量返回保留 `T[]` 或暴露索引访问。 +例外: +- 返回多个元素时,`ReadOnlySpan` 仍然要求一个显式持有者。只要能避免隐藏生命周期耦合,返回具体类型依然可以接受。 --- -## 2. 跨队列同步 +## 2. 显式 Upload / Download / Copy 模型 -需求:跨队列依赖只暴露**一个**值类型 `CommandSubmission`,对齐三端时间线模型。 +需求:CPU 数据搬运继续保持描述符化,公共 texture 上传接口不回退到 `ReadOnlySpan` 风格。 ```csharp -public enum CommandQueueType -{ - Graphics, - Compute, - Copy -} - -/// -/// 一次 Submit / Present 产生的一个时间线点。 -/// default 是合法的"空"值,后端会从 waits 中过滤掉;其上 为空操作。 -/// -public readonly struct CommandSubmission(CommandQueue? queue, ulong value) -{ - public CommandQueue? Queue = queue; - - public ulong Value = value; - - public void Wait() - { - Queue?.Wait(Value); - } -} - -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { - public CommandQueueType Type { get; } = type; + public void Upload(Buffer buffer, uint offsetInBytes, BufferData data); - /// 获取一个可录制的 CommandBuffer,必要时复用已退役实例。 - public CommandBuffer CommandBuffer(); + public void Download(Buffer buffer, uint offsetInBytes, BufferData data); - /// 等待此 queue 上所有已提交命令完成。幂等。 - public void WaitForIdle(); + public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - /// 等待该 queue 时间线推进到 - public void Wait(ulong value); - - protected abstract ulong GetCompletedValue(); - - protected abstract void WaitCore(ulong value); + public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - protected abstract void SubmitCore(CommandBuffer commandBuffer, - ReadOnlySpan waits, - ulong signalValue); + public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); - protected abstract CommandBuffer CreateCommandBuffer(); -} + public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public CommandQueue Queue { get; } = queue; + public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent); - /// 结束录制并在所属 queue 上入队。 - public CommandSubmission Submit(params ReadOnlySpan waits); + public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout); } ``` 设计要点: -- 每个 queue 持一条单调递增完成时间线;`Submit` 推进 `Value`,并以 `CommandSubmission(queue, value)` 公开 -- 跨队列依赖 = 把上游 `CommandSubmission` 传入下游 `Submit(waits)` -- `GetCompletedValue()` 是方法不是属性:三端原生 API 都是调用语义 -- `CommandBuffer` 池化是 queue 内部实现,公共面只暴露 `CommandBuffer()` + `Submit(...)` -- `GraphicsContext.Graphics` / `Compute` / `Copy` 三条队列;`Present` 始终在 `Graphics` - -后端时间线对照: - -| 后端 | 时间线对象 | API | -|---|---|---| -| Metal 4 | 每 queue 一个 `MTLSharedEvent` | `commandBuffer.EncodeSignalEvent(event, value)` / `event.SignaledValue` | -| Vulkan 1.4 | 每 queue 一个 timeline `VkSemaphore` | `vkQueueSubmit2` 的 `pSignalSemaphoreInfos[].value` / `vkGetSemaphoreCounterValue` | -| DX12 | 每 queue 一个 `ID3D12Fence` | `queue.Signal(fence, value)` / `fence.GetCompletedValue()` | +- `Buffer.Upload` / `Buffer.Download` 在 buffer 可 CPU 访问时直接走 `Map()` / `Unmap()`;否则通过 `Context.Copy` staging。 +- `Texture.Upload` / `Texture.Download` 始终通过 `CommandBuffer` 完成,以保持 copy 路径显式且可由后端控制。 +- offset 与 pitch 故意分离。`TextureDataLayout` 只描述内存布局,`offsetInBytes` 决定 copy 在 staging / 目标 buffer 中从哪里开始。 +- 当前 `Extensions.ImageSharp` 与 `Views.Avalonia.Surface` 已经在按这套模型使用。 --- -## 3. 子资源 / 偏移 / 范围的值类型化 +## 3. 子资源、偏移、尺寸与 BufferRange 值类型 -需求:用 `TextureSubresource` / `TextureSubresourceLayers` / `TextureSubresourceRange` / `Offset3D` / `Extent3D` 替代旧版 `TextureSlice` / `TextureOffset` / `TextureExtent`,统一命名并明确"单点 / 单 mip 多 layer / 多 mip 多 layer"三档。 +需求:子资源与范围相关输入保持为小型值类型,并与当前 copy / render 接口对齐。 ```csharp public record struct TextureSubresource @@ -263,27 +224,7 @@ public record struct Extent3D public uint Depth; } -``` - -| 类型 | 表达 | 用途 | -|---|---|---| -| `TextureSubresource` | 单 mip × 单 layer | RTV / DSV / Resolve 端点 | -| `TextureSubresourceLayers` | 单 mip × 连续 layer 段 | Copy / Upload | -| `TextureSubresourceRange` | 连续 mip 段 × 连续 layer 段 | Barrier / `TextureView.Range` | -约定: -- 公共面不暴露 `aspect`;后端从 `Texture.Format` 推断 color / depth / stencil -- Cube face 以 `ArrayLayer = cubeIndex * 6 + face` 表达,无独立 face 轴 -- `Texture` 隐式转 `TextureSubresourceRange`(覆盖整张纹理)让常见调用一行内写完 -- 命名后缀对齐 VK:`Subresource`(点)/ `SubresourceLayers`(面)/ `SubresourceRange`(体) - ---- - -## 4. 删除 BufferView,统一为 BufferRange - -需求:移除 `BufferView` 类型与所有 `CreateBufferView` 入口,buffer 子范围一律用调用现场的 `BufferRange` 值表达。 - -```csharp public record struct BufferRange { public Buffer Buffer; @@ -307,152 +248,136 @@ public record struct BufferRange } ``` -理由: -- buffer 不需要"格式族重解释";offset/size/stride 三元组就是描述子范围所需的全部信息 -- DX12 `D3D12_*_VIEW_DESC`、VK `VkDescriptorBufferInfo`、Metal `setBuffer:offset:` 都接受调用现场的 offset/size,不存在长生命周期 view 对象的必要 -- shader 端的解释维度(CBV / structured-SRV / byteaddress-SRV / typed-SRV / UAV)由槽位的 `ResourceLayout` 决定,与 buffer 本身无关 -- 隐式转换让 `Write(0, vbo)`、`SetVertexBuffer(0, vbo, 0)` 这类常见单资源绑定保持一行 +约定: +- 公共面不携带显式 aspect 标记,后端从 `Texture.Desc.Format` 推断 color / depth / stencil。 +- Cube face 继续通过 `ArrayLayer = cubeIndex * 6 + face` 线性化。 +- `BufferRange` 是唯一公共 buffer 子范围形态,不再恢复 `BufferView` 工厂。 --- -## 5. TextureView 重构(Format / ViewType) +## 4. TextureView 继续作为长生命周期资源 -需求:`TextureView` 重新定位为**调用现场值**,5 元组 → 4 元组(去掉 swizzle),加入 `Format` / `ViewType` 用于格式族重解释与维度切换。 +需求:`TextureView` 保持为独立持有的资源对象。旧草案里“仅作为调用现场值”的方向已经不再符合当前绑定模型和扩展层用法。 ```csharp -public enum TextureViewType -{ - Texture1D, - Texture2D, - Texture3D, - TextureCube, - Texture1DArray, - Texture2DArray, - TextureCubeArray -} - -public record struct TextureView +public record struct TextureViewDesc { public Texture Texture; - public TextureSubresourceRange Range; + public TextureType Type; - /// null 表示按 Texture.Desc + Range 推导。 - public TextureViewType? ViewType; + public PixelFormat Format; - /// null 表示沿用 Texture.Desc.Format;非 null 时按同一兼容族重解释。 - public PixelFormat? Format; + public TextureSubresourceRange Range; +} - public static implicit operator TextureView(Texture texture) - { - return new() - { - Texture = texture, - Range = texture, - ViewType = null, - Format = null - }; - } +public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) : GraphicsResource(context) +{ + public ref readonly TextureViewDesc Desc => ref desc; } ``` -- 对齐 VK `VkImageViewCreateInfo` 与 Metal `newTextureViewWithPixelFormat:textureType:levels:slices:` 的参数集 -- 后端按 `(Texture, Range, ViewType, Format)` 为 key 懒加载并缓存原生 view,纯实现细节 -- 通道 swizzle **不在公共面暴露**:单通道→灰度类约定在 shader 侧解决;BGRA ↔ RGBA 一类靠 `Format` 在同兼容族内重解释覆盖。如未来出现刚需,可作为可选 `ComponentMapping?` 字段加回,向后兼容 -- `Texture` 隐式转 `TextureView`(全范围、原 format、原维度)让常见单资源绑定一行写完 +设计要点: +- `GraphicsContext.CreateTextureView(...)` 继续保留在公共面上。 +- 直接绑定 `Texture` 仍然是默认的整资源路径。 +- 绑定 `TextureView` 只用于显式子资源、维度切换或格式重解释。 +- 这与当前 `ImGui` 渲染器一致:它会长期持有 view 对象,并通过 `ResourceTable` 绑定。 +- 后端内部可以让 `Texture` 自带默认 native view,但显式 `TextureView` 仍然是公共逃生口。 --- -## 6. Barrier — 1:1 同步原语 +## 5. 平铺绑定模型:`ResourceBinding[]` + `ResourceTable` -需求:移除所有"隐式布局转换"。所有可见性 / 执行依赖 / 布局切换由调用方通过 `(stage, access)` 二元组显式声明,对齐 Metal 4 / VK 1.4 / DX12 Enhanced Barriers。 +需求:绑定模型保持扁平且具体:`ResourceBinding[]` 描述槽位,`ResourceTable` 保存资源值,`Write(...)` 继续按资源种类分类型重载。 ```csharp -[Flags] -public enum BarrierStage : uint -{ - None = 0, - All = ~0u, - Draw = 1u << 0, - VertexInput = 1u << 1, - VertexShader = 1u << 2, - PixelShader = 1u << 3, - EarlyDepthStencil = 1u << 4, - LateDepthStencil = 1u << 5, - RenderTarget = 1u << 6, - ComputeShader = 1u << 7, - RayTracing = 1u << 8, - Copy = 1u << 9, - Resolve = 1u << 10, - IndirectArgument = 1u << 11, - AccelerationStructureBuild = 1u << 12, -} - -[Flags] -public enum BarrierAccess : uint -{ - None = 0, - VertexBuffer = 1u << 0, - IndexBuffer = 1u << 1, - ConstantBuffer = 1u << 2, - ShaderRead = 1u << 3, - UnorderedAccessRead = 1u << 4, - UnorderedAccessWrite = 1u << 5, - RenderTarget = 1u << 6, - DepthStencilRead = 1u << 7, - DepthStencilWrite = 1u << 8, - CopySource = 1u << 9, - CopyDestination = 1u << 10, - ResolveSource = 1u << 11, - ResolveDestination = 1u << 12, - IndirectArgument = 1u << 13, - Present = 1u << 14, - AccelerationStructureRead = 1u << 15, - AccelerationStructureWrite = 1u << 16, +public enum ResourceType +{ + ConstantBuffer, + StructuredBuffer, + StructuredBufferReadWrite, + Texture, + TextureReadWrite, + Sampler, + AccelerationStructure +} + +public record struct ResourceBinding +{ + public ResourceType Type; + + public uint Count; +} + +public record struct ResourceTableDesc +{ + public ResourceBinding[] Bindings; +} + +public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc desc) : GraphicsResource(context) +{ + public ref readonly ResourceTableDesc Desc => ref desc; + + public abstract void Write(uint binding, Buffer buffer); + + public abstract void Write(uint binding, BufferRange bufferRange); + + public abstract void Write(uint binding, Texture texture); + + public abstract void Write(uint binding, TextureView textureView); + + public abstract void Write(uint binding, Sampler sampler); + + public abstract void Write(uint binding, TopLevelAccelerationStructure topLevelAccelerationStructure); + + public abstract void Write(uint binding, ReadOnlySpan buffers); + + public abstract void Write(uint binding, ReadOnlySpan bufferRanges); + + public abstract void Write(uint binding, ReadOnlySpan textures); + + public abstract void Write(uint binding, ReadOnlySpan textureViews); + + public abstract void Write(uint binding, ReadOnlySpan samplers); + + public abstract void Write(uint binding, ReadOnlySpan topLevelAccelerationStructures); } ``` -CommandBuffer 上的 barrier 入口三种形态: +CommandBuffer 侧入口继续保持简洁: ```csharp -public void Barrier(BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); +public void SetPipeline(GraphicsPipeline pipeline); + +public void SetPipeline(ComputePipeline pipeline); -public void BufferBarrier(BufferRange range, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); +public void SetPipeline(MeshShadingPipeline pipeline); -public void TextureBarrier(TextureView view, - BarrierStage afterStages, BarrierAccess afterAccess, - BarrierStage beforeStages, BarrierAccess beforeAccess); +public void PushResourceTable(ResourceTable resourceTable); ``` 设计要点: -- `BarrierStage` 描述访问发生在管线**何处**(when);`BarrierAccess` 描述访问的**性质**(what)。两者对齐 Metal 4 `MTL4RenderStages` + `MTL4VisibilityOptions`、VK 1.4 `VkPipelineStageFlags2` + `VkAccessFlags2`、DX12 `D3D12_BARRIER_SYNC` + `D3D12_BARRIER_ACCESS` -- 三种形态参数序一致:`(afterStages, afterAccess) → (beforeStages, beforeAccess)` -- texture image-layout:完全在后端内部维护,由 `BarrierAccess` + RenderPass attachment 元数据推导。公共面无 layout 枚举 -- 公共面**没有**全局内存屏障的"快捷形式"以外的简化入口;调用方必须显式给出 stage + access -- 队列内部的"同 stage 同资源连续访问"由后端折叠为 noop,不强求调用方手动去重 +- 没有单独的 `ResourceLayout` 对象。 +- 没有 `IBindableResource` 多态入口。 +- Pipeline 描述里的 `ResourceBindings` 与 `ResourceTableDesc` 里的 `Bindings` 故意保持同一扁平形状。 +- `Texture` 与 `TextureView` 都继续是一等写入目标,因为“默认整资源绑定”和“显式 view 绑定”在当前代码里都是真实存在的需求。 +- 先完成所有 `Write(...)`,再调用 `PushResourceTable(...)`;后端在 push 时按当前 pipeline 绑定这张表的当前内容。 +- 该模型可以自然映射到 DX12 descriptor table、Vulkan push descriptor,以及 Metal encoder 侧的资源表绑定。 --- -## 7. 删除 FrameBuffer — 内联 RenderPass + SwapChain 公开当前目标 +## 6. 内联 RenderPass、Output、SwapChain 目标与 Views 集成 -需求:移除 `FrameBuffer` / `RenderPassInfo` 等长生命周期对象。RenderPass 以 `BeginRenderPass` / `EndRenderPass` 内联表达;`SwapChain` 直接暴露 `CurrentColorTarget` / `CurrentDepthStencilTarget`,使 backbuffer 与普通 `Texture` 同形。 +需求:render target 继续保持内联表达,Views 层继续保持“渲染到一个 `Texture target`”的契约。 ```csharp -public enum LoadAction +public record struct Output { - Load, - Clear, - DontCare -} + public PixelFormat[] ColorAttachments; -public enum StoreAction -{ - Store, - DontCare, - Resolve + public PixelFormat? DepthStencilAttachment; + + public SampleCount SampleCount; } public record struct ColorAttachment @@ -491,204 +416,310 @@ public record struct DepthStencilAttachment public byte ClearStencil; } -// CommandBuffer -public void BeginRenderPass(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment); - -public void EndRenderPass(); -``` - -```csharp public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) { - private SwapChainDesc desc = desc; - public ref readonly SwapChainDesc Desc => ref desc; - public abstract uint Width { get; } - - public abstract uint Height { get; } - public abstract Texture CurrentColorTarget { get; } public abstract Texture? CurrentDepthStencilTarget { get; } - /// - /// 在 GraphicsQueue 上提交 present,等待 ; - /// 返回的 表示下一张 backbuffer 可写。 - /// - public abstract CommandSubmission Present(params ReadOnlySpan waits); - public void Resize(uint width, uint height); public void Refresh(Surface surface); } + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); + + public void EndRenderPass(); + + public void SetScissors(ReadOnlySpan scissors); + + public void SetViewports(ReadOnlySpan viewports); +} ``` 设计要点: -- backbuffer 以普通 `Texture` 公开(颜色 + 可选深度模板);它们既能进 `BeginRenderPass`,也能进 `TextureBarrier` / `CopyTexture` / `Write(...)` -- `BeginRenderPass` 接受 attachment span,无深度时显式传 `null`;按 attachment 尺寸自动填默认 `SetViewports` / `SetScissors`,调用方可在其后再调一次覆盖 -- `Present(...)` 与 `Submit(...)` 形态对称:吃 waits、产 `CommandSubmission` -- 当前 image index、acquire / present 同步原语都是后端内部事务 +- 不恢复 `FrameBuffer` 对象。 +- `Output` 继续挂在 pipeline 描述上,负责描述附件兼容性,而不是承载运行时 render target 对象。 +- `BeginRenderPass(...)` 会先按附件尺寸写入默认 scissors / viewports,调用方后续仍可覆盖。 +- `SwapChain.CurrentColorTarget` 与 `CurrentDepthStencilTarget` 按普通 `Texture` 使用。 +- `RenderEventArgs` 继续以 `Texture` 为中心:view 代码把 target texture 交给用户渲染,而不是把平台 swapchain 细节泄漏进回调。 +- 当前仓库里已经存在两类 view 路径。 +- `WinForms` / `WPF` / `WinUI` 风格 view 直接渲染到 swapchain 支撑的 target。 +- `Avalonia` 风格 view 渲染到离屏 target,并通过 `Texture.Download(...)` 完成 present。 --- -## 8. ResourceTable 多重载 + 删除 IBindableResource +## 7. 显式 Texture Layout 转换 + 轻量 `PipelineBarrier` -需求:`ResourceTable.Write` 直接按资源类型重载(`BufferRange` / `TextureView` / `Sampler`),不再走 `IBindableResource` 多态入口。 +需求:补上缺失的显式 texture layout 转换接口,以及一个最小化的、面向读写 shader hazard 的 pipeline barrier;不再把旧的完整 `(stage, access)` 矩阵搬回公共面。 ```csharp -public abstract class ResourceTable(GraphicsContext context, ResourceLayout layout) : GraphicsResource(context) +public enum TextureLayout { - public ResourceLayout Layout { get; } = layout; + Undefined, + Common, + ShaderResource, + UnorderedAccess, + RenderTarget, + DepthStencilRead, + DepthStencilWrite, + CopySource, + CopyDestination, + ResolveSource, + ResolveDestination, + Present +} - public void Write(uint binding, BufferRange range); +public record struct TextureTransition +{ + public Texture Texture; - public void Write(uint binding, TextureView view); + public TextureSubresourceRange Range; - public void Write(uint binding, Sampler sampler); + public TextureLayout Before; - public void Write(uint binding, ReadOnlySpan ranges); + public TextureLayout After; +} - public void Write(uint binding, ReadOnlySpan views); +public enum PipelineBarrierScope +{ + All, + Draw, + Compute, + MeshShading, + RayTracing, + Copy, + AccelerationStructureBuild +} - public void Write(uint binding, ReadOnlySpan samplers); +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public void Transition(Texture texture, TextureSubresourceRange range, TextureLayout before, TextureLayout after); - protected abstract void WriteCore(uint binding, ReadOnlySpan ranges); + public void Transition(TextureView textureView, TextureLayout before, TextureLayout after); - protected abstract void WriteCore(uint binding, ReadOnlySpan views); + public void Transition(ReadOnlySpan transitions); - protected abstract void WriteCore(uint binding, ReadOnlySpan samplers); + public void PipelineBarrier(PipelineBarrierScope before, PipelineBarrierScope after); } ``` -CommandBuffer 上对应入口: +设计要点: +- `Transition(...)` 是显式 texture 状态 / layout 接口。Copy、采样、RenderPass、Present 流程里不再保留隐式 layout 变化。 +- `TextureView` 重载复用 `textureView.Desc.Range`;`textureView.Desc.Format` 不参与 layout 选择。 +- `PipelineBarrier(...)` 是故意做小的 UAV 风格顺序原语,用于“dispatch A 写 `RWTexture` / `RWBuffer`,dispatch B 又访问它”这类场景。它不改变 layout,也不跨 queue。 +- 本轮不增加公共 `BufferBarrier`。Buffer 的 copy / bind 顺序继续遵从命令语义,显式用户同步仅保留给 layout 转换和 shader 读写 hazard 隔离。 +- 后端映射很直接: +- DX12:resource state transition + UAV barrier +- Vulkan:image memory barrier + 轻量 memory barrier +- Metal:texture usage-state transition + encoder memory barrier / fence + +--- + +## 8. 基于时间线的跨 Queue 同步 + +需求:加入规范化的时间线提交模型,同时不删除当前已经在用的便利包装。 ```csharp -public void SetPipeline(Pipeline pipeline); +public enum CommandQueueType +{ + Graphics, + Compute, + Copy +} + +public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) +{ + public bool IsEmpty => Queue is null; + + public void Wait() + { + Queue?.Wait(Value); + } +} + +public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) +{ + public CommandQueueType Type { get; } = type; + + public CommandBuffer CommandBuffer(); + + public void WaitIdle(); + + public void Wait(ulong value); + + public abstract ulong GetCompletedValue(); +} + +public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + public CommandQueue Queue { get; } = queue; + + public CommandSubmission Submit(params ReadOnlySpan waits); -public void PushResourceTable(ResourceTable table); + public void Submit(bool waitForCompletion = false); +} + +public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) +{ + public abstract CommandSubmission Present(params ReadOnlySpan waits); + + public abstract void Present(); +} ``` 设计要点: -- `Buffer` / `Texture` 经 §4 / §5 的隐式转换让单资源绑定一行写完:`table.Write(0, vbo)` / `table.Write(1, tex)` -- buffer 解释(CBV / SRV-structured / SRV-byteaddress / UAV / typed)由槽位的 `ResourceLayout` 决定,与传入的 `BufferRange` 无关 -- 每个 pipeline 仅支持一张 table(对齐 Metal 4 argument-table 模型) -- `PushResourceTable` 不接受 stages 参数:stages 信息由 layout 在每个 binding 上自带 -- **Push-snapshot 语义**:`PushResourceTable` 在调用现场把 `table` 的当前内容快照进 cmd buffer;之后对该 `table` 的 `Write` 不影响已 push 的绑定,所以同一个 `ResourceTable` 可以在帧内反复 `Write` + `Push` -- 校验:UAV 槽位要求资源声明对应 `UnorderedAccess` 能力位,shape / layout / 格式族不匹配在 `Write` 时报错 - -为什么不用 `IBindableResource`: -- 三种资源在原生 API 上的写入路径完全不同(descriptor / argument buffer slot / sampler heap),多态接口反而需要在运行时 dispatch -- 重载形式让编译期就能选中正确的 `Write` 路径,避免装箱与运行时 type test -- 隐式转换 `Buffer → BufferRange` / `Texture → TextureView` 已经覆盖"我就想直接传句柄"的便利性 +- 时间线路径是规范低层模型:`Submit(waits)` 与 `Present(waits)` 都返回一个 `CommandSubmission`。 +- `Submit(bool waitForCompletion = false)` 与无参 `Present()` 继续保留,服务当前 `Extensions` 与 `Views` 中已经存在的单 queue、单帧循环用法。 +- 每条 queue 持有一条单调递增完成时间线。 +- `default` / 空 `CommandSubmission` 会在等待集里被忽略。 +- `Present(waits)` 返回“当前呈现 backbuffer 重新可写”的那个时间线点。 +- 后端映射直接对应各 API 的自然原语: +- DX12:每条 queue 一条 `ID3D12Fence` +- Vulkan:每条 queue 一条 timeline `VkSemaphore` +- Metal 4:每条 queue 一条 `MTLSharedEvent` --- -## 9. INativeObject — 原生句柄统一入口 +## 9. 统一 Native Handle 与完整 `NativeObjectType` -需求:所有持有原生对象的公共类型实现 `INativeObject`,通过 `GetNativeObject(NativeObjectType)` 暴露后端句柄;公共面不挂任何平台条件属性。 +需求:所有封装 native 对象的公共类型继续统一走 `INativeObject`,而 `NativeObjectType` 则按稳定 native 角色补齐,不再依赖后端特化 cast helper。 ```csharp public enum NativeObjectType { - // DirectX 12 + // DirectX 12 / DXGI DxgiFactory, DxgiAdapter, + DxgiSwapChain, D3D12Device, D3D12CommandQueue, + D3D12Fence, D3D12GraphicsCommandList, + D3D12PipelineState, + D3D12RootSignature, D3D12Resource, - D3D12CpuDescriptorHandleSampler, + D3D12DescriptorHeap, D3D12CpuDescriptorHandleRtv, D3D12CpuDescriptorHandleDsv, D3D12CpuDescriptorHandleSrv, D3D12CpuDescriptorHandleUav, + D3D12CpuDescriptorHandleSampler, + D3D12QueryHeap, - // Metal 4 + // Metal / Metal 4 MtlDevice, Mtl4CommandQueue, Mtl4CommandBuffer, + MtlSharedEvent, MtlTexture, MtlBuffer, MtlSamplerState, + MtlRenderPipelineState, + MtlComputePipelineState, + MtlDepthStencilState, + MtlHeap, + MtlAccelerationStructure, + CaMetalLayer, + CaMetalDrawable, // Vulkan VkInstance, VkPhysicalDevice, + VkSurfaceKHR, + VkSwapchainKHR, VkDevice, VkQueue, VkQueueFamilyIndex, + VkSemaphore, VkCommandBuffer, + VkPipeline, + VkPipelineLayout, + VkShaderModule, VkImage, VkImageView, VkBuffer, - VkSampler + VkSampler, + VkDescriptorSet, + VkQueryPool, + VkAccelerationStructureKHR } public interface INativeObject { - /// 枚举不匹配本对象 / 当前后端时返回 0。 + /// 当请求的 native 角色与当前对象或当前后端不匹配时,返回 0。 nint GetNativeObject(NativeObjectType type); } - -public abstract class GraphicsContext : DisposableObject, INativeObject -{ - public abstract nint GetNativeObject(NativeObjectType type); -} - -// GraphicsResource 已在 §0.3 实现 INativeObject ``` 设计要点: -- 命名沿用各原生 API 既有前缀:DX12 体系按 `Dxgi` / `D3D12` 区分;Metal 4 与旧 Metal 共存时用 `Mtl` / `Mtl4` 区分;Vulkan 一律 `Vk` -- `D3D12CpuDescriptorHandle*` 按 view 类型拆开,避免单入口语义模糊 -- 非句柄的标量信息(如 `VkQueueFamilyIndex`)也走同一入口,以 `nint` 承载 `uint` -- 所有 `GraphicsResource` 子类一律实现该接口;不感兴趣的子类对所有类型返回 0 -- 取该接口的代码必须自己处理"返回 0"的退化路径,公共面不抛异常 +- 该枚举围绕稳定的 native 角色组织,能够自然映射到当前 Zenith 的对象类别。 +- 一个公共对象可以暴露多个 native 角色。例如 DX12 pipeline 可以同时暴露 `D3D12PipelineState` 与 `D3D12RootSignature`。 +- 多个 Zenith 对象类别也可以复用同一个 native 角色。例如 DX12 buffer、texture、acceleration structure 都可通过 `D3D12Resource` 暴露。 +- `VkQueueFamilyIndex` 这类非指针标量也继续通过同一个 `nint` 入口承载。 +- `GetNativeObject(...)` 不增加引用计数,也不转移所有权;返回句柄只在 Zenith 对象存活期间有效。 +- 返回 `0` 是唯一退化路径,不支持的组合不抛异常。 --- -## 附录 A:典型帧循环(综合 §2 / §6 / §7) +## 附录 A:当前 Upload 路径 ```csharp -CommandSubmission imageReady = default; +CommandBuffer commandBuffer = context.Copy.CommandBuffer(); + +commandBuffer.Upload(texture, + default, + default, + new() { Width = width, Height = height, Depth = 1 }, + new() + { + Pointer = pixels, + Layout = new() + { + SizeInBytes = sizeInBytes, + RowPitchInBytes = rowPitchInBytes, + SlicePitchInBytes = slicePitchInBytes + } + }); + +commandBuffer.Submit(true); +``` -while (running) -{ - CommandBuffer cmd = ctx.Graphics.CommandBuffer(); +这与当前 `ImageSharp` 扩展,以及当前离屏 view 的 present 路径一致。 - ReadOnlySpan colors = - [ - new() - { - Texture = swapChain.CurrentColorTarget, - Subresource = new() { MipLevel = 0, ArrayLayer = 0 }, - LoadAction = LoadAction.Clear, - StoreAction = StoreAction.Store, - ClearColor = new(0, 0, 0, 1) - } - ]; - - DepthStencilAttachment? depth = swapChain.CurrentDepthStencilTarget is { } ds - ? new() - { - Texture = ds, - Subresource = new(), - DepthLoadAction = LoadAction.Clear, - DepthStoreAction = StoreAction.DontCare, - ClearDepth = 1.0f - } - : null; +## 附录 B:当前绑定路径 - cmd.BeginRenderPass(colors, depth); - // draws - cmd.EndRenderPass(); +```csharp +ResourceBinding[] bindings = +[ + new() { Type = ResourceType.ConstantBuffer, Count = 1 }, + new() { Type = ResourceType.Texture, Count = 1 }, + new() { Type = ResourceType.Sampler, Count = 1 } +]; + +ResourceTable table = context.CreateResourceTable(new() { Bindings = bindings }); +table.Write(0, constants); +table.Write(1, textureView); +table.Write(2, sampler); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.PushResourceTable(table); +``` - CommandSubmission frame = cmd.Submit(imageReady); +这与当前 `ImGui` 扩展一致:长期持有 `ResourceTable`,显式 `Write(...)`,然后在当前 pipeline 下 `PushResourceTable(...)`。 - imageReady = swapChain.Present(frame); -} +## 附录 C:时间线形状的多 Queue 流程 + +```csharp +CommandSubmission copyDone = copyCommandBuffer.Submit(); + +CommandSubmission graphicsDone = graphicsCommandBuffer.Submit(copyDone); + +CommandSubmission presentDone = swapChain.Present(graphicsDone); ``` -每一步形态一致:拿一组 `CommandSubmission` waits,返回一个 `CommandSubmission`。`Submit` 与 `Present` 在 API 表面对称。 +现有 `Submit(true)` / `Present()` 的便利调用在简单单 queue 路径下仍然有效;只有当工作跨 queue 或跨帧传播时,`CommandSubmission` 才成为必需。 \ No newline at end of file From 76a862810c6b75f8cd4e50b3c08d8ff6cbf542fe Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 25 Apr 2026 19:09:26 +0800 Subject: [PATCH 037/466] Add TextureLayout enum and CommandBuffer.Transition method Added TextureLayout enum to represent various texture layouts. Introduced the Transition method in CommandBuffer for handling texture layout transitions (currently not implemented). --- sources/Zenith.NET/CommandBuffer.cs | 5 +++++ sources/Zenith.NET/Enums/TextureLayout.cs | 24 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 sources/Zenith.NET/Enums/TextureLayout.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 7fadd0f2..76bf2445 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -88,6 +88,11 @@ public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerati accelerationStructure.Refresh(newDesc); } + public void Transition(Texture texture, TextureSubresource subresource, TextureLayout layout) + { + throw new NotImplementedException(); + } + public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { Span scissors = stackalloc Scissor[8]; diff --git a/sources/Zenith.NET/Enums/TextureLayout.cs b/sources/Zenith.NET/Enums/TextureLayout.cs new file mode 100644 index 00000000..eb091656 --- /dev/null +++ b/sources/Zenith.NET/Enums/TextureLayout.cs @@ -0,0 +1,24 @@ +namespace Zenith.NET; + +public enum TextureLayout +{ + RenderTarget, + + DepthStencil, + + DepthStencilReadOnly, + + ShaderResource, + + UnorderedAccess, + + CopySrc, + + CopyDest, + + ResolveSrc, + + ResolveDest, + + Present +} From d80b0ccdb73820b4ab8a28f822b110dcd732bf5e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 12:47:11 +0800 Subject: [PATCH 038/466] Refactor command buffer submission and sync primitives Refactored command buffer submission to return CommandSubmission structs for explicit synchronization. Updated CommandQueue to track and recycle buffers using signal values, and added memory barrier methods to CommandBuffer. SwapChain present/acquire now use CommandSubmission. These changes improve resource management and synchronization flexibility. --- sources/Zenith.NET/Buffer.cs | 4 +- sources/Zenith.NET/CommandBuffer.cs | 30 +++++++-- sources/Zenith.NET/CommandQueue.cs | 65 ++++++++++++------- .../Zenith.NET/Structs/CommandSubmission.cs | 9 +++ sources/Zenith.NET/SwapChain.cs | 4 +- sources/Zenith.NET/Texture.cs | 4 +- 6 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 sources/Zenith.NET/Structs/CommandSubmission.cs diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 52845eaa..74d05a76 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -28,7 +28,7 @@ public void Upload(uint offsetInBytes, BufferData data) CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); commandBuffer.Upload(this, offsetInBytes, data); - commandBuffer.Submit(true); + commandBuffer.Submit().Wait(); } } @@ -50,7 +50,7 @@ public void Download(uint offsetInBytes, BufferData data) CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); commandBuffer.Download(this, offsetInBytes, data); - commandBuffer.Submit(true); + commandBuffer.Submit().Wait(); } } } diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 76bf2445..f535ff39 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -4,14 +4,26 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) { private Pipeline? currentPipeline; - public void Submit(bool waitForCompletion = false) + public CommandQueue Queue => queue; + + public CommandSubmission Submit(params ReadOnlySpan waits) { - queue.Submit(this); + return queue.Submit(this, waits); + } - if (waitForCompletion) - { - queue.WaitIdle(); - } + public void MemoryBarrier() + { + MemoryBarrierImpl(); + } + + public void MemoryBarrier(Texture texture) + { + MemoryBarrierImpl(texture); + } + + public void MemoryBarrier(Buffer buffer) + { + MemoryBarrierImpl(buffer); } public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) @@ -360,6 +372,12 @@ protected override void Destroy() currentPipeline = null; } + protected abstract void MemoryBarrierImpl(); + + protected abstract void MemoryBarrierImpl(Texture texture); + + protected abstract void MemoryBarrierImpl(Buffer buffer); + protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 9fde2e09..cdc99a75 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -4,7 +4,9 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ { private readonly Lock @lock = new(); private readonly Queue available = []; - private readonly Queue execution = []; + private readonly Queue execution = []; + + private ulong value; public CommandQueueType Type { get; } = type; @@ -12,6 +14,8 @@ public CommandBuffer CommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); + Recycle(); + CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); commandBuffer.Begin(); @@ -19,52 +23,67 @@ public CommandBuffer CommandBuffer() return commandBuffer; } - public void WaitIdle() + internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) { using Lock.Scope _ = @lock.EnterScope(); - if (execution.Count is 0) - { - return; - } + Recycle(); - WaitIdleImpl(); + commandBuffer.End(); - while (execution.TryDequeue(out CommandBuffer? commandBuffer)) - { - commandBuffer.Reset(); + SubmitImpl(commandBuffer, waits, ++value); - available.Enqueue(commandBuffer); - } + execution.Enqueue(new(commandBuffer, value)); + + return new(this, value); } - internal void Submit(CommandBuffer commandBuffer) + internal void Wait(ulong value) { - using Lock.Scope _ = @lock.EnterScope(); - - commandBuffer.End(); - - SubmitImpl(commandBuffer); + if (value is 0) + { + return; + } - execution.Enqueue(commandBuffer); + WaitImpl(value); } + protected abstract ulong GetCompletedValue(); + protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract void WaitIdleImpl(); + protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); - protected abstract void SubmitImpl(CommandBuffer commandBuffer); + protected abstract void WaitImpl(ulong value); protected override void Destroy() { + Wait(value); + while (available.TryDequeue(out CommandBuffer? commandBuffer)) { commandBuffer.Dispose(); } - while (execution.TryDequeue(out CommandBuffer? commandBuffer)) + while (execution.TryDequeue(out PendingCommandBuffer pending)) { - commandBuffer.Dispose(); + pending.CommandBuffer.Dispose(); + } + } + + private void Recycle() + { + ulong completed = GetCompletedValue(); + + while (execution.TryPeek(out PendingCommandBuffer pending) && pending.Value <= completed) + { + execution.Dequeue(); + + pending.CommandBuffer.Reset(); + + available.Enqueue(pending.CommandBuffer); } } + + private readonly record struct PendingCommandBuffer(CommandBuffer CommandBuffer, ulong Value); } diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs new file mode 100644 index 00000000..de8e65e3 --- /dev/null +++ b/sources/Zenith.NET/Structs/CommandSubmission.cs @@ -0,0 +1,9 @@ +namespace Zenith.NET; + +public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) +{ + public void Wait() + { + Queue?.Wait(Value); + } +} diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 3af2e8ee..8f96c261 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -10,7 +10,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture? CurrentDepthStencilTarget { get; } - public abstract void Present(); + public abstract CommandSubmission Acquire(); + + public abstract CommandSubmission Present(params ReadOnlySpan waits); public void Resize(uint width, uint height) { diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index f42b8c0a..96ccdb74 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -11,7 +11,7 @@ public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D ext CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); commandBuffer.Upload(this, subresource, offset, extent, data); - commandBuffer.Submit(true); + commandBuffer.Submit().Wait(); } public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) @@ -19,6 +19,6 @@ public void Download(TextureSubresource subresource, Offset3D offset, Extent3D e CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); commandBuffer.Download(this, subresource, offset, extent, data); - commandBuffer.Submit(true); + commandBuffer.Submit().Wait(); } } From 5ad0913203c8e40844040d0de3e70a560b652dbd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 12:54:01 +0800 Subject: [PATCH 039/466] Refactor CommandSubmission to Submission struct Replaces CommandSubmission with Submission across the codebase, updating method signatures and internal logic in CommandBuffer, CommandQueue, and SwapChain. Renames PendingCommandBuffer to InFlight for clarity. Submission struct retains the Wait method for compatibility. --- sources/Zenith.NET/CommandBuffer.cs | 2 +- sources/Zenith.NET/CommandQueue.cs | 18 +++++++++--------- .../{CommandSubmission.cs => Submission.cs} | 2 +- sources/Zenith.NET/SwapChain.cs | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) rename sources/Zenith.NET/Structs/{CommandSubmission.cs => Submission.cs} (53%) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index f535ff39..3739f4bf 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -6,7 +6,7 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) public CommandQueue Queue => queue; - public CommandSubmission Submit(params ReadOnlySpan waits) + public Submission Submit(params ReadOnlySpan waits) { return queue.Submit(this, waits); } diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index cdc99a75..904ce806 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -4,7 +4,7 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ { private readonly Lock @lock = new(); private readonly Queue available = []; - private readonly Queue execution = []; + private readonly Queue execution = []; private ulong value; @@ -23,7 +23,7 @@ public CommandBuffer CommandBuffer() return commandBuffer; } - internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) + internal Submission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) { using Lock.Scope _ = @lock.EnterScope(); @@ -52,7 +52,7 @@ internal void Wait(ulong value) protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); protected abstract void WaitImpl(ulong value); @@ -65,9 +65,9 @@ protected override void Destroy() commandBuffer.Dispose(); } - while (execution.TryDequeue(out PendingCommandBuffer pending)) + while (execution.TryDequeue(out InFlight inFlight)) { - pending.CommandBuffer.Dispose(); + inFlight.CommandBuffer.Dispose(); } } @@ -75,15 +75,15 @@ private void Recycle() { ulong completed = GetCompletedValue(); - while (execution.TryPeek(out PendingCommandBuffer pending) && pending.Value <= completed) + while (execution.TryPeek(out InFlight inFlight) && inFlight.Value <= completed) { execution.Dequeue(); - pending.CommandBuffer.Reset(); + inFlight.CommandBuffer.Reset(); - available.Enqueue(pending.CommandBuffer); + available.Enqueue(inFlight.CommandBuffer); } } - private readonly record struct PendingCommandBuffer(CommandBuffer CommandBuffer, ulong Value); + private readonly record struct InFlight(CommandBuffer CommandBuffer, ulong Value); } diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/Submission.cs similarity index 53% rename from sources/Zenith.NET/Structs/CommandSubmission.cs rename to sources/Zenith.NET/Structs/Submission.cs index de8e65e3..36de52c5 100644 --- a/sources/Zenith.NET/Structs/CommandSubmission.cs +++ b/sources/Zenith.NET/Structs/Submission.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) +public readonly record struct Submission(CommandQueue? Queue, ulong Value) { public void Wait() { diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 8f96c261..4b97500d 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -10,9 +10,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture? CurrentDepthStencilTarget { get; } - public abstract CommandSubmission Acquire(); + public abstract Submission Acquire(); - public abstract CommandSubmission Present(params ReadOnlySpan waits); + public abstract Submission Present(params ReadOnlySpan waits); public void Resize(uint width, uint height) { From fe82359dc113a5382f5e2ae41c1fee6278d3daa3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 12:56:09 +0800 Subject: [PATCH 040/466] Refactor to use CommandSubmission instead of Submission Replaces the Submission struct with CommandSubmission across the codebase. Updates all relevant method signatures and implementations in CommandBuffer, CommandQueue, and SwapChain. Removes the obsolete Submission struct and introduces CommandSubmission with a similar Wait method. --- sources/Zenith.NET/CommandBuffer.cs | 2 +- sources/Zenith.NET/CommandQueue.cs | 4 ++-- .../Structs/{Submission.cs => CommandSubmission.cs} | 2 +- sources/Zenith.NET/SwapChain.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename sources/Zenith.NET/Structs/{Submission.cs => CommandSubmission.cs} (53%) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 3739f4bf..f535ff39 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -6,7 +6,7 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) public CommandQueue Queue => queue; - public Submission Submit(params ReadOnlySpan waits) + public CommandSubmission Submit(params ReadOnlySpan waits) { return queue.Submit(this, waits); } diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 904ce806..878da14c 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -23,7 +23,7 @@ public CommandBuffer CommandBuffer() return commandBuffer; } - internal Submission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) + internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) { using Lock.Scope _ = @lock.EnterScope(); @@ -52,7 +52,7 @@ internal void Wait(ulong value) protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); protected abstract void WaitImpl(ulong value); diff --git a/sources/Zenith.NET/Structs/Submission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs similarity index 53% rename from sources/Zenith.NET/Structs/Submission.cs rename to sources/Zenith.NET/Structs/CommandSubmission.cs index 36de52c5..de8e65e3 100644 --- a/sources/Zenith.NET/Structs/Submission.cs +++ b/sources/Zenith.NET/Structs/CommandSubmission.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public readonly record struct Submission(CommandQueue? Queue, ulong Value) +public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) { public void Wait() { diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 4b97500d..8f96c261 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -10,9 +10,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture? CurrentDepthStencilTarget { get; } - public abstract Submission Acquire(); + public abstract CommandSubmission Acquire(); - public abstract Submission Present(params ReadOnlySpan waits); + public abstract CommandSubmission Present(params ReadOnlySpan waits); public void Resize(uint width, uint height) { From 4b95addf950311062407413fe56ecf7fcf5db0c1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 13:03:57 +0800 Subject: [PATCH 041/466] Refactor CommandQueue internal queue and record names Renamed 'available' to 'commandBuffers' and 'execution' to 'submitteds' for clarity. Also renamed the 'InFlight' record to 'Submitted' and updated all related logic to use the new names. No changes to underlying behavior. --- sources/Zenith.NET/CommandQueue.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 878da14c..ac8f5952 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -3,8 +3,8 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) { private readonly Lock @lock = new(); - private readonly Queue available = []; - private readonly Queue execution = []; + private readonly Queue commandBuffers = []; + private readonly Queue submitteds = []; private ulong value; @@ -16,7 +16,7 @@ public CommandBuffer CommandBuffer() Recycle(); - CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); + CommandBuffer commandBuffer = commandBuffers.Count is 0 ? CreateCommandBuffer() : commandBuffers.Dequeue(); commandBuffer.Begin(); @@ -27,13 +27,13 @@ internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan Date: Sun, 26 Apr 2026 13:15:08 +0800 Subject: [PATCH 042/466] Add texture transition support to CommandBuffer Added a public Transition method to CommandBuffer for transitioning texture subresources to specified layouts. Removed the previous NotImplementedException placeholder and introduced the abstract TransitionImpl method for backend-specific implementations. --- sources/Zenith.NET/CommandBuffer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index f535ff39..a4435d0d 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -26,6 +26,11 @@ public void MemoryBarrier(Buffer buffer) MemoryBarrierImpl(buffer); } + public void Transition(Texture texture, TextureSubresource subresource, TextureLayout layout) + { + TransitionImpl(texture, subresource, layout); + } + public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) { Buffer temporary = Context.Uploader.Buffer(this, data.SizeInBytes); @@ -100,11 +105,6 @@ public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerati accelerationStructure.Refresh(newDesc); } - public void Transition(Texture texture, TextureSubresource subresource, TextureLayout layout) - { - throw new NotImplementedException(); - } - public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { Span scissors = stackalloc Scissor[8]; @@ -378,6 +378,8 @@ protected override void Destroy() protected abstract void MemoryBarrierImpl(Buffer buffer); + protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout layout); + protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); From 24137c7ae10216d85dcfbff009c724df1dc5daf8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 13:35:00 +0800 Subject: [PATCH 043/466] Refactor graphics enums and state structs for clarity - Reordered Blend enum and added BlendFactor - Updated RasterizerState to include FrontFace, removed FillMode - Refactored RasterizerStates defaults and wireframe states - Reordered and cleaned up SamplerDesc struct, removed BorderColor - Improved validation logic in ValidationLayer for new struct layouts --- sources/Zenith.NET/Enums/Blend.cs | 16 ++++++++-------- sources/Zenith.NET/RasterizerStates.cs | 14 +++++++------- sources/Zenith.NET/Structs/RasterizerState.cs | 4 ++-- sources/Zenith.NET/Structs/SamplerDesc.cs | 8 ++++---- sources/Zenith.NET/ValidationLayer.cs | 14 +++++++------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/sources/Zenith.NET/Enums/Blend.cs b/sources/Zenith.NET/Enums/Blend.cs index 1d25ebe9..51ad57cb 100644 --- a/sources/Zenith.NET/Enums/Blend.cs +++ b/sources/Zenith.NET/Enums/Blend.cs @@ -6,14 +6,6 @@ public enum Blend One, - SrcAlpha, - - InverseSrcAlpha, - - DestAlpha, - - InverseDestAlpha, - SrcColor, InverseSrcColor, @@ -22,6 +14,14 @@ public enum Blend InverseDestColor, + SrcAlpha, + + InverseSrcAlpha, + + DestAlpha, + + InverseDestAlpha, + BlendFactor, InverseBlendFactor diff --git a/sources/Zenith.NET/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs index 94da7783..039bae6e 100644 --- a/sources/Zenith.NET/RasterizerStates.cs +++ b/sources/Zenith.NET/RasterizerStates.cs @@ -4,8 +4,8 @@ public static class RasterizerStates { public static readonly RasterizerState Default = new() { - CullMode = CullMode.None, FillMode = FillMode.Solid, + CullMode = CullMode.None, FrontFace = FrontFace.CounterClockwise, DepthBias = 0, DepthBiasClamp = 0.0f, @@ -31,19 +31,19 @@ public static class RasterizerStates public static readonly RasterizerState WireframeCullFront = Default with { - CullMode = CullMode.Front, - FillMode = FillMode.Wireframe + FillMode = FillMode.Wireframe, + CullMode = CullMode.Front }; public static readonly RasterizerState WireframeCullBack = Default with { - CullMode = CullMode.Back, - FillMode = FillMode.Wireframe + FillMode = FillMode.Wireframe, + CullMode = CullMode.Back }; public static readonly RasterizerState Wireframe = Default with { - CullMode = CullMode.None, - FillMode = FillMode.Wireframe + FillMode = FillMode.Wireframe, + CullMode = CullMode.None }; } diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index 2c9afe2c..96051992 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -2,10 +2,10 @@ public record struct RasterizerState { - public CullMode CullMode; - public FillMode FillMode; + public CullMode CullMode; + public FrontFace FrontFace; public int DepthBias; diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 2361937c..1a08e29f 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -2,23 +2,23 @@ public record struct SamplerDesc { + public Filter Filter; + public AddressMode U; public AddressMode V; public AddressMode W; - public Filter Filter; - public ComparisonFunc ComparisonFunc; public uint MaxAnisotropy; + public float LodBias; + public float MinLod; public float MaxLod; - public float LodBias; - public BorderColor BorderColor; } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index fc160549..169088af 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -263,6 +263,11 @@ internal void ValidateDesc(TextureViewDesc desc) internal void ValidateDesc(SamplerDesc desc) { + if (!Enum.IsDefined(desc.Filter)) + { + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.Filter", desc.Filter)); + } + if (!Enum.IsDefined(desc.U)) { ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.U", desc.U)); @@ -278,9 +283,9 @@ internal void ValidateDesc(SamplerDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.W", desc.W)); } - if (!Enum.IsDefined(desc.Filter)) + if (!Enum.IsDefined(desc.ComparisonFunc)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.Filter", desc.Filter)); + ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.ComparisonFunc", desc.ComparisonFunc)); } if (desc.Filter is Filter.Anisotropic && desc.MaxAnisotropy is 0) @@ -293,11 +298,6 @@ internal void ValidateDesc(SamplerDesc desc) ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); } - if (!Enum.IsDefined(desc.ComparisonFunc)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.ComparisonFunc", desc.ComparisonFunc)); - } - if (desc.MinLod > desc.MaxLod) { ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); From 0903d27200616f8947ff5ae3fab845bf0e6a3a3e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 14:19:30 +0800 Subject: [PATCH 044/466] Add Downloader for GPU-to-CPU buffer transfers Introduce Downloader class for managing buffer downloads and leasing. Implement Download methods in CommandBuffer for Buffer and Texture. Refactor CommandQueue recycling logic into Wait. Update GraphicsContext to own and dispose Downloader. Improves GPU-to-CPU data transfer and resource management. --- sources/Zenith.NET/CommandBuffer.cs | 10 +- sources/Zenith.NET/CommandQueue.cs | 31 +++---- sources/Zenith.NET/Downloader.cs | 126 ++++++++++++++++++++++++++ sources/Zenith.NET/GraphicsContext.cs | 4 + 4 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 sources/Zenith.NET/Downloader.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index a4435d0d..1737ba1c 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -41,7 +41,9 @@ public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { - throw new NotImplementedException(); + Buffer temporary = Context.Downloader.Buffer(this, data.Pointer, data.SizeInBytes); + + CopyBuffer(buffer, offsetInBytes, temporary, 0, data.SizeInBytes); } public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) @@ -54,7 +56,9 @@ public void Upload(Texture texture, TextureSubresource subresource, Offset3D off public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - throw new NotImplementedException(); + Buffer temporary = Context.Downloader.Buffer(this, data.Pointer, data.Layout.SizeInBytes); + + CopyTextureToBuffer(texture, subresource, offset, extent, temporary, 0, data.Layout); } public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) @@ -361,6 +365,7 @@ internal void Reset() ResetImpl(); Context.Uploader.Release(this); + Context.Downloader.Release(this); currentPipeline = null; } @@ -368,6 +373,7 @@ internal void Reset() protected override void Destroy() { Context.Uploader.Release(this); + Context.Downloader.Release(this); currentPipeline = null; } diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index ac8f5952..10f11680 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -14,8 +14,6 @@ public CommandBuffer CommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); - Recycle(); - CommandBuffer commandBuffer = commandBuffers.Count is 0 ? CreateCommandBuffer() : commandBuffers.Dequeue(); commandBuffer.Begin(); @@ -29,8 +27,6 @@ internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan available = []; + private readonly Dictionary> borrowed = []; + + public Buffer Buffer(CommandBuffer commandBuffer, nint destination, uint sizeInBytes) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (!borrowed.TryGetValue(commandBuffer, out List? leases)) + { + borrowed[commandBuffer] = leases = []; + } + + if (!(available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) + { + lease = new(context.CreateBuffer(new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 1, + Flags = BufferUsageFlags.MapRead + })); + } + + leases.Add(lease.Borrow(destination, sizeInBytes)); + + return lease.Buffer; + } + + public void Release(CommandBuffer commandBuffer) + { + using Lock.Scope _ = @lock.EnterScope(); + + CleanupExpiredLeases(); + + if (borrowed.Remove(commandBuffer, out List? leases)) + { + foreach (Lease lease in leases) + { + available.Add(lease.Writeback().Renew()); + } + } + } + + protected override void Destroy() + { + foreach (CommandBuffer commandBuffer in borrowed.Keys.ToArray()) + { + Release(commandBuffer); + } + + foreach (Lease lease in available) + { + lease.Release(); + } + available.Clear(); + } + + private void CleanupExpiredLeases() + { + available.RemoveAll(static item => item.TryExpire()); + } + + private class Lease(Buffer buffer) + { + private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + private nint destination; + private uint sizeInBytes; + + public Buffer Buffer { get; } = buffer; + + public bool HasCapacityFor(uint sizeInBytes) + { + return Buffer.Desc.SizeInBytes >= sizeInBytes; + } + + public Lease Borrow(nint destination, uint sizeInBytes) + { + this.destination = destination; + this.sizeInBytes = sizeInBytes; + + return this; + } + + public Lease Writeback() + { + MappedMemory mappedMemory = Buffer.Map(); + + unsafe + { + new ReadOnlySpan((void*)mappedMemory.Pointer, (int)sizeInBytes).CopyTo(new((void*)destination, (int)sizeInBytes)); + } + + Buffer.Unmap(); + + return this; + } + + public bool TryExpire() + { + if (DateTime.UtcNow >= expirationTime) + { + Release(); + + return true; + } + + return false; + } + + public Lease Renew() + { + expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + + return this; + } + + public void Release() + { + Buffer.Dispose(); + } + } +} diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 22118810..dd6f8fbb 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -22,6 +22,7 @@ protected GraphicsContext(Backend backend, bool useValidationLayer) ValidationLayer = validationLayer; Uploader = new(this); + Downloader = new(this); } public Backend Backend { get; } @@ -38,6 +39,8 @@ protected GraphicsContext(Backend backend, bool useValidationLayer) internal Uploader Uploader { get; } + internal Downloader Downloader { get; } + public event EventHandler? ValidationMessage; public SwapChain CreateSwapChain(SwapChainDesc desc) @@ -127,6 +130,7 @@ protected override void Destroy() ValidationLayer?.Dispose(); Uploader.Dispose(); + Downloader.Dispose(); } protected abstract void Initialize(bool useValidationLayer, From bdb9c90a6a63ede85c76eb77e67bdf2f0082e055 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 14:29:51 +0800 Subject: [PATCH 045/466] Refactor: replace ColorComponentFlags with ColorWriteMask Refactored the graphics abstraction layer to use a new ColorWriteMask enum instead of ColorComponentFlags. Updated all relevant methods, properties, and platform-specific mappings (DirectX12, Metal, Vulkan) to use ColorWriteMask. Adjusted BlendStateRenderTarget and BlendStates to reflect this change. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 10 +++++----- sources/Zenith.NET.Metal/MTLFormats.cs | 10 +++++----- sources/Zenith.NET.Vulkan/VKFormats.cs | 10 +++++----- sources/Zenith.NET/BlendStates.cs | 4 ++-- .../{ColorComponentFlags.cs => ColorWriteMask.cs} | 2 +- sources/Zenith.NET/Structs/BlendStateRenderTarget.cs | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) rename sources/Zenith.NET/Enums/{ColorComponentFlags.cs => ColorWriteMask.cs} (84%) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 4e5eaa97..9b891a7e 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -380,26 +380,26 @@ public static DxBlendOp DirectX12(BlendOp blendOp) }; } - public static ColorWriteEnable DirectX12(ColorComponentFlags colorComponentFlags) + public static ColorWriteEnable DirectX12(ColorWriteMask writeMask) { ColorWriteEnable result = ColorWriteEnable.None; - if (colorComponentFlags.HasFlag(ColorComponentFlags.Red)) + if (writeMask.HasFlag(ColorWriteMask.Red)) { result |= ColorWriteEnable.Red; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Green)) + if (writeMask.HasFlag(ColorWriteMask.Green)) { result |= ColorWriteEnable.Green; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Blue)) + if (writeMask.HasFlag(ColorWriteMask.Blue)) { result |= ColorWriteEnable.Blue; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Alpha)) + if (writeMask.HasFlag(ColorWriteMask.Alpha)) { result |= ColorWriteEnable.Alpha; } diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 56a7f965..42842038 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -321,26 +321,26 @@ public static MTLBlendOperation Metal(BlendOp blendOp) }; } - public static MTLColorWriteMask Metal(ColorComponentFlags colorComponentFlags) + public static MTLColorWriteMask Metal(ColorWriteMask writeMask) { MTLColorWriteMask result = MTLColorWriteMask.None; - if (colorComponentFlags.HasFlag(ColorComponentFlags.Red)) + if (writeMask.HasFlag(ColorWriteMask.Red)) { result |= MTLColorWriteMask.Red; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Green)) + if (writeMask.HasFlag(ColorWriteMask.Green)) { result |= MTLColorWriteMask.Green; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Blue)) + if (writeMask.HasFlag(ColorWriteMask.Blue)) { result |= MTLColorWriteMask.Blue; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Alpha)) + if (writeMask.HasFlag(ColorWriteMask.Alpha)) { result |= MTLColorWriteMask.Alpha; } diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index fc83082d..515a9f05 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -500,26 +500,26 @@ public static VkBlendOp Vulkan(BlendOp blendOp) }; } - public static VkColorComponentFlags Vulkan(ColorComponentFlags colorComponentFlags) + public static VkColorComponentFlags Vulkan(ColorWriteMask writeMask) { VkColorComponentFlags result = VkColorComponentFlags.None; - if (colorComponentFlags.HasFlag(ColorComponentFlags.Red)) + if (writeMask.HasFlag(ColorWriteMask.Red)) { result |= VkColorComponentFlags.RBit; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Green)) + if (writeMask.HasFlag(ColorWriteMask.Green)) { result |= VkColorComponentFlags.GBit; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Blue)) + if (writeMask.HasFlag(ColorWriteMask.Blue)) { result |= VkColorComponentFlags.BBit; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Alpha)) + if (writeMask.HasFlag(ColorWriteMask.Alpha)) { result |= VkColorComponentFlags.ABit; } diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs index 75a0d8d9..183c16d0 100644 --- a/sources/Zenith.NET/BlendStates.cs +++ b/sources/Zenith.NET/BlendStates.cs @@ -14,7 +14,7 @@ public static class BlendStates SrcBlendAlpha = Blend.One, DestBlendAlpha = Blend.Zero, BlendOpAlpha = BlendOp.Add, - Flags = ColorComponentFlags.All + WriteMask = ColorWriteMask.All } }; @@ -67,7 +67,7 @@ public static class BlendStates RenderTarget0 = Default.RenderTarget0 with { BlendEnable = true, - Flags = ColorComponentFlags.None + WriteMask = ColorWriteMask.None } }; } diff --git a/sources/Zenith.NET/Enums/ColorComponentFlags.cs b/sources/Zenith.NET/Enums/ColorWriteMask.cs similarity index 84% rename from sources/Zenith.NET/Enums/ColorComponentFlags.cs rename to sources/Zenith.NET/Enums/ColorWriteMask.cs index ba1b463d..cb5692c6 100644 --- a/sources/Zenith.NET/Enums/ColorComponentFlags.cs +++ b/sources/Zenith.NET/Enums/ColorWriteMask.cs @@ -1,7 +1,7 @@ namespace Zenith.NET; [Flags] -public enum ColorComponentFlags +public enum ColorWriteMask { None = 0, diff --git a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs index df35c77f..f7330c38 100644 --- a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs +++ b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs @@ -16,5 +16,5 @@ public record struct BlendStateRenderTarget public BlendOp BlendOpAlpha; - public ColorComponentFlags Flags; + public ColorWriteMask WriteMask; } From 5382ea74213b988dda759300f745171627b86d44 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 15:24:22 +0800 Subject: [PATCH 046/466] Update RHI redesign drafts: clarify headers and scope Aligned document headers in both English and Chinese drafts to clarify working draft status, rebuild context, and deferred validation-layer design. No technical or API changes; updates are limited to introductory and organizational comments for clarity and consistency. --- rhi-redesign.draft.md | 725 --------------------------------------- rhi-redesign.zh.draft.md | 725 --------------------------------------- 2 files changed, 1450 deletions(-) delete mode 100644 rhi-redesign.draft.md delete mode 100644 rhi-redesign.zh.draft.md diff --git a/rhi-redesign.draft.md b/rhi-redesign.draft.md deleted file mode 100644 index 5473298d..00000000 --- a/rhi-redesign.draft.md +++ /dev/null @@ -1,725 +0,0 @@ -# RHI Redesign — Working Draft - -> Working spec for the next public surface. Not for commit. Mirrors `rhi-redesign.zh.draft.md`. -> Rebuilt against the current `Zenith.NET`, `Extensions`, and `Views` folders. Validation-layer design is intentionally deferred. -> The document stays organized as nine numbered requirements; shared conventions live in §0. - -## 0. Common Conventions - -### 0.1 Naming and Code Style - -- Every public value type is a `record struct` with public mutable fields. -- Multi-element inputs use `ReadOnlySpan` / `params ReadOnlySpan`. No `T[]`, `IEnumerable`, or `IReadOnlyList` on the low-level surface. -- Every byte-denominated field or parameter uses the `*InBytes` suffix. -- Backend hooks use plain `protected abstract` members; wrappers above them are only for validation or convenience. -- Public convenience wrappers may remain when they do not obscure the low-level model, for example `Submit(bool waitForCompletion = false)` on top of the timeline submit path. - -### 0.2 Public Surface Boundaries - -- `BufferView` stays removed. Buffer subranges are expressed as `BufferRange`. -- `TextureView` stays a long-lived `GraphicsResource` created by `GraphicsContext.CreateTextureView(...)`. -- `ResourceTable` stays a long-lived `GraphicsResource` with explicit typed `Write(...)` overloads. -- There is no public `FrameBuffer` / `RenderPassInfo` object. Render passes are inline. -- CPU upload and readback are described with `BufferData`, `TextureData`, and `TextureDataLayout`. -- The view layer renders into a `Texture target`; the platform view decides whether that target is a swapchain backbuffer or a CPU-readback texture. -- This round only adds the missing low-level synchronization pieces: explicit texture layout transitions, a lightweight `PipelineBarrier` for read-write hazards, queue timelines, and a completed `NativeObjectType`. -- Residency, heap management, and validation-layer redesign remain out of scope. - -### 0.3 Core Object Skeleton - -```csharp -public abstract class GraphicsContext : DisposableObject, INativeObject -{ - public Backend Backend { get; } - - public Capabilities Capabilities { get; } - - public CommandQueue Graphics { get; } - - public CommandQueue Compute { get; } - - public CommandQueue Copy { get; } - - public SwapChain CreateSwapChain(SwapChainDesc desc); - - public Buffer CreateBuffer(BufferDesc desc); - - public Texture CreateTexture(TextureDesc desc); - - public TextureView CreateTextureView(TextureViewDesc desc); - - public ResourceTable CreateResourceTable(ResourceTableDesc desc); - - public abstract nint GetNativeObject(NativeObjectType type); -} - -public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject -{ - public GraphicsContext Context { get; } = context; - - public abstract nint GetNativeObject(NativeObjectType type); -} - -public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) -{ - public ref readonly BufferDesc Desc => ref desc; - - public abstract MappedMemory Map(); - - public abstract void Unmap(); - - public void Upload(uint offsetInBytes, BufferData data); - - public void Download(uint offsetInBytes, BufferData data); -} - -public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) -{ - public ref readonly TextureDesc Desc => ref desc; - - public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - - public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); -} -``` - -### 0.4 Explicit CPU Data Descriptors - -```csharp -public record struct BufferData -{ - public nint Pointer; - - public uint SizeInBytes; -} - -public record struct TextureData -{ - public nint Pointer; - - public TextureDataLayout Layout; -} - -public record struct TextureDataLayout -{ - public uint SizeInBytes; - - public uint RowPitchInBytes; - - public uint SlicePitchInBytes; -} -``` - -Design points: -- `TextureDataLayout` is a pure layout descriptor. It does not carry offsets. -- Byte offsets stay on copy entry points such as `CopyBufferToTexture(..., uint srcOffsetInBytes, TextureDataLayout srcLayout, ...)`. -- Tight layouts can be computed with `ZenithHelper`, but callers remain responsible for the final `SizeInBytes` / `RowPitchInBytes` / `SlicePitchInBytes` they pass. -- Backend-specific copy alignment remains backend-local; it is not lifted back into `GraphicsContext`. - ---- - -## 1. ReadOnlySpan at Multi-element Boundaries - -Requirement: every multi-element public input keeps using `ReadOnlySpan` / `params ReadOnlySpan`. - -Representative surfaces: - -| Surface | Shape | -|---|---| -| Render pass color attachments | `ReadOnlySpan` | -| Viewports / scissors | `ReadOnlySpan` / `ReadOnlySpan` | -| ResourceTable array writes | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | -| Timeline waits | `params ReadOnlySpan` | -| Batch transitions | `ReadOnlySpan` | - -Rationale: -- Accepts stack, array, and single-element inputs uniformly. -- Matches the current `BeginRenderPass`, `SetScissors`, `SetViewports`, and `ResourceTable.Write` family. -- Keeps the future timeline and transition APIs allocation-free at call sites. - -Exception: -- Returning multiple elements as `ReadOnlySpan` still implies an owner. Concrete return types remain acceptable when they avoid hidden lifetime coupling. - ---- - -## 2. Explicit Upload / Download / Copy Model - -Requirement: CPU data movement remains descriptor-based. Public texture upload does not move back to `ReadOnlySpan`-shaped APIs. - -```csharp -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public void Upload(Buffer buffer, uint offsetInBytes, BufferData data); - - public void Download(Buffer buffer, uint offsetInBytes, BufferData data); - - public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - - public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - - public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); - - public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); - - public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent); - - public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout); -} -``` - -Design points: -- `Buffer.Upload` / `Buffer.Download` short-circuit to `Map()` / `Unmap()` when the buffer is CPU-visible; otherwise they stage through `Context.Copy`. -- `Texture.Upload` / `Texture.Download` always go through `CommandBuffer`, which keeps the copy path explicit and backend-controlled. -- Offsets and pitches are intentionally separate. `TextureDataLayout` describes memory layout; `offsetInBytes` chooses where inside the staging or destination buffer the copy starts. -- `Extensions.ImageSharp` and `Views.Avalonia.Surface` already follow this model today. - ---- - -## 3. Subresource, Offset, Extent, and Buffer Range Values - -Requirement: subresource and range inputs remain small value types, aligned with the current code and explicit copy/render surfaces. - -```csharp -public record struct TextureSubresource -{ - public uint MipLevel; - - public uint ArrayLayer; -} - -public record struct TextureSubresourceLayers -{ - public uint MipLevel; - - public uint BaseArrayLayer; - - public uint LayerCount; -} - -public record struct TextureSubresourceRange -{ - public uint BaseMipLevel; - - public uint LevelCount; - - public uint BaseArrayLayer; - - public uint LayerCount; -} - -public record struct Offset3D -{ - public uint X; - - public uint Y; - - public uint Z; -} - -public record struct Extent3D -{ - public uint Width; - - public uint Height; - - public uint Depth; -} - -public record struct BufferRange -{ - public Buffer Buffer; - - public ulong OffsetInBytes; - - public ulong SizeInBytes; - - public uint StrideInBytes; - - public static implicit operator BufferRange(Buffer buffer) - { - return new() - { - Buffer = buffer, - OffsetInBytes = 0, - SizeInBytes = buffer.Desc.SizeInBytes, - StrideInBytes = buffer.Desc.StrideInBytes - }; - } -} -``` - -Conventions: -- The public surface carries no explicit aspect flag. Backends derive color / depth / stencil from `Texture.Desc.Format`. -- Cube faces remain linearized through `ArrayLayer = cubeIndex * 6 + face`. -- `BufferRange` is the only public buffer subrange shape. No `BufferView` factory comes back. - ---- - -## 4. TextureView Remains a Long-lived Resource - -Requirement: keep `TextureView` as an owned resource object. The old "call-site value only" direction is not aligned with the current binding model or extension usage. - -```csharp -public record struct TextureViewDesc -{ - public Texture Texture; - - public TextureType Type; - - public PixelFormat Format; - - public TextureSubresourceRange Range; -} - -public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) : GraphicsResource(context) -{ - public ref readonly TextureViewDesc Desc => ref desc; -} -``` - -Design points: -- `GraphicsContext.CreateTextureView(...)` stays on the public surface. -- Binding a plain `Texture` remains the default whole-resource path. -- Binding a `TextureView` is reserved for explicit subresource, dimensionality, or format reinterpretation. -- This matches the current `ImGui` renderer, which keeps view objects alive and binds them through `ResourceTable`. -- Backends are free to let `Texture` carry a default native view internally, but the explicit `TextureView` object remains the public escape hatch. - ---- - -## 5. Flat Binding Model: ResourceBinding[] + ResourceTable - -Requirement: the binding model remains flat and concrete: `ResourceBinding[]` describes slots, `ResourceTable` holds values, and `Write(...)` stays typed by resource kind. - -```csharp -public enum ResourceType -{ - ConstantBuffer, - StructuredBuffer, - StructuredBufferReadWrite, - Texture, - TextureReadWrite, - Sampler, - AccelerationStructure -} - -public record struct ResourceBinding -{ - public ResourceType Type; - - public uint Count; -} - -public record struct ResourceTableDesc -{ - public ResourceBinding[] Bindings; -} - -public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc desc) : GraphicsResource(context) -{ - public ref readonly ResourceTableDesc Desc => ref desc; - - public abstract void Write(uint binding, Buffer buffer); - - public abstract void Write(uint binding, BufferRange bufferRange); - - public abstract void Write(uint binding, Texture texture); - - public abstract void Write(uint binding, TextureView textureView); - - public abstract void Write(uint binding, Sampler sampler); - - public abstract void Write(uint binding, TopLevelAccelerationStructure topLevelAccelerationStructure); - - public abstract void Write(uint binding, ReadOnlySpan buffers); - - public abstract void Write(uint binding, ReadOnlySpan bufferRanges); - - public abstract void Write(uint binding, ReadOnlySpan textures); - - public abstract void Write(uint binding, ReadOnlySpan textureViews); - - public abstract void Write(uint binding, ReadOnlySpan samplers); - - public abstract void Write(uint binding, ReadOnlySpan topLevelAccelerationStructures); -} -``` - -CommandBuffer-side entry points stay simple: - -```csharp -public void SetPipeline(GraphicsPipeline pipeline); - -public void SetPipeline(ComputePipeline pipeline); - -public void SetPipeline(MeshShadingPipeline pipeline); - -public void PushResourceTable(ResourceTable resourceTable); -``` - -Design points: -- There is no separate `ResourceLayout` object. -- There is no `IBindableResource` polymorphic entry. -- `ResourceBindings` on pipeline descriptors and `Bindings` on `ResourceTableDesc` intentionally share the same flat shape. -- `Texture` and `TextureView` both remain first-class write targets because "default whole-resource binding" and "explicit view binding" are both used in current code. -- Record all `Write(...)` operations before `PushResourceTable(...)`; the backend binds the table's current contents against the current pipeline. -- This maps cleanly to DX12 descriptor tables, Vulkan push descriptors, and Metal encoder-side resource table binding. - ---- - -## 6. Inline RenderPass, Output, SwapChain Targets, and View Integration - -Requirement: render targets stay inline, and the view layer keeps the "render into a `Texture target`" contract. - -```csharp -public record struct Output -{ - public PixelFormat[] ColorAttachments; - - public PixelFormat? DepthStencilAttachment; - - public SampleCount SampleCount; -} - -public record struct ColorAttachment -{ - public Texture Texture; - - public TextureSubresource Subresource; - - public Texture? ResolveTexture; - - public TextureSubresource ResolveSubresource; - - public LoadAction LoadAction; - - public StoreAction StoreAction; - - public Vector4 ClearColor; -} - -public record struct DepthStencilAttachment -{ - public Texture Texture; - - public TextureSubresource Subresource; - - public LoadAction DepthLoadAction; - - public StoreAction DepthStoreAction; - - public LoadAction StencilLoadAction; - - public StoreAction StencilStoreAction; - - public float ClearDepth; - - public byte ClearStencil; -} - -public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) -{ - public ref readonly SwapChainDesc Desc => ref desc; - - public abstract Texture CurrentColorTarget { get; } - - public abstract Texture? CurrentDepthStencilTarget { get; } - - public void Resize(uint width, uint height); - - public void Refresh(Surface surface); -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); - - public void EndRenderPass(); - - public void SetScissors(ReadOnlySpan scissors); - - public void SetViewports(ReadOnlySpan viewports); -} -``` - -Design points: -- No `FrameBuffer` object comes back. -- `Output` continues to live on pipeline descriptors and describes attachment compatibility, not a runtime render target object. -- `BeginRenderPass(...)` seeds default scissors / viewports from the attachment size, and callers can override them afterward. -- `SwapChain.CurrentColorTarget` and `CurrentDepthStencilTarget` behave like ordinary `Texture` instances. -- `RenderEventArgs` remains `Texture`-centric: view code passes a target texture into user rendering rather than leaking platform swapchain details into the callback. -- The view layer supports both patterns that already exist in the repo: - - swapchain-backed targets in WinForms / WPF / WinUI-style views - - offscreen target + `Texture.Download(...)` present paths such as Avalonia - ---- - -## 7. Explicit Texture Layout Transitions + Lightweight PipelineBarrier - -Requirement: add the missing explicit texture layout transition interface and a minimal pipeline barrier for read-write shader hazards. Do not bring back the old full `(stage, access)` matrix. - -```csharp -public enum TextureLayout -{ - Undefined, - Common, - ShaderResource, - UnorderedAccess, - RenderTarget, - DepthStencilRead, - DepthStencilWrite, - CopySource, - CopyDestination, - ResolveSource, - ResolveDestination, - Present -} - -public record struct TextureTransition -{ - public Texture Texture; - - public TextureSubresourceRange Range; - - public TextureLayout Before; - - public TextureLayout After; -} - -public enum PipelineBarrierScope -{ - All, - Draw, - Compute, - MeshShading, - RayTracing, - Copy, - AccelerationStructureBuild -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public void Transition(Texture texture, TextureSubresourceRange range, TextureLayout before, TextureLayout after); - - public void Transition(TextureView textureView, TextureLayout before, TextureLayout after); - - public void Transition(ReadOnlySpan transitions); - - public void PipelineBarrier(PipelineBarrierScope before, PipelineBarrierScope after); -} -``` - -Design points: -- `Transition(...)` is the explicit texture state / layout interface. No hidden layout changes remain in copy, sampling, render-pass, or present flows. -- `TextureView` overloads reuse `textureView.Desc.Range`; `textureView.Desc.Format` does not affect layout selection. -- `PipelineBarrier(...)` is the intentionally small UAV-style ordering primitive for cases like "dispatch A writes `RWTexture` / `RWBuffer`, dispatch B touches it again". It does not change layouts and it does not cross queues. -- No public `BufferBarrier` is added in this round. Buffer copy / bind ordering continues to follow command semantics, and explicit user-authored synchronization is reserved for layout transitions and shader read-write hazard isolation. -- Backend mapping is straightforward: - - DX12: resource-state transitions plus UAV barriers - - Vulkan: image memory barriers plus lightweight memory barriers - - Metal: texture usage-state transitions plus encoder memory barriers / fences as needed - ---- - -## 8. Timeline-based Cross-queue Synchronization - -Requirement: add a canonical timeline submission model without removing the current convenience wrappers. - -```csharp -public enum CommandQueueType -{ - Graphics, - Compute, - Copy -} - -public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) -{ - public bool IsEmpty => Queue is null; - - public void Wait() - { - Queue?.Wait(Value); - } -} - -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) -{ - public CommandQueueType Type { get; } = type; - - public CommandBuffer CommandBuffer(); - - public void WaitIdle(); - - public void Wait(ulong value); - - public abstract ulong GetCompletedValue(); -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public CommandQueue Queue { get; } = queue; - - public CommandSubmission Submit(params ReadOnlySpan waits); - - public void Submit(bool waitForCompletion = false); -} - -public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) -{ - public abstract CommandSubmission Present(params ReadOnlySpan waits); - - public abstract void Present(); -} -``` - -Design points: -- The timeline path is the canonical low-level model: `Submit(waits)` and `Present(waits)` both return a `CommandSubmission`. -- `Submit(bool waitForCompletion = false)` and parameterless `Present()` stay as convenience wrappers for the current one-queue, one-frame-loop usage already present in `Extensions` and `Views`. -- Each queue owns one monotonic completion timeline. -- `default` / empty submissions are ignored in wait sets. -- `Present(waits)` returns the point at which the presented backbuffer becomes writable again. -- Backend mapping follows the natural primitive on each API: - - DX12: one `ID3D12Fence` per queue - - Vulkan: one timeline `VkSemaphore` per queue - - Metal 4: one `MTLSharedEvent` per queue - ---- - -## 9. Unified Native Handles and a Completed NativeObjectType - -Requirement: every public object that wraps a native object keeps using `INativeObject`, and `NativeObjectType` is completed around stable native roles rather than backend-specific cast helpers. - -```csharp -public enum NativeObjectType -{ - // DirectX 12 / DXGI - DxgiFactory, - DxgiAdapter, - DxgiSwapChain, - D3D12Device, - D3D12CommandQueue, - D3D12Fence, - D3D12GraphicsCommandList, - D3D12PipelineState, - D3D12RootSignature, - D3D12Resource, - D3D12DescriptorHeap, - D3D12CpuDescriptorHandleRtv, - D3D12CpuDescriptorHandleDsv, - D3D12CpuDescriptorHandleSrv, - D3D12CpuDescriptorHandleUav, - D3D12CpuDescriptorHandleSampler, - D3D12QueryHeap, - - // Metal / Metal 4 - MtlDevice, - Mtl4CommandQueue, - Mtl4CommandBuffer, - MtlSharedEvent, - MtlTexture, - MtlBuffer, - MtlSamplerState, - MtlRenderPipelineState, - MtlComputePipelineState, - MtlDepthStencilState, - MtlHeap, - MtlAccelerationStructure, - CaMetalLayer, - CaMetalDrawable, - - // Vulkan - VkInstance, - VkPhysicalDevice, - VkSurfaceKHR, - VkSwapchainKHR, - VkDevice, - VkQueue, - VkQueueFamilyIndex, - VkSemaphore, - VkCommandBuffer, - VkPipeline, - VkPipelineLayout, - VkShaderModule, - VkImage, - VkImageView, - VkBuffer, - VkSampler, - VkDescriptorSet, - VkQueryPool, - VkAccelerationStructureKHR -} - -public interface INativeObject -{ - /// Returns 0 when the requested native role does not match this object or the active backend. - nint GetNativeObject(NativeObjectType type); -} -``` - -Design points: -- The enum is organized around stable native roles that map cleanly to current Zenith object categories. -- One public object may expose several native roles. For example, a DX12 pipeline may expose both `D3D12PipelineState` and `D3D12RootSignature`. -- Several Zenith object kinds may expose the same native role. For example, DX12 buffers, textures, and acceleration structures all surface `D3D12Resource`. -- Non-pointer scalars such as `VkQueueFamilyIndex` still flow through the same `nint` entry point. -- `GetNativeObject(...)` does not add reference counts or retain ownership. Returned handles are only valid while the Zenith object is alive. -- Returning `0` is the only degradation path; unsupported combinations do not throw. - ---- - -## Appendix A: Current Upload Path - -```csharp -CommandBuffer commandBuffer = context.Copy.CommandBuffer(); - -commandBuffer.Upload(texture, - default, - default, - new() { Width = width, Height = height, Depth = 1 }, - new() - { - Pointer = pixels, - Layout = new() - { - SizeInBytes = sizeInBytes, - RowPitchInBytes = rowPitchInBytes, - SlicePitchInBytes = slicePitchInBytes - } - }); - -commandBuffer.Submit(true); -``` - -This matches the current `ImageSharp` extension and the current offscreen-view presentation path. - -## Appendix B: Current Binding Path - -```csharp -ResourceBinding[] bindings = -[ - new() { Type = ResourceType.ConstantBuffer, Count = 1 }, - new() { Type = ResourceType.Texture, Count = 1 }, - new() { Type = ResourceType.Sampler, Count = 1 } -]; - -ResourceTable table = context.CreateResourceTable(new() { Bindings = bindings }); -table.Write(0, constants); -table.Write(1, textureView); -table.Write(2, sampler); - -commandBuffer.SetPipeline(pipeline); -commandBuffer.PushResourceTable(table); -``` - -This matches the current `ImGui` extension: long-lived `ResourceTable`, explicit `Write(...)`, then `PushResourceTable(...)` under the current pipeline. - -## Appendix C: Timeline-shaped Multi-queue Flow - -```csharp -CommandSubmission copyDone = copyCommandBuffer.Submit(); - -CommandSubmission graphicsDone = graphicsCommandBuffer.Submit(copyDone); - -CommandSubmission presentDone = swapChain.Present(graphicsDone); -``` - -The existing `Submit(true)` / `Present()` convenience calls remain valid for the simple single-queue path; `CommandSubmission` only becomes necessary once work crosses queues or frames. \ No newline at end of file diff --git a/rhi-redesign.zh.draft.md b/rhi-redesign.zh.draft.md deleted file mode 100644 index a4c07380..00000000 --- a/rhi-redesign.zh.draft.md +++ /dev/null @@ -1,725 +0,0 @@ -# RHI 重设计草案 — 工作稿 - -> 下一版公共面的工作草案,不进入提交。英文同步版见 `rhi-redesign.draft.md`。 -> 本稿依据当前 `Zenith.NET`、`Extensions` 与 `Views` 目录重新整理。验证层设计明确延后处理。 -> 文档仍按 9 条需求组织,共享约定放在 §0。 - -## 0. 公共约定 - -### 0.1 命名与代码风格 - -- 所有公共值类型一律为 `record struct`,公共字段可写。 -- 多元素输入使用 `ReadOnlySpan` / `params ReadOnlySpan`。低层公共面不使用 `T[]`、`IEnumerable`、`IReadOnlyList`。 -- 所有字节单位字段与参数统一使用 `*InBytes` 后缀。 -- 后端钩子保持普通 `protected abstract` 形式;其上的包装层只负责校验或便利调用。 -- 只要不遮蔽低层模型,就可以保留便利包装,例如时间线提交接口之上的 `Submit(bool waitForCompletion = false)`。 - -### 0.2 公共面边界 - -- `BufferView` 继续移除,buffer 子范围统一使用 `BufferRange` 表达。 -- `TextureView` 继续作为长生命周期 `GraphicsResource`,由 `GraphicsContext.CreateTextureView(...)` 创建。 -- `ResourceTable` 继续作为长生命周期 `GraphicsResource`,通过显式类型化 `Write(...)` 重载写入资源。 -- 公共面没有 `FrameBuffer` / `RenderPassInfo` 对象;RenderPass 保持内联表达。 -- CPU 上传与回读统一由 `BufferData`、`TextureData`、`TextureDataLayout` 描述。 -- Views 层统一向上层暴露 `Texture target`;具体平台 view 自己决定该 target 是 swapchain backbuffer 还是 CPU 回读纹理。 -- 本轮仅补齐缺失的低层同步能力:显式 texture layout 转换、面向读写 hazard 的轻量 `PipelineBarrier`、queue 时间线,以及完整的 `NativeObjectType`。 -- Residency、heap 管理、验证层重设计继续留在范围之外。 - -### 0.3 核心对象骨架 - -```csharp -public abstract class GraphicsContext : DisposableObject, INativeObject -{ - public Backend Backend { get; } - - public Capabilities Capabilities { get; } - - public CommandQueue Graphics { get; } - - public CommandQueue Compute { get; } - - public CommandQueue Copy { get; } - - public SwapChain CreateSwapChain(SwapChainDesc desc); - - public Buffer CreateBuffer(BufferDesc desc); - - public Texture CreateTexture(TextureDesc desc); - - public TextureView CreateTextureView(TextureViewDesc desc); - - public ResourceTable CreateResourceTable(ResourceTableDesc desc); - - public abstract nint GetNativeObject(NativeObjectType type); -} - -public abstract class GraphicsResource(GraphicsContext context) : DisposableObject, INativeObject -{ - public GraphicsContext Context { get; } = context; - - public abstract nint GetNativeObject(NativeObjectType type); -} - -public abstract class Buffer(GraphicsContext context, BufferDesc desc) : GraphicsResource(context) -{ - public ref readonly BufferDesc Desc => ref desc; - - public abstract MappedMemory Map(); - - public abstract void Unmap(); - - public void Upload(uint offsetInBytes, BufferData data); - - public void Download(uint offsetInBytes, BufferData data); -} - -public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) -{ - public ref readonly TextureDesc Desc => ref desc; - - public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - - public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); -} -``` - -### 0.4 显式 CPU 数据描述 - -```csharp -public record struct BufferData -{ - public nint Pointer; - - public uint SizeInBytes; -} - -public record struct TextureData -{ - public nint Pointer; - - public TextureDataLayout Layout; -} - -public record struct TextureDataLayout -{ - public uint SizeInBytes; - - public uint RowPitchInBytes; - - public uint SlicePitchInBytes; -} -``` - -设计要点: -- `TextureDataLayout` 是纯布局描述,不携带 offset。 -- 字节偏移保留在 copy 接口上,例如 `CopyBufferToTexture(..., uint srcOffsetInBytes, TextureDataLayout srcLayout, ...)`。 -- 紧凑布局可以通过 `ZenithHelper` 计算,但最终传入的 `SizeInBytes` / `RowPitchInBytes` / `SlicePitchInBytes` 仍由调用方负责。 -- 后端特有的 copy 对齐继续下沉到各后端内部,不再抬回 `GraphicsContext`。 - ---- - -## 1. `ReadOnlySpan` 作为多元素边界 - -需求:所有多元素公共输入继续统一使用 `ReadOnlySpan` / `params ReadOnlySpan`。 - -代表性接口: - -| 接口面 | 形状 | -|---|---| -| RenderPass 颜色附件 | `ReadOnlySpan` | -| Viewport / Scissor | `ReadOnlySpan` / `ReadOnlySpan` | -| ResourceTable 数组写入 | `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` / `ReadOnlySpan` | -| 时间线等待集 | `params ReadOnlySpan` | -| 批量转换 | `ReadOnlySpan` | - -理由: -- 同时接受栈上、数组以及单元素输入,调用形态统一。 -- 与当前 `BeginRenderPass`、`SetScissors`、`SetViewports`、`ResourceTable.Write` 家族保持一致。 -- 让未来的时间线接口和转换接口在调用现场保持零托管分配。 - -例外: -- 返回多个元素时,`ReadOnlySpan` 仍然要求一个显式持有者。只要能避免隐藏生命周期耦合,返回具体类型依然可以接受。 - ---- - -## 2. 显式 Upload / Download / Copy 模型 - -需求:CPU 数据搬运继续保持描述符化,公共 texture 上传接口不回退到 `ReadOnlySpan` 风格。 - -```csharp -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public void Upload(Buffer buffer, uint offsetInBytes, BufferData data); - - public void Download(Buffer buffer, uint offsetInBytes, BufferData data); - - public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - - public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data); - - public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); - - public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); - - public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent); - - public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout); -} -``` - -设计要点: -- `Buffer.Upload` / `Buffer.Download` 在 buffer 可 CPU 访问时直接走 `Map()` / `Unmap()`;否则通过 `Context.Copy` staging。 -- `Texture.Upload` / `Texture.Download` 始终通过 `CommandBuffer` 完成,以保持 copy 路径显式且可由后端控制。 -- offset 与 pitch 故意分离。`TextureDataLayout` 只描述内存布局,`offsetInBytes` 决定 copy 在 staging / 目标 buffer 中从哪里开始。 -- 当前 `Extensions.ImageSharp` 与 `Views.Avalonia.Surface` 已经在按这套模型使用。 - ---- - -## 3. 子资源、偏移、尺寸与 BufferRange 值类型 - -需求:子资源与范围相关输入保持为小型值类型,并与当前 copy / render 接口对齐。 - -```csharp -public record struct TextureSubresource -{ - public uint MipLevel; - - public uint ArrayLayer; -} - -public record struct TextureSubresourceLayers -{ - public uint MipLevel; - - public uint BaseArrayLayer; - - public uint LayerCount; -} - -public record struct TextureSubresourceRange -{ - public uint BaseMipLevel; - - public uint LevelCount; - - public uint BaseArrayLayer; - - public uint LayerCount; -} - -public record struct Offset3D -{ - public uint X; - - public uint Y; - - public uint Z; -} - -public record struct Extent3D -{ - public uint Width; - - public uint Height; - - public uint Depth; -} - -public record struct BufferRange -{ - public Buffer Buffer; - - public ulong OffsetInBytes; - - public ulong SizeInBytes; - - public uint StrideInBytes; - - public static implicit operator BufferRange(Buffer buffer) - { - return new() - { - Buffer = buffer, - OffsetInBytes = 0, - SizeInBytes = buffer.Desc.SizeInBytes, - StrideInBytes = buffer.Desc.StrideInBytes - }; - } -} -``` - -约定: -- 公共面不携带显式 aspect 标记,后端从 `Texture.Desc.Format` 推断 color / depth / stencil。 -- Cube face 继续通过 `ArrayLayer = cubeIndex * 6 + face` 线性化。 -- `BufferRange` 是唯一公共 buffer 子范围形态,不再恢复 `BufferView` 工厂。 - ---- - -## 4. TextureView 继续作为长生命周期资源 - -需求:`TextureView` 保持为独立持有的资源对象。旧草案里“仅作为调用现场值”的方向已经不再符合当前绑定模型和扩展层用法。 - -```csharp -public record struct TextureViewDesc -{ - public Texture Texture; - - public TextureType Type; - - public PixelFormat Format; - - public TextureSubresourceRange Range; -} - -public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) : GraphicsResource(context) -{ - public ref readonly TextureViewDesc Desc => ref desc; -} -``` - -设计要点: -- `GraphicsContext.CreateTextureView(...)` 继续保留在公共面上。 -- 直接绑定 `Texture` 仍然是默认的整资源路径。 -- 绑定 `TextureView` 只用于显式子资源、维度切换或格式重解释。 -- 这与当前 `ImGui` 渲染器一致:它会长期持有 view 对象,并通过 `ResourceTable` 绑定。 -- 后端内部可以让 `Texture` 自带默认 native view,但显式 `TextureView` 仍然是公共逃生口。 - ---- - -## 5. 平铺绑定模型:`ResourceBinding[]` + `ResourceTable` - -需求:绑定模型保持扁平且具体:`ResourceBinding[]` 描述槽位,`ResourceTable` 保存资源值,`Write(...)` 继续按资源种类分类型重载。 - -```csharp -public enum ResourceType -{ - ConstantBuffer, - StructuredBuffer, - StructuredBufferReadWrite, - Texture, - TextureReadWrite, - Sampler, - AccelerationStructure -} - -public record struct ResourceBinding -{ - public ResourceType Type; - - public uint Count; -} - -public record struct ResourceTableDesc -{ - public ResourceBinding[] Bindings; -} - -public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc desc) : GraphicsResource(context) -{ - public ref readonly ResourceTableDesc Desc => ref desc; - - public abstract void Write(uint binding, Buffer buffer); - - public abstract void Write(uint binding, BufferRange bufferRange); - - public abstract void Write(uint binding, Texture texture); - - public abstract void Write(uint binding, TextureView textureView); - - public abstract void Write(uint binding, Sampler sampler); - - public abstract void Write(uint binding, TopLevelAccelerationStructure topLevelAccelerationStructure); - - public abstract void Write(uint binding, ReadOnlySpan buffers); - - public abstract void Write(uint binding, ReadOnlySpan bufferRanges); - - public abstract void Write(uint binding, ReadOnlySpan textures); - - public abstract void Write(uint binding, ReadOnlySpan textureViews); - - public abstract void Write(uint binding, ReadOnlySpan samplers); - - public abstract void Write(uint binding, ReadOnlySpan topLevelAccelerationStructures); -} -``` - -CommandBuffer 侧入口继续保持简洁: - -```csharp -public void SetPipeline(GraphicsPipeline pipeline); - -public void SetPipeline(ComputePipeline pipeline); - -public void SetPipeline(MeshShadingPipeline pipeline); - -public void PushResourceTable(ResourceTable resourceTable); -``` - -设计要点: -- 没有单独的 `ResourceLayout` 对象。 -- 没有 `IBindableResource` 多态入口。 -- Pipeline 描述里的 `ResourceBindings` 与 `ResourceTableDesc` 里的 `Bindings` 故意保持同一扁平形状。 -- `Texture` 与 `TextureView` 都继续是一等写入目标,因为“默认整资源绑定”和“显式 view 绑定”在当前代码里都是真实存在的需求。 -- 先完成所有 `Write(...)`,再调用 `PushResourceTable(...)`;后端在 push 时按当前 pipeline 绑定这张表的当前内容。 -- 该模型可以自然映射到 DX12 descriptor table、Vulkan push descriptor,以及 Metal encoder 侧的资源表绑定。 - ---- - -## 6. 内联 RenderPass、Output、SwapChain 目标与 Views 集成 - -需求:render target 继续保持内联表达,Views 层继续保持“渲染到一个 `Texture target`”的契约。 - -```csharp -public record struct Output -{ - public PixelFormat[] ColorAttachments; - - public PixelFormat? DepthStencilAttachment; - - public SampleCount SampleCount; -} - -public record struct ColorAttachment -{ - public Texture Texture; - - public TextureSubresource Subresource; - - public Texture? ResolveTexture; - - public TextureSubresource ResolveSubresource; - - public LoadAction LoadAction; - - public StoreAction StoreAction; - - public Vector4 ClearColor; -} - -public record struct DepthStencilAttachment -{ - public Texture Texture; - - public TextureSubresource Subresource; - - public LoadAction DepthLoadAction; - - public StoreAction DepthStoreAction; - - public LoadAction StencilLoadAction; - - public StoreAction StencilStoreAction; - - public float ClearDepth; - - public byte ClearStencil; -} - -public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) -{ - public ref readonly SwapChainDesc Desc => ref desc; - - public abstract Texture CurrentColorTarget { get; } - - public abstract Texture? CurrentDepthStencilTarget { get; } - - public void Resize(uint width, uint height); - - public void Refresh(Surface surface); -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); - - public void EndRenderPass(); - - public void SetScissors(ReadOnlySpan scissors); - - public void SetViewports(ReadOnlySpan viewports); -} -``` - -设计要点: -- 不恢复 `FrameBuffer` 对象。 -- `Output` 继续挂在 pipeline 描述上,负责描述附件兼容性,而不是承载运行时 render target 对象。 -- `BeginRenderPass(...)` 会先按附件尺寸写入默认 scissors / viewports,调用方后续仍可覆盖。 -- `SwapChain.CurrentColorTarget` 与 `CurrentDepthStencilTarget` 按普通 `Texture` 使用。 -- `RenderEventArgs` 继续以 `Texture` 为中心:view 代码把 target texture 交给用户渲染,而不是把平台 swapchain 细节泄漏进回调。 -- 当前仓库里已经存在两类 view 路径。 -- `WinForms` / `WPF` / `WinUI` 风格 view 直接渲染到 swapchain 支撑的 target。 -- `Avalonia` 风格 view 渲染到离屏 target,并通过 `Texture.Download(...)` 完成 present。 - ---- - -## 7. 显式 Texture Layout 转换 + 轻量 `PipelineBarrier` - -需求:补上缺失的显式 texture layout 转换接口,以及一个最小化的、面向读写 shader hazard 的 pipeline barrier;不再把旧的完整 `(stage, access)` 矩阵搬回公共面。 - -```csharp -public enum TextureLayout -{ - Undefined, - Common, - ShaderResource, - UnorderedAccess, - RenderTarget, - DepthStencilRead, - DepthStencilWrite, - CopySource, - CopyDestination, - ResolveSource, - ResolveDestination, - Present -} - -public record struct TextureTransition -{ - public Texture Texture; - - public TextureSubresourceRange Range; - - public TextureLayout Before; - - public TextureLayout After; -} - -public enum PipelineBarrierScope -{ - All, - Draw, - Compute, - MeshShading, - RayTracing, - Copy, - AccelerationStructureBuild -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public void Transition(Texture texture, TextureSubresourceRange range, TextureLayout before, TextureLayout after); - - public void Transition(TextureView textureView, TextureLayout before, TextureLayout after); - - public void Transition(ReadOnlySpan transitions); - - public void PipelineBarrier(PipelineBarrierScope before, PipelineBarrierScope after); -} -``` - -设计要点: -- `Transition(...)` 是显式 texture 状态 / layout 接口。Copy、采样、RenderPass、Present 流程里不再保留隐式 layout 变化。 -- `TextureView` 重载复用 `textureView.Desc.Range`;`textureView.Desc.Format` 不参与 layout 选择。 -- `PipelineBarrier(...)` 是故意做小的 UAV 风格顺序原语,用于“dispatch A 写 `RWTexture` / `RWBuffer`,dispatch B 又访问它”这类场景。它不改变 layout,也不跨 queue。 -- 本轮不增加公共 `BufferBarrier`。Buffer 的 copy / bind 顺序继续遵从命令语义,显式用户同步仅保留给 layout 转换和 shader 读写 hazard 隔离。 -- 后端映射很直接: -- DX12:resource state transition + UAV barrier -- Vulkan:image memory barrier + 轻量 memory barrier -- Metal:texture usage-state transition + encoder memory barrier / fence - ---- - -## 8. 基于时间线的跨 Queue 同步 - -需求:加入规范化的时间线提交模型,同时不删除当前已经在用的便利包装。 - -```csharp -public enum CommandQueueType -{ - Graphics, - Compute, - Copy -} - -public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) -{ - public bool IsEmpty => Queue is null; - - public void Wait() - { - Queue?.Wait(Value); - } -} - -public abstract class CommandQueue(GraphicsContext context, CommandQueueType type) : GraphicsResource(context) -{ - public CommandQueueType Type { get; } = type; - - public CommandBuffer CommandBuffer(); - - public void WaitIdle(); - - public void Wait(ulong value); - - public abstract ulong GetCompletedValue(); -} - -public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) -{ - public CommandQueue Queue { get; } = queue; - - public CommandSubmission Submit(params ReadOnlySpan waits); - - public void Submit(bool waitForCompletion = false); -} - -public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : GraphicsResource(context) -{ - public abstract CommandSubmission Present(params ReadOnlySpan waits); - - public abstract void Present(); -} -``` - -设计要点: -- 时间线路径是规范低层模型:`Submit(waits)` 与 `Present(waits)` 都返回一个 `CommandSubmission`。 -- `Submit(bool waitForCompletion = false)` 与无参 `Present()` 继续保留,服务当前 `Extensions` 与 `Views` 中已经存在的单 queue、单帧循环用法。 -- 每条 queue 持有一条单调递增完成时间线。 -- `default` / 空 `CommandSubmission` 会在等待集里被忽略。 -- `Present(waits)` 返回“当前呈现 backbuffer 重新可写”的那个时间线点。 -- 后端映射直接对应各 API 的自然原语: -- DX12:每条 queue 一条 `ID3D12Fence` -- Vulkan:每条 queue 一条 timeline `VkSemaphore` -- Metal 4:每条 queue 一条 `MTLSharedEvent` - ---- - -## 9. 统一 Native Handle 与完整 `NativeObjectType` - -需求:所有封装 native 对象的公共类型继续统一走 `INativeObject`,而 `NativeObjectType` 则按稳定 native 角色补齐,不再依赖后端特化 cast helper。 - -```csharp -public enum NativeObjectType -{ - // DirectX 12 / DXGI - DxgiFactory, - DxgiAdapter, - DxgiSwapChain, - D3D12Device, - D3D12CommandQueue, - D3D12Fence, - D3D12GraphicsCommandList, - D3D12PipelineState, - D3D12RootSignature, - D3D12Resource, - D3D12DescriptorHeap, - D3D12CpuDescriptorHandleRtv, - D3D12CpuDescriptorHandleDsv, - D3D12CpuDescriptorHandleSrv, - D3D12CpuDescriptorHandleUav, - D3D12CpuDescriptorHandleSampler, - D3D12QueryHeap, - - // Metal / Metal 4 - MtlDevice, - Mtl4CommandQueue, - Mtl4CommandBuffer, - MtlSharedEvent, - MtlTexture, - MtlBuffer, - MtlSamplerState, - MtlRenderPipelineState, - MtlComputePipelineState, - MtlDepthStencilState, - MtlHeap, - MtlAccelerationStructure, - CaMetalLayer, - CaMetalDrawable, - - // Vulkan - VkInstance, - VkPhysicalDevice, - VkSurfaceKHR, - VkSwapchainKHR, - VkDevice, - VkQueue, - VkQueueFamilyIndex, - VkSemaphore, - VkCommandBuffer, - VkPipeline, - VkPipelineLayout, - VkShaderModule, - VkImage, - VkImageView, - VkBuffer, - VkSampler, - VkDescriptorSet, - VkQueryPool, - VkAccelerationStructureKHR -} - -public interface INativeObject -{ - /// 当请求的 native 角色与当前对象或当前后端不匹配时,返回 0。 - nint GetNativeObject(NativeObjectType type); -} -``` - -设计要点: -- 该枚举围绕稳定的 native 角色组织,能够自然映射到当前 Zenith 的对象类别。 -- 一个公共对象可以暴露多个 native 角色。例如 DX12 pipeline 可以同时暴露 `D3D12PipelineState` 与 `D3D12RootSignature`。 -- 多个 Zenith 对象类别也可以复用同一个 native 角色。例如 DX12 buffer、texture、acceleration structure 都可通过 `D3D12Resource` 暴露。 -- `VkQueueFamilyIndex` 这类非指针标量也继续通过同一个 `nint` 入口承载。 -- `GetNativeObject(...)` 不增加引用计数,也不转移所有权;返回句柄只在 Zenith 对象存活期间有效。 -- 返回 `0` 是唯一退化路径,不支持的组合不抛异常。 - ---- - -## 附录 A:当前 Upload 路径 - -```csharp -CommandBuffer commandBuffer = context.Copy.CommandBuffer(); - -commandBuffer.Upload(texture, - default, - default, - new() { Width = width, Height = height, Depth = 1 }, - new() - { - Pointer = pixels, - Layout = new() - { - SizeInBytes = sizeInBytes, - RowPitchInBytes = rowPitchInBytes, - SlicePitchInBytes = slicePitchInBytes - } - }); - -commandBuffer.Submit(true); -``` - -这与当前 `ImageSharp` 扩展,以及当前离屏 view 的 present 路径一致。 - -## 附录 B:当前绑定路径 - -```csharp -ResourceBinding[] bindings = -[ - new() { Type = ResourceType.ConstantBuffer, Count = 1 }, - new() { Type = ResourceType.Texture, Count = 1 }, - new() { Type = ResourceType.Sampler, Count = 1 } -]; - -ResourceTable table = context.CreateResourceTable(new() { Bindings = bindings }); -table.Write(0, constants); -table.Write(1, textureView); -table.Write(2, sampler); - -commandBuffer.SetPipeline(pipeline); -commandBuffer.PushResourceTable(table); -``` - -这与当前 `ImGui` 扩展一致:长期持有 `ResourceTable`,显式 `Write(...)`,然后在当前 pipeline 下 `PushResourceTable(...)`。 - -## 附录 C:时间线形状的多 Queue 流程 - -```csharp -CommandSubmission copyDone = copyCommandBuffer.Submit(); - -CommandSubmission graphicsDone = graphicsCommandBuffer.Submit(copyDone); - -CommandSubmission presentDone = swapChain.Present(graphicsDone); -``` - -现有 `Submit(true)` / `Present()` 的便利调用在简单单 queue 路径下仍然有效;只有当工作跨 queue 或跨帧传播时,`CommandSubmission` 才成为必需。 \ No newline at end of file From a358204f341c8b2ffa2163669c78f4cd56385769 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 15:52:24 +0800 Subject: [PATCH 047/466] Rename 'destination' parameter to 'pointer' for clarity Renamed the 'destination' parameter and related fields to 'pointer' in Buffer and Lease.Borrow methods for improved consistency and clarity throughout the codebase. Updated all references accordingly. --- sources/Zenith.NET/Downloader.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index df789ded..f4639636 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -6,7 +6,7 @@ internal class Downloader(GraphicsContext context) : DisposableObject private readonly List available = []; private readonly Dictionary> borrowed = []; - public Buffer Buffer(CommandBuffer commandBuffer, nint destination, uint sizeInBytes) + public Buffer Buffer(CommandBuffer commandBuffer, nint pointer, uint sizeInBytes) { using Lock.Scope _ = @lock.EnterScope(); @@ -25,7 +25,7 @@ public Buffer Buffer(CommandBuffer commandBuffer, nint destination, uint sizeInB })); } - leases.Add(lease.Borrow(destination, sizeInBytes)); + leases.Add(lease.Borrow(pointer, sizeInBytes)); return lease.Buffer; } @@ -67,7 +67,7 @@ private void CleanupExpiredLeases() private class Lease(Buffer buffer) { private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); - private nint destination; + private nint pointer; private uint sizeInBytes; public Buffer Buffer { get; } = buffer; @@ -77,9 +77,9 @@ public bool HasCapacityFor(uint sizeInBytes) return Buffer.Desc.SizeInBytes >= sizeInBytes; } - public Lease Borrow(nint destination, uint sizeInBytes) + public Lease Borrow(nint pointer, uint sizeInBytes) { - this.destination = destination; + this.pointer = pointer; this.sizeInBytes = sizeInBytes; return this; @@ -91,7 +91,7 @@ public Lease Writeback() unsafe { - new ReadOnlySpan((void*)mappedMemory.Pointer, (int)sizeInBytes).CopyTo(new((void*)destination, (int)sizeInBytes)); + new ReadOnlySpan((void*)mappedMemory.Pointer, (int)sizeInBytes).CopyTo(new((void*)pointer, (int)sizeInBytes)); } Buffer.Unmap(); From 28d96895b240165d48baa6f5a3903b5ae51fec19 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 16:58:43 +0800 Subject: [PATCH 048/466] Add pointer and sizeInBytes fields to Lease class Added pointer (nint) and sizeInBytes (uint) fields to the Lease class. These fields are reset in the Renew() method to nint.Zero and 0, respectively, on each renewal. --- sources/Zenith.NET/Downloader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index f4639636..c4d2d2a3 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -67,6 +67,7 @@ private void CleanupExpiredLeases() private class Lease(Buffer buffer) { private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + private nint pointer; private uint sizeInBytes; @@ -115,6 +116,9 @@ public Lease Renew() { expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + pointer = nint.Zero; + sizeInBytes = 0; + return this; } From 3dc9d93c7c8b282f4e00d651d67e57b15a975fdb Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 18:23:08 +0800 Subject: [PATCH 049/466] Refactor buffer download logic to use BufferData struct Simplify buffer download methods by replacing separate pointer and size parameters with a BufferData struct. Update Downloader and Lease classes to accept and store BufferData, improving code clarity and maintainability. --- sources/Zenith.NET/CommandBuffer.cs | 4 ++-- sources/Zenith.NET/Downloader.cs | 28 +++++++++------------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 1737ba1c..2a90ad46 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -41,7 +41,7 @@ public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { - Buffer temporary = Context.Downloader.Buffer(this, data.Pointer, data.SizeInBytes); + Buffer temporary = Context.Downloader.Buffer(this, data); CopyBuffer(buffer, offsetInBytes, temporary, 0, data.SizeInBytes); } @@ -56,7 +56,7 @@ public void Upload(Texture texture, TextureSubresource subresource, Offset3D off public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer temporary = Context.Downloader.Buffer(this, data.Pointer, data.Layout.SizeInBytes); + Buffer temporary = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); CopyTextureToBuffer(texture, subresource, offset, extent, temporary, 0, data.Layout); } diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index c4d2d2a3..1a9a193d 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -6,7 +6,7 @@ internal class Downloader(GraphicsContext context) : DisposableObject private readonly List available = []; private readonly Dictionary> borrowed = []; - public Buffer Buffer(CommandBuffer commandBuffer, nint pointer, uint sizeInBytes) + public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) { using Lock.Scope _ = @lock.EnterScope(); @@ -15,17 +15,17 @@ public Buffer Buffer(CommandBuffer commandBuffer, nint pointer, uint sizeInBytes borrowed[commandBuffer] = leases = []; } - if (!(available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) + if (!(available.Where(item => item.HasCapacityFor(data.SizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) { lease = new(context.CreateBuffer(new() { - SizeInBytes = sizeInBytes, + SizeInBytes = data.SizeInBytes, StrideInBytes = 1, Flags = BufferUsageFlags.MapRead })); } - leases.Add(lease.Borrow(pointer, sizeInBytes)); + leases.Add(lease.Borrow(data)); return lease.Buffer; } @@ -68,8 +68,7 @@ private class Lease(Buffer buffer) { private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); - private nint pointer; - private uint sizeInBytes; + private BufferData data; public Buffer Buffer { get; } = buffer; @@ -78,24 +77,16 @@ public bool HasCapacityFor(uint sizeInBytes) return Buffer.Desc.SizeInBytes >= sizeInBytes; } - public Lease Borrow(nint pointer, uint sizeInBytes) + public Lease Borrow(BufferData data) { - this.pointer = pointer; - this.sizeInBytes = sizeInBytes; + this.data = data; return this; } public Lease Writeback() { - MappedMemory mappedMemory = Buffer.Map(); - - unsafe - { - new ReadOnlySpan((void*)mappedMemory.Pointer, (int)sizeInBytes).CopyTo(new((void*)pointer, (int)sizeInBytes)); - } - - Buffer.Unmap(); + Buffer.Download(0, data); return this; } @@ -116,8 +107,7 @@ public Lease Renew() { expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); - pointer = nint.Zero; - sizeInBytes = 0; + data = default; return this; } From 072922b7da7e1e72b312136027be3f1b54df63ad Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 26 Apr 2026 21:32:49 +0800 Subject: [PATCH 050/466] Refactor validation logic with reusable helper methods Modernize ValidationLayer by introducing reusable validation helpers, reducing code duplication and improving maintainability. Update validation for all descriptor types and ray tracing structures to use these helpers. Add new validation for input layouts, render states, blend states, and outputs. Restore CreateShader and add CreateGraphicsPipeline to GraphicsContext. Update ValidationMessages with new error messages for flags and instance counts. Improves clarity, error reporting, and extensibility. --- sources/Zenith.NET/GraphicsContext.cs | 18 +- sources/Zenith.NET/ValidationLayer.cs | 856 ++++++++++---------------- 2 files changed, 333 insertions(+), 541 deletions(-) diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index dd6f8fbb..40bdf2e1 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -50,13 +50,6 @@ public SwapChain CreateSwapChain(SwapChainDesc desc) return CreateSwapChainImpl(desc); } - public Shader CreateShader(ShaderDesc desc) - { - ValidationLayer?.ValidateDesc(desc); - - return CreateShaderImpl(desc); - } - public Buffer CreateBuffer(BufferDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -92,6 +85,13 @@ public ResourceTable CreateResourceTable(ResourceTableDesc desc) return CreateResourceTableImpl(desc); } + public Shader CreateShader(ShaderDesc desc) + { + ValidationLayer?.ValidateDesc(desc); + + return CreateShaderImpl(desc); + } + public GraphicsPipeline CreateGraphicsPipeline(GraphicsPipelineDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -142,8 +142,6 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract SwapChain CreateSwapChainImpl(SwapChainDesc desc); - protected abstract Shader CreateShaderImpl(ShaderDesc desc); - protected abstract Buffer CreateBufferImpl(BufferDesc desc); protected abstract Texture CreateTextureImpl(TextureDesc desc); @@ -154,6 +152,8 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract ResourceTable CreateResourceTableImpl(ResourceTableDesc desc); + protected abstract Shader CreateShaderImpl(ShaderDesc desc); + protected abstract GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc); protected abstract ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc); diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 169088af..207f0343 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -1,7 +1,19 @@ -namespace Zenith.NET; +using System.Numerics; + +namespace Zenith.NET; public abstract class ValidationLayer(GraphicsContext context) : GraphicsResource(context) { + private static readonly Dictionary ExpectedSurfaceHandleCount = new() + { + [SurfaceType.Win32] = 1, + [SurfaceType.Wayland] = 2, + [SurfaceType.Xlib] = 2, + [SurfaceType.Android] = 1, + [SurfaceType.Apple] = 1, + [SurfaceType.D3D11Interop] = 1 + }; + protected void Report(MessageSource source, MessageSeverity severity, string message) { Context.OnValidationMessage(new(source, severity, message)); @@ -9,241 +21,107 @@ protected void Report(MessageSource source, MessageSeverity severity, string mes internal void ValidateDesc(SwapChainDesc desc) { - if (desc.Surface.Handles is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "SwapChainDesc.Surface.Handles")); - - return; - } - - switch (desc.Surface.Type) - { - case SurfaceType.Win32: - if (desc.Surface.Handles.Length is not 1) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustHaveExactlyNHandles, "SwapChainDesc.Surface.Handles", 1, "SurfaceType.Win32")); - } - else if (desc.Surface.Handles[0] is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeValidHandle, "SwapChainDesc.Surface.Handles[0]", "SurfaceType.Win32")); - } - break; - - case SurfaceType.Wayland: - if (desc.Surface.Handles.Length is not 2) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustHaveExactlyNHandles, "SwapChainDesc.Surface.Handles", 2, "SurfaceType.Wayland")); - } - else if (desc.Surface.Handles[0] is 0 || desc.Surface.Handles[1] is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeValidHandles, "SwapChainDesc.Surface.Handles", "SurfaceType.Wayland")); - } - break; - - case SurfaceType.Xlib: - if (desc.Surface.Handles.Length is not 2) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustHaveExactlyNHandles, "SwapChainDesc.Surface.Handles", 2, "SurfaceType.Xlib")); - } - else if (desc.Surface.Handles[0] is 0 || desc.Surface.Handles[1] is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeValidHandles, "SwapChainDesc.Surface.Handles", "SurfaceType.Xlib")); - } - break; - - case SurfaceType.Android: - if (desc.Surface.Handles.Length is not 1) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustHaveExactlyNHandles, "SwapChainDesc.Surface.Handles", 1, "SurfaceType.Android")); - } - else if (desc.Surface.Handles[0] is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeValidHandle, "SwapChainDesc.Surface.Handles[0]", "SurfaceType.Android")); - } - break; - - case SurfaceType.Apple: - if (desc.Surface.Handles.Length is not 1) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustHaveExactlyNHandles, "SwapChainDesc.Surface.Handles", 1, "SurfaceType.Apple")); - } - else if (desc.Surface.Handles[0] is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeValidHandle, "SwapChainDesc.Surface.Handles[0]", "SurfaceType.Apple")); - } - break; - case SurfaceType.D3D11Interop: - if (desc.Surface.Handles.Length is not 1) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustHaveExactlyNHandles, "SwapChainDesc.Surface.Handles", 1, "SurfaceType.D3D11Interop")); - } - else if (desc.Surface.Handles[0] is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeValidHandle, "SwapChainDesc.Surface.Handles[0]", "SurfaceType.D3D11Interop")); - } - break; - - default: - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasUnsupportedSurfaceType, "SwapChainDesc.Surface", desc.Surface.Type)); - break; - } - - if (!Enum.IsDefined(desc.ColorTargetFormat)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SwapChainDesc.ColorTargetFormat", desc.ColorTargetFormat)); - } - - if (desc.DepthStencilTargetFormat is not null && !Enum.IsDefined(desc.DepthStencilTargetFormat.Value)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SwapChainDesc.DepthStencilTargetFormat", desc.DepthStencilTargetFormat.Value)); - } - } - - internal void ValidateDesc(ShaderDesc desc) - { - if (desc.ShaderBytes is null || desc.ShaderBytes.Length is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ShaderDesc.ShaderBytes")); - } + CheckSurface("SwapChainDesc.Surface", desc.Surface); - if (string.IsNullOrWhiteSpace(desc.EntryPoint)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrWhitespace, "ShaderDesc.EntryPoint")); - } + CheckEnum("SwapChainDesc.ColorTargetFormat", desc.ColorTargetFormat); - if (!Enum.IsDefined(desc.Stage)) + if (desc.DepthStencilTargetFormat is { } format) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "ShaderDesc.Stage", desc.Stage)); + CheckEnum("SwapChainDesc.DepthStencilTargetFormat", format); } } internal void ValidateDesc(BufferDesc desc) { - if (desc.SizeInBytes is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "BufferDesc.SizeInBytes")); - } + CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); if (desc.StrideInBytes is 0) { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); + ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); } if (desc.Flags is BufferUsageFlags.None) { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.IsSetToNoneWarning, "BufferDesc.Flags")); + ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "BufferDesc.Flags")); } } internal void ValidateDesc(TextureDesc desc) { - if (!Enum.IsDefined(desc.Type)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureDesc.Type", desc.Type)); - } - - if (!Enum.IsDefined(desc.Format)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureDesc.Format", desc.Format)); - } + CheckEnum("TextureDesc.Type", desc.Type); + CheckEnum("TextureDesc.Format", desc.Format); if (desc.Width is 0 || desc.Height is 0 || desc.Depth is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); - } - - if (desc.MipLevels is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc.MipLevels")); + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); } - if (desc.ArrayLayers is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc.ArrayLayers")); - } + CheckGreaterThanZero("TextureDesc.MipLevels", desc.MipLevels); + CheckGreaterThanZero("TextureDesc.ArrayLayers", desc.ArrayLayers); if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); + ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); } if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); } if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); } - if (!Enum.IsDefined(desc.SampleCount)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureDesc.SampleCount", desc.SampleCount)); - } + CheckEnum("TextureDesc.SampleCount", desc.SampleCount); if (desc.Flags is TextureUsageFlags.None) { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Flags")); + ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Flags")); } } internal void ValidateDesc(TextureViewDesc desc) { - if (desc.Texture is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "TextureViewDesc.Texture")); - - return; - } - - if (desc.Texture.IsDisposed) + if (!CheckResource("TextureViewDesc.Texture", desc.Texture)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "TextureViewDesc.Texture")); - return; } - if (!Enum.IsDefined(desc.Type)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureViewDesc.Type", desc.Type)); - } - - if (!Enum.IsDefined(desc.Format)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureViewDesc.Format", desc.Format)); - } + CheckEnum("TextureViewDesc.Type", desc.Type); + CheckEnum("TextureViewDesc.Format", desc.Format); if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); + ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); } if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); } if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); + ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); } if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); } if (desc.Type is TextureType.TextureCube) { if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); } if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); + ReportError(string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); } } @@ -251,70 +129,46 @@ internal void ValidateDesc(TextureViewDesc desc) { if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); } if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); } } } internal void ValidateDesc(SamplerDesc desc) { - if (!Enum.IsDefined(desc.Filter)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.Filter", desc.Filter)); - } - - if (!Enum.IsDefined(desc.U)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.U", desc.U)); - } - - if (!Enum.IsDefined(desc.V)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.V", desc.V)); - } - - if (!Enum.IsDefined(desc.W)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.W", desc.W)); - } - - if (!Enum.IsDefined(desc.ComparisonFunc)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.ComparisonFunc", desc.ComparisonFunc)); - } + CheckEnum("SamplerDesc.Filter", desc.Filter); + CheckEnum("SamplerDesc.U", desc.U); + CheckEnum("SamplerDesc.V", desc.V); + CheckEnum("SamplerDesc.W", desc.W); + CheckEnum("SamplerDesc.ComparisonFunc", desc.ComparisonFunc); if (desc.Filter is Filter.Anisotropic && desc.MaxAnisotropy is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "SamplerDesc.MaxAnisotropy")); + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "SamplerDesc.MaxAnisotropy")); } if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); } if (desc.MinLod > desc.MaxLod) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); } - if (!Enum.IsDefined(desc.BorderColor)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.BorderColor", desc.BorderColor)); - } + CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); } internal void ValidateDesc(ResourceTableDesc desc) { - if (desc.Bindings is null || desc.Bindings.Length is 0) + if (!CheckArrayNotEmpty("ResourceTableDesc.Bindings", desc.Bindings)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ResourceTableDesc.Bindings")); - return; } @@ -324,43 +178,25 @@ internal void ValidateDesc(ResourceTableDesc desc) } } - internal void ValidateDesc(GraphicsPipelineDesc desc) + internal void ValidateDesc(ShaderDesc desc) { - CheckRenderStates("GraphicsPipelineDesc", desc.RenderStates); + CheckArrayNotEmpty("ShaderDesc.ShaderBytes", desc.ShaderBytes); + CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); + CheckEnum("ShaderDesc.Stage", desc.Stage); + } - if (desc.Vertex is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.Vertex")); - } - else if (desc.Vertex.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "GraphicsPipelineDesc.Vertex")); - } + internal void ValidateDesc(GraphicsPipelineDesc desc) + { + CheckRenderStates("GraphicsPipelineDesc.RenderStates", desc.RenderStates); - if (desc.Pixel is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.Pixel")); - } - else if (desc.Pixel.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "GraphicsPipelineDesc.Pixel")); - } + CheckResource("GraphicsPipelineDesc.Vertex", desc.Vertex); + CheckResource("GraphicsPipelineDesc.Pixel", desc.Pixel); - if (desc.ResourceBindings is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.ResourceBindings")); - } - else - { - for (int i = 0; i < desc.ResourceBindings.Length; i++) - { - CheckResourceBinding($"GraphicsPipelineDesc.ResourceBindings[{i}]", desc.ResourceBindings[i]); - } - } + CheckResourceBindings("GraphicsPipelineDesc.ResourceBindings", desc.ResourceBindings); if (desc.InputLayouts is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); + ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); } else { @@ -370,457 +206,411 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) } } - if (!Enum.IsDefined(desc.PrimitiveTopology)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology)); - } - + CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); CheckOutput("GraphicsPipelineDesc.Output", desc.Output); + } - void CheckInputLayout(string name, InputLayout inputLayout) - { - if (inputLayout.Elements is null || inputLayout.Elements.Length is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, $"{name}.Elements")); - - return; - } + internal void ValidateDesc(ComputePipelineDesc desc) + { + CheckResource("ComputePipelineDesc.Compute", desc.Compute); - foreach (InputElement element in inputLayout.Elements) - { - if (!Enum.IsDefined(element.Format)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Elements.ElementFormat", element.Format)); - } + CheckResourceBindings("ComputePipelineDesc.ResourceBindings", desc.ResourceBindings); - if (!Enum.IsDefined(element.Semantic)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Elements.ElementSemantic", element.Semantic)); - } - } + if (desc.ThreadGroupSizeX is 0 || desc.ThreadGroupSizeY is 0 || desc.ThreadGroupSizeZ is 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "ComputePipelineDesc thread group sizes (ThreadGroupSizeX, ThreadGroupSizeY, ThreadGroupSizeZ)")); } } - internal void ValidateDesc(ComputePipelineDesc desc) + internal void ValidateDesc(MeshShadingPipelineDesc desc) { - if (desc.Compute is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ComputePipelineDesc.Compute")); - } - else if (desc.Compute.IsDisposed) + CheckRenderStates("MeshShadingPipelineDesc.RenderStates", desc.RenderStates); + + if (desc.Amplification is not null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ComputePipelineDesc.Compute")); + CheckResource("MeshShadingPipelineDesc.Amplification", desc.Amplification); } - if (desc.ResourceBindings is null) + CheckResource("MeshShadingPipelineDesc.Mesh", desc.Mesh); + CheckResource("MeshShadingPipelineDesc.Pixel", desc.Pixel); + + CheckResourceBindings("MeshShadingPipelineDesc.ResourceBindings", desc.ResourceBindings); + + if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ComputePipelineDesc.ResourceBindings")); + ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); } - else + + CheckOutput("MeshShadingPipelineDesc.Output", desc.Output); + + if (desc.Amplification is not null && (desc.AmplificationThreadGroupSizeX is 0 || desc.AmplificationThreadGroupSizeY is 0 || desc.AmplificationThreadGroupSizeZ is 0)) { - for (int i = 0; i < desc.ResourceBindings.Length; i++) - { - CheckResourceBinding($"ComputePipelineDesc.ResourceBindings[{i}]", desc.ResourceBindings[i]); - } + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc amplification thread group sizes (AmplificationThreadGroupSizeX, AmplificationThreadGroupSizeY, AmplificationThreadGroupSizeZ)")); } - if (desc.ThreadGroupSizeX is 0 || desc.ThreadGroupSizeY is 0 || desc.ThreadGroupSizeZ is 0) + if (desc.MeshThreadGroupSizeX is 0 || desc.MeshThreadGroupSizeY is 0 || desc.MeshThreadGroupSizeZ is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "ComputePipelineDesc thread group sizes (ThreadGroupSizeX, ThreadGroupSizeY, ThreadGroupSizeZ)")); + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc mesh thread group sizes (MeshThreadGroupSizeX, MeshThreadGroupSizeY, MeshThreadGroupSizeZ)")); } } - internal void ValidateDesc(MeshShadingPipelineDesc desc) + internal void ValidateDesc(QueryHeapDesc desc) { - CheckRenderStates("MeshShadingPipelineDesc", desc.RenderStates); + CheckEnum("QueryHeapDesc.Type", desc.Type); + CheckGreaterThanZero("QueryHeapDesc.Count", desc.Count); + } - if (desc.Amplification?.IsDisposed is true) + internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) + { + if (!CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Amplification")); + return; } - if (desc.Mesh is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.Mesh")); - } - else if (desc.Mesh.IsDisposed) + for (int i = 0; i < desc.Geometries.Length; i++) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Mesh")); + CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{i}]", desc.Geometries[i]); } + } - if (desc.Pixel is null) + internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) + { + if (!CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.Pixel")); + return; } - else if (desc.Pixel.IsDisposed) + + for (int i = 0; i < desc.Instances.Length; i++) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Pixel")); + CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{i}]", desc.Instances[i]); } + } - if (desc.ResourceBindings is null) + internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) + { + if (!oldDesc.Flags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.ResourceBindings")); + ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.Flags", AccelerationStructureBuildFlags.AllowUpdate)); } - else + + ValidateDesc(newDesc); + + if (newDesc.Instances is null) { - for (int i = 0; i < desc.ResourceBindings.Length; i++) - { - CheckResourceBinding($"MeshShadingPipelineDesc.ResourceBindings[{i}]", desc.ResourceBindings[i]); - } + return; } - if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) + if (oldDesc.Instances.Length != newDesc.Instances.Length) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); + ReportError(ValidationMessages.InstanceCountMustRemainSame); } + } - CheckOutput("MeshShadingPipelineDesc.Output", desc.Output); - - if (desc.Amplification is not null && (desc.AmplificationThreadGroupSizeX is 0 || desc.AmplificationThreadGroupSizeY is 0 || desc.AmplificationThreadGroupSizeZ is 0)) + private bool CheckResource(string name, T? resource) where T : GraphicsResource + { + if (resource is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc amplification thread group sizes (AmplificationThreadGroupSizeX, AmplificationThreadGroupSizeY, AmplificationThreadGroupSizeZ)")); + ReportError(string.Format(ValidationMessages.MustNotBeNull, name)); + + return false; } - if (desc.MeshThreadGroupSizeX is 0 || desc.MeshThreadGroupSizeY is 0 || desc.MeshThreadGroupSizeZ is 0) + if (resource.IsDisposed) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc mesh thread group sizes (MeshThreadGroupSizeX, MeshThreadGroupSizeY, MeshThreadGroupSizeZ)")); + ReportError(string.Format(ValidationMessages.MustNotBeDisposed, name)); + + return false; } + + return true; } - internal void ValidateDesc(QueryHeapDesc desc) + private bool CheckArrayNotEmpty(string name, T[]? array) { - if (!Enum.IsDefined(desc.Type)) + if (array is null || array.Length is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "QueryHeapDesc.Type", desc.Type)); - } + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, name)); - if (desc.Count is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "QueryHeapDesc.Count")); + return false; } + + return true; } - internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) + private bool CheckStringNotWhitespace(string name, string? value) { - if (desc.Geometries is null || desc.Geometries.Length is 0) + if (string.IsNullOrWhiteSpace(value)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "BottomLevelAccelerationStructureDesc.Geometries")); + ReportError(string.Format(ValidationMessages.MustNotBeNullOrWhitespace, name)); - return; + return false; } - for (int i = 0; i < desc.Geometries.Length; i++) + return true; + } + + private bool CheckEnum(string name, T value) where T : struct, Enum + { + if (!Enum.IsDefined(value)) { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{i}]", desc.Geometries[i]); + ReportError(string.Format(ValidationMessages.HasInvalidValue, name, value)); + + return false; } - void CheckRayTracingGeometry(string name, RayTracingGeometry rayTracingGeometry) + return true; + } + + private bool CheckGreaterThanZero(string name, T value) where T : struct, INumber + { + if (value <= T.Zero) { - switch (rayTracingGeometry.Type) - { - case RayTracingGeometryType.Triangles: - { - RayTracingTriangles triangles = rayTracingGeometry.Triangles; - - if (triangles.VertexBuffer is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.Triangles.VertexBuffer")); - - break; - } - - if (triangles.VertexBuffer.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, $"{name}.Triangles.VertexBuffer")); - - break; - } - - if (!Enum.IsDefined(triangles.VertexFormat)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Triangles.VertexFormat", triangles.VertexFormat)); - } - - if (triangles.VertexCount is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.Triangles.VertexCount")); - } - - if (triangles.VertexStrideInBytes is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.Triangles.VertexStrideInBytes")); - } - - if (triangles.VertexOffsetInBytes + (triangles.VertexCount * triangles.VertexStrideInBytes) > triangles.VertexBuffer.Desc.SizeInBytes) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Triangles.VertexCount", "the vertex buffer")); - } - - if (triangles.IndexBuffer is null) - { - break; - } - - if (triangles.IndexBuffer.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, $"{name}.Triangles.IndexBuffer")); - - break; - } - - if (!Enum.IsDefined(triangles.IndexFormat)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Triangles.IndexFormat", triangles.IndexFormat)); - } - - if (triangles.IndexCount is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.Triangles.IndexCount")); - } - - uint indexSizeInBytes = triangles.IndexFormat switch - { - IndexFormat.UInt16 => ValidationConstants.IndexSizeUInt16, - IndexFormat.UInt32 => ValidationConstants.IndexSizeUInt32, - _ => 0 - }; - - if (triangles.IndexOffsetInBytes + (triangles.IndexCount * indexSizeInBytes) > triangles.IndexBuffer.Desc.SizeInBytes) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Triangles.IndexCount", "the index buffer")); - } - } - break; - - case RayTracingGeometryType.AABBs: - { - RayTracingAABBs aABBs = rayTracingGeometry.AABBs; - - if (aABBs.Buffer is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.AABBs.Buffer")); - - break; - } - - if (aABBs.Buffer.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, $"{name}.AABBs.Buffer")); - - break; - } - - if (aABBs.Count is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.AABBs.Count")); - } - - if (aABBs.StrideInBytes is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.AABBs.StrideInBytes")); - } - - if (aABBs.OffsetInBytes + (aABBs.Count * aABBs.StrideInBytes) > aABBs.Buffer.Desc.SizeInBytes) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.AABBs.Count", "the AABBs buffer")); - } - } - break; - - default: - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", rayTracingGeometry.Type)); - break; - } + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, name)); + + return false; } + + return true; } - internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) + private void CheckSurface(string name, Surface surface) { - if (desc.Instances is null || desc.Instances.Length is 0) + if (surface.Handles is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "TopLevelAccelerationStructureDesc.Instances")); + ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.Handles")); return; } - for (int i = 0; i < desc.Instances.Length; i++) + if (!ExpectedSurfaceHandleCount.TryGetValue(surface.Type, out int expected)) { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{i}]", desc.Instances[i]); + ReportError(string.Format(ValidationMessages.HasUnsupportedSurfaceType, name, surface.Type)); + + return; } - void CheckRayTracingInstance(string name, RayTracingInstance rayTracingInstance) + if (surface.Handles.Length != expected) { - if (rayTracingInstance.AccelerationStructure is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.AccelerationStructure")); + ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.Handles", expected, surface.Type)); - return; - } + return; + } - if (rayTracingInstance.AccelerationStructure.IsDisposed) + for (int i = 0; i < surface.Handles.Length; i++) + { + if (surface.Handles[i] is 0) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, $"{name}.AccelerationStructure")); + if (expected is 1) + { + ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.Handles[0]", surface.Type)); + } + else + { + ReportError(string.Format(ValidationMessages.MustBeValidHandles, $"{name}.Handles", surface.Type)); + } return; } - - if (rayTracingInstance.ID > ValidationConstants.MaxRayTracingInstanceID) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.ID", ValidationConstants.MaxRayTracingInstanceID)); - } } } - internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) + private void CheckResourceBindings(string name, ResourceBinding[]? bindings) { - ValidateDesc(newDesc); - - if (newDesc.Instances is null) + if (bindings is null) { + ReportError(string.Format(ValidationMessages.MustNotBeNull, name)); + return; } - if (oldDesc.Instances.Length != newDesc.Instances.Length) + for (int i = 0; i < bindings.Length; i++) { - ReportFrameworkMessage(MessageSeverity.Error, ValidationMessages.InstanceCountMustRemainSame); + CheckResourceBinding($"{name}[{i}]", bindings[i]); } } - private void CheckResourceBinding(string name, ResourceBinding resourceBinding) + private void CheckResourceBinding(string name, ResourceBinding binding) { - if (!Enum.IsDefined(resourceBinding.Type)) + CheckEnum($"{name}.Type", binding.Type); + CheckGreaterThanZero($"{name}.Count", binding.Count); + } + + private void CheckInputLayout(string name, InputLayout inputLayout) + { + if (!CheckArrayNotEmpty($"{name}.Elements", inputLayout.Elements)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", resourceBinding.Type)); + return; } - if (resourceBinding.Count is 0) + foreach (InputElement element in inputLayout.Elements) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.Count")); + CheckEnum($"{name}.Elements.Format", element.Format); + CheckEnum($"{name}.Elements.Semantic", element.Semantic); } } private void CheckRenderStates(string name, RenderStates renderStates) { - if (!Enum.IsDefined(renderStates.RasterizerState.CullMode)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.RasterizerState.CullMode", renderStates.RasterizerState.CullMode)); - } + CheckEnum($"{name}.RasterizerState.FillMode", renderStates.RasterizerState.FillMode); + CheckEnum($"{name}.RasterizerState.CullMode", renderStates.RasterizerState.CullMode); + CheckEnum($"{name}.RasterizerState.FrontFace", renderStates.RasterizerState.FrontFace); + + CheckEnum($"{name}.DepthStencilState.DepthFunc", renderStates.DepthStencilState.DepthFunc); + CheckDepthStencilStateOp($"{name}.DepthStencilState.FrontFace", renderStates.DepthStencilState.FrontFace); + CheckDepthStencilStateOp($"{name}.DepthStencilState.BackFace", renderStates.DepthStencilState.BackFace); + + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget0", renderStates.BlendState.RenderTarget0); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget1", renderStates.BlendState.RenderTarget1); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget2", renderStates.BlendState.RenderTarget2); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget3", renderStates.BlendState.RenderTarget3); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget4", renderStates.BlendState.RenderTarget4); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget5", renderStates.BlendState.RenderTarget5); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget6", renderStates.BlendState.RenderTarget6); + CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget7", renderStates.BlendState.RenderTarget7); + } + + private void CheckDepthStencilStateOp(string name, DepthStencilStateOp op) + { + CheckEnum($"{name}.StencilFailOp", op.StencilFailOp); + CheckEnum($"{name}.StencilDepthFailOp", op.StencilDepthFailOp); + CheckEnum($"{name}.StencilPassOp", op.StencilPassOp); + CheckEnum($"{name}.StencilFunc", op.StencilFunc); + } - if (!Enum.IsDefined(renderStates.RasterizerState.FillMode)) + private void CheckBlendStateRenderTarget(string name, BlendStateRenderTarget renderTarget) + { + CheckEnum($"{name}.SrcBlend", renderTarget.SrcBlend); + CheckEnum($"{name}.DestBlend", renderTarget.DestBlend); + CheckEnum($"{name}.BlendOp", renderTarget.BlendOp); + CheckEnum($"{name}.SrcBlendAlpha", renderTarget.SrcBlendAlpha); + CheckEnum($"{name}.DestBlendAlpha", renderTarget.DestBlendAlpha); + CheckEnum($"{name}.BlendOpAlpha", renderTarget.BlendOpAlpha); + } + + private void CheckOutput(string name, Output output) + { + if (output.ColorAttachments is null) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.RasterizerState.FillMode", renderStates.RasterizerState.FillMode)); + ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorAttachments")); + + return; } - if (!Enum.IsDefined(renderStates.RasterizerState.FrontFace)) + for (int i = 0; i < output.ColorAttachments.Length; i++) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.RasterizerState.FrontFace", renderStates.RasterizerState.FrontFace)); + CheckEnum($"{name}.ColorAttachments[{i}]", output.ColorAttachments[i]); } - if (!Enum.IsDefined(renderStates.DepthStencilState.DepthFunc)) + if (output.DepthStencilAttachment is { } format) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.DepthStencilState.DepthFunc", renderStates.DepthStencilState.DepthFunc)); + CheckEnum($"{name}.DepthStencilAttachment", format); } - CheckDepthStencilStateOp($"{name}.RenderStates.DepthStencilState.FrontFace", renderStates.DepthStencilState.FrontFace); - CheckDepthStencilStateOp($"{name}.RenderStates.DepthStencilState.BackFace", renderStates.DepthStencilState.BackFace); + CheckEnum($"{name}.SampleCount", output.SampleCount); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget0", renderStates.BlendState.RenderTarget0); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget1", renderStates.BlendState.RenderTarget1); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget2", renderStates.BlendState.RenderTarget2); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget3", renderStates.BlendState.RenderTarget3); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget4", renderStates.BlendState.RenderTarget4); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget5", renderStates.BlendState.RenderTarget5); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget6", renderStates.BlendState.RenderTarget6); - CheckBlendStateRenderTarget($"{name}.RenderStates.BlendState.RenderTarget7", renderStates.BlendState.RenderTarget7); + if (output.ColorAttachments.Length is 0 && output.DepthStencilAttachment is null) + { + ReportWarning(string.Format(ValidationMessages.HasNoAttachments, name)); + } + } - void CheckDepthStencilStateOp(string name, DepthStencilStateOp depthStencilStateOp) + private void CheckRayTracingGeometry(string name, RayTracingGeometry geometry) + { + switch (geometry.Type) { - if (!Enum.IsDefined(depthStencilStateOp.StencilFailOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilFailOp", depthStencilStateOp.StencilFailOp)); - } + case RayTracingGeometryType.Triangles: + CheckRayTracingTriangles(name, geometry.Triangles); + break; - if (!Enum.IsDefined(depthStencilStateOp.StencilDepthFailOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilDepthFailOp", depthStencilStateOp.StencilDepthFailOp)); - } + case RayTracingGeometryType.AABBs: + CheckRayTracingAABBs(name, geometry.AABBs); + break; - if (!Enum.IsDefined(depthStencilStateOp.StencilPassOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilPassOp", depthStencilStateOp.StencilPassOp)); - } + default: + ReportError(string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", geometry.Type)); + break; + } + } - if (!Enum.IsDefined(depthStencilStateOp.StencilFunc)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilFunc", depthStencilStateOp.StencilFunc)); - } + private void CheckRayTracingTriangles(string name, RayTracingTriangles triangles) + { + if (!CheckResource($"{name}.Triangles.VertexBuffer", triangles.VertexBuffer)) + { + return; } - void CheckBlendStateRenderTarget(string name, BlendStateRenderTarget blendStateRenderTarget) + CheckEnum($"{name}.Triangles.VertexFormat", triangles.VertexFormat); + CheckGreaterThanZero($"{name}.Triangles.VertexCount", triangles.VertexCount); + CheckGreaterThanZero($"{name}.Triangles.VertexStrideInBytes", triangles.VertexStrideInBytes); + + if (triangles.VertexOffsetInBytes + (triangles.VertexCount * triangles.VertexStrideInBytes) > triangles.VertexBuffer.Desc.SizeInBytes) { - if (!Enum.IsDefined(blendStateRenderTarget.SrcBlend)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.SrcBlend", blendStateRenderTarget.SrcBlend)); - } + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Triangles.VertexCount", "the vertex buffer")); + } - if (!Enum.IsDefined(blendStateRenderTarget.DestBlend)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.DestBlend", blendStateRenderTarget.DestBlend)); - } + if (triangles.IndexBuffer is null) + { + return; + } - if (!Enum.IsDefined(blendStateRenderTarget.BlendOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.BlendOp", blendStateRenderTarget.BlendOp)); - } + if (!CheckResource($"{name}.Triangles.IndexBuffer", triangles.IndexBuffer)) + { + return; + } - if (!Enum.IsDefined(blendStateRenderTarget.SrcBlendAlpha)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.SrcBlendAlpha", blendStateRenderTarget.SrcBlendAlpha)); - } + CheckEnum($"{name}.Triangles.IndexFormat", triangles.IndexFormat); + CheckGreaterThanZero($"{name}.Triangles.IndexCount", triangles.IndexCount); - if (!Enum.IsDefined(blendStateRenderTarget.DestBlendAlpha)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.DestBlendAlpha", blendStateRenderTarget.DestBlendAlpha)); - } + uint indexSizeInBytes = triangles.IndexFormat switch + { + IndexFormat.UInt16 => ValidationConstants.IndexSizeUInt16, + IndexFormat.UInt32 => ValidationConstants.IndexSizeUInt32, + _ => 0 + }; - if (!Enum.IsDefined(blendStateRenderTarget.BlendOpAlpha)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.BlendOpAlpha", blendStateRenderTarget.BlendOpAlpha)); - } + if (triangles.IndexOffsetInBytes + (triangles.IndexCount * indexSizeInBytes) > triangles.IndexBuffer.Desc.SizeInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Triangles.IndexCount", "the index buffer")); } } - private void CheckOutput(string name, Output output) + private void CheckRayTracingAABBs(string name, RayTracingAABBs aabbs) { - if (output.ColorAttachments is null) + if (!CheckResource($"{name}.AABBs.Buffer", aabbs.Buffer)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorAttachments")); - return; } - for (int i = 0; i < output.ColorAttachments.Length; i++) + CheckGreaterThanZero($"{name}.AABBs.Count", aabbs.Count); + CheckGreaterThanZero($"{name}.AABBs.StrideInBytes", aabbs.StrideInBytes); + + if (aabbs.OffsetInBytes + (aabbs.Count * aabbs.StrideInBytes) > aabbs.Buffer.Desc.SizeInBytes) { - if (!Enum.IsDefined(output.ColorAttachments[i])) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.ColorAttachments[{i}]", output.ColorAttachments[i])); - } + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.AABBs.Count", "the AABBs buffer")); } + } - if (output.DepthStencilAttachment is not null && !Enum.IsDefined(output.DepthStencilAttachment.Value)) + private void CheckRayTracingInstance(string name, RayTracingInstance instance) + { + if (!CheckResource($"{name}.AccelerationStructure", instance.AccelerationStructure)) { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.DepthStencilAttachment", output.DepthStencilAttachment.Value)); + return; } - if (output.ColorAttachments.Length is 0 && output.DepthStencilAttachment is null) + if (instance.ID > ValidationConstants.MaxRayTracingInstanceID) { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.HasNoAttachments, name)); + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.ID", ValidationConstants.MaxRayTracingInstanceID)); } } - private void ReportFrameworkMessage(MessageSeverity severity, string message) + private void ReportError(string message) { - Report(MessageSource.Framework, severity, message); + Report(MessageSource.Framework, MessageSeverity.Error, message); + } + + private void ReportWarning(string message) + { + Report(MessageSource.Framework, MessageSeverity.Warning, message); } } @@ -879,5 +669,7 @@ file static class ValidationMessages public const string MustBeOneOf = "{0} must be one of: {1}."; + public const string MustHaveFlag = "{0} must have the flag '{1}' set."; + public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; -} \ No newline at end of file +} From a48540eac7e2f829592e02e843166afac708a15e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 27 Apr 2026 20:41:46 +0800 Subject: [PATCH 051/466] Refactor string encoding and message severity enums Replaced StringEncoding.Uni with UTF16 and updated all usages. Renamed MessageSeverity.Message to Info and adjusted related mappings. Updated DXCapabilities to use UTF16 for device name extraction. Modified ZenithMarshal methods to handle UTF16 encoding consistently. Changed default severity mapping in validation layers to Info. --- sources/Zenith.NET.DirectX12/DXCapabilities.cs | 2 +- sources/Zenith.NET.DirectX12/DXValidationLayer.cs | 2 +- sources/Zenith.NET.Vulkan/VKValidationLayer.cs | 2 +- sources/Zenith.NET/Enums/MessageSeverity.cs | 2 +- sources/Zenith.NET/Enums/StringEncoding.cs | 4 ++-- sources/Zenith.NET/ZenithMarshal.cs | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Zenith.NET.DirectX12/DXCapabilities.cs index 13f0400a..9326aa66 100644 --- a/sources/Zenith.NET.DirectX12/DXCapabilities.cs +++ b/sources/Zenith.NET.DirectX12/DXCapabilities.cs @@ -17,7 +17,7 @@ public DXCapabilities(DXGraphicsContext context) FeatureDataD3D12Options7 options7 = new(); context.Device.CheckFeatureSupport(Feature.D3D12Options7, &options7, (uint)sizeof(FeatureDataD3D12Options7)).Success(); - DeviceName = ZenithMarshal.StringFromPointer((nint)desc.Description, StringEncoding.Uni); + DeviceName = ZenithMarshal.StringFromPointer((nint)desc.Description, StringEncoding.UTF16); RayTracingSupported = options5.RaytracingTier >= RaytracingTier.Tier11; MeshShadingSupported = options7.MeshShaderTier is not MeshShaderTier.TierNotSupported; } diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index cd9b536b..99a079ad 100644 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -31,7 +31,7 @@ private void Callback(MessageCategory category, DxMessageSeverity severity, Mess { DxMessageSeverity.Error => MessageSeverity.Error, DxMessageSeverity.Warning => MessageSeverity.Warning, - _ => MessageSeverity.Message + _ => MessageSeverity.Info }, ZenithMarshal.StringFromPointer((nint)pDescription, StringEncoding.UTF8)); } } diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs index 2abc3e1b..bc6e0fce 100644 --- a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs +++ b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs @@ -47,7 +47,7 @@ private uint UserCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, { DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => MessageSeverity.Error, DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => MessageSeverity.Warning, - _ => MessageSeverity.Message + _ => MessageSeverity.Info }, ZenithMarshal.StringFromPointer((nint)pCallbackData->PMessage, StringEncoding.UTF8)); return Vk.False; diff --git a/sources/Zenith.NET/Enums/MessageSeverity.cs b/sources/Zenith.NET/Enums/MessageSeverity.cs index 28fe5768..70a8601d 100644 --- a/sources/Zenith.NET/Enums/MessageSeverity.cs +++ b/sources/Zenith.NET/Enums/MessageSeverity.cs @@ -6,5 +6,5 @@ public enum MessageSeverity Warning, - Message + Info } diff --git a/sources/Zenith.NET/Enums/StringEncoding.cs b/sources/Zenith.NET/Enums/StringEncoding.cs index a4c024df..7154b604 100644 --- a/sources/Zenith.NET/Enums/StringEncoding.cs +++ b/sources/Zenith.NET/Enums/StringEncoding.cs @@ -2,7 +2,7 @@ public enum StringEncoding { - Uni, + UTF8, - UTF8 + UTF16 } diff --git a/sources/Zenith.NET/ZenithMarshal.cs b/sources/Zenith.NET/ZenithMarshal.cs index f8ef7dd6..9c79fe3d 100644 --- a/sources/Zenith.NET/ZenithMarshal.cs +++ b/sources/Zenith.NET/ZenithMarshal.cs @@ -49,8 +49,8 @@ public static nint StringToPointer(Scope scope, string value, StringEncoding enc { byte[] values = encoding switch { - StringEncoding.Uni => Encoding.Unicode.GetBytes(value + '\0'), StringEncoding.UTF8 => Encoding.UTF8.GetBytes(value + '\0'), + StringEncoding.UTF16 => Encoding.Unicode.GetBytes(value + '\0'), _ => [] }; @@ -73,8 +73,8 @@ public static string StringFromPointer(nint pointer, StringEncoding encoding) { return encoding switch { - StringEncoding.Uni => Marshal.PtrToStringUni(pointer) ?? string.Empty, StringEncoding.UTF8 => Marshal.PtrToStringUTF8(pointer) ?? string.Empty, + StringEncoding.UTF16 => Marshal.PtrToStringUni(pointer) ?? string.Empty, _ => string.Empty }; } From 9a97adc61a74d3dc0042504ca7f29671e47d60e1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 1 May 2026 15:52:29 +0800 Subject: [PATCH 052/466] Modernize graphics API: enums, fields, and validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor API for clarity and alignment with modern graphics APIs (Vulkan, Metal, DX12). Renamed enums and struct fields for consistency (e.g., Backend → GraphicsApi, Blend/BlendOp → BlendFactor/BlendOperation, ComparisonFunc → CompareFunction). Updated struct fields and method signatures to use new types and names. Introduced new enums and records (BufferAccess, BufferUsages, ColorWrites, etc.) and removed deprecated types. Revised validation logic and default state objects to match new API. These changes are not backward compatible and require updates to dependent code. --- sources/Zenith.NET/BlendStates.cs | 44 ++--- sources/Zenith.NET/Buffer.cs | 4 +- sources/Zenith.NET/CommandBuffer.cs | 42 ++--- sources/Zenith.NET/CommandQueue.cs | 2 +- sources/Zenith.NET/DepthStencilStates.cs | 20 +-- sources/Zenith.NET/Downloader.cs | 2 +- .../Enums/AccelerationStructureBuildFlags.cs | 4 +- sources/Zenith.NET/Enums/Blend.cs | 28 --- sources/Zenith.NET/Enums/BlendFactor.cs | 28 +++ .../Enums/{BlendOp.cs => BlendOperation.cs} | 2 +- sources/Zenith.NET/Enums/BufferAccess.cs | 10 ++ sources/Zenith.NET/Enums/BufferUsageFlags.cs | 25 --- sources/Zenith.NET/Enums/BufferUsages.cs | 21 +++ .../{ColorWriteMask.cs => ColorWrites.cs} | 2 +- .../{ComparisonFunc.cs => CompareFunction.cs} | 2 +- sources/Zenith.NET/Enums/ElementFormat.cs | 24 +-- sources/Zenith.NET/Enums/ElementSemantic.cs | 4 +- sources/Zenith.NET/Enums/Filter.cs | 22 --- sources/Zenith.NET/Enums/FilterMode.cs | 8 + .../Enums/{Backend.cs => GraphicsApi.cs} | 2 +- .../Enums/{LoadOp.cs => LoadOperation.cs} | 2 +- sources/Zenith.NET/Enums/MessageSource.cs | 2 +- .../Enums/RayTracingGeometryFlags.cs | 9 - .../Enums/RayTracingInstanceFlags.cs | 4 +- sources/Zenith.NET/Enums/ResourceType.cs | 10 +- .../{ShaderStageFlags.cs => ShaderStages.cs} | 6 +- .../{StencilOp.cs => StencilOperation.cs} | 2 +- .../Enums/{StoreOp.cs => StoreOperation.cs} | 2 +- .../{TextureLayout.cs => TextureState.cs} | 10 +- ...{TextureUsageFlags.cs => TextureUsages.cs} | 6 +- sources/Zenith.NET/GraphicsContext.cs | 8 +- sources/Zenith.NET/RasterizerStates.cs | 2 +- .../Zenith.NET/Structs/AttachmentFormats.cs | 10 ++ sources/Zenith.NET/Structs/BlendState.cs | 2 +- .../Structs/BlendStateRenderTarget.cs | 14 +- sources/Zenith.NET/Structs/BufferDesc.cs | 4 +- sources/Zenith.NET/Structs/ColorAttachment.cs | 4 +- .../Zenith.NET/Structs/ComputePipelineDesc.cs | 8 +- .../Structs/DepthStencilAttachment.cs | 8 +- .../Zenith.NET/Structs/DepthStencilState.cs | 6 +- .../Zenith.NET/Structs/DepthStencilStateOp.cs | 12 -- .../Structs/GraphicsPipelineDesc.cs | 8 +- .../Structs/MeshShadingPipelineDesc.cs | 20 +-- sources/Zenith.NET/Structs/Output.cs | 10 -- sources/Zenith.NET/Structs/RasterizerState.cs | 2 +- .../Zenith.NET/Structs/RayTracingGeometry.cs | 2 +- .../Zenith.NET/Structs/RayTracingInstance.cs | 4 +- sources/Zenith.NET/Structs/RenderStates.cs | 2 +- sources/Zenith.NET/Structs/SamplerDesc.cs | 14 +- sources/Zenith.NET/Structs/ShaderDesc.cs | 4 +- .../Zenith.NET/Structs/StencilFaceState.cs | 12 ++ sources/Zenith.NET/Structs/SwapChainDesc.cs | 4 +- sources/Zenith.NET/Structs/TextureData.cs | 2 +- sources/Zenith.NET/Structs/TextureDesc.cs | 2 +- sources/Zenith.NET/Uploader.cs | 2 +- sources/Zenith.NET/ValidationLayer.cs | 170 +++++++++--------- sources/Zenith.NET/ZenithHelper.cs | 24 +-- 57 files changed, 341 insertions(+), 368 deletions(-) delete mode 100644 sources/Zenith.NET/Enums/Blend.cs create mode 100644 sources/Zenith.NET/Enums/BlendFactor.cs rename sources/Zenith.NET/Enums/{BlendOp.cs => BlendOperation.cs} (77%) create mode 100644 sources/Zenith.NET/Enums/BufferAccess.cs delete mode 100644 sources/Zenith.NET/Enums/BufferUsageFlags.cs create mode 100644 sources/Zenith.NET/Enums/BufferUsages.cs rename sources/Zenith.NET/Enums/{ColorWriteMask.cs => ColorWrites.cs} (86%) rename sources/Zenith.NET/Enums/{ComparisonFunc.cs => CompareFunction.cs} (83%) delete mode 100644 sources/Zenith.NET/Enums/Filter.cs create mode 100644 sources/Zenith.NET/Enums/FilterMode.cs rename sources/Zenith.NET/Enums/{Backend.cs => GraphicsApi.cs} (74%) rename sources/Zenith.NET/Enums/{LoadOp.cs => LoadOperation.cs} (71%) delete mode 100644 sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs rename sources/Zenith.NET/Enums/{ShaderStageFlags.cs => ShaderStages.cs} (60%) rename sources/Zenith.NET/Enums/{StencilOp.cs => StencilOperation.cs} (85%) rename sources/Zenith.NET/Enums/{StoreOp.cs => StoreOperation.cs} (66%) rename sources/Zenith.NET/Enums/{TextureLayout.cs => TextureState.cs} (59%) rename sources/Zenith.NET/Enums/{TextureUsageFlags.cs => TextureUsages.cs} (55%) create mode 100644 sources/Zenith.NET/Structs/AttachmentFormats.cs delete mode 100644 sources/Zenith.NET/Structs/DepthStencilStateOp.cs delete mode 100644 sources/Zenith.NET/Structs/Output.cs create mode 100644 sources/Zenith.NET/Structs/StencilFaceState.cs diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs index 183c16d0..6de604be 100644 --- a/sources/Zenith.NET/BlendStates.cs +++ b/sources/Zenith.NET/BlendStates.cs @@ -8,13 +8,13 @@ public static class BlendStates IndependentBlendEnable = false, RenderTarget0 = new() { - SrcBlend = Blend.One, - DestBlend = Blend.Zero, - BlendOp = BlendOp.Add, - SrcBlendAlpha = Blend.One, - DestBlendAlpha = Blend.Zero, - BlendOpAlpha = BlendOp.Add, - WriteMask = ColorWriteMask.All + SrcFactor = BlendFactor.One, + DstFactor = BlendFactor.Zero, + Operation = BlendOperation.Add, + SrcFactorAlpha = BlendFactor.One, + DstFactorAlpha = BlendFactor.Zero, + OperationAlpha = BlendOperation.Add, + Writes = ColorWrites.All } }; @@ -23,10 +23,10 @@ public static class BlendStates RenderTarget0 = Default.RenderTarget0 with { BlendEnable = true, - SrcBlend = Blend.SrcAlpha, - DestBlend = Blend.One, - SrcBlendAlpha = Blend.SrcAlpha, - DestBlendAlpha = Blend.One + SrcFactor = BlendFactor.SrcAlpha, + DstFactor = BlendFactor.One, + SrcFactorAlpha = BlendFactor.SrcAlpha, + DstFactorAlpha = BlendFactor.One } }; @@ -35,10 +35,10 @@ public static class BlendStates RenderTarget0 = Default.RenderTarget0 with { BlendEnable = true, - SrcBlend = Blend.SrcAlpha, - DestBlend = Blend.InverseSrcAlpha, - SrcBlendAlpha = Blend.SrcAlpha, - DestBlendAlpha = Blend.InverseSrcAlpha + SrcFactor = BlendFactor.One, + DstFactor = BlendFactor.OneMinusSrcAlpha, + SrcFactorAlpha = BlendFactor.One, + DstFactorAlpha = BlendFactor.OneMinusSrcAlpha } }; @@ -47,10 +47,10 @@ public static class BlendStates RenderTarget0 = Default.RenderTarget0 with { BlendEnable = true, - SrcBlend = Blend.SrcAlpha, - DestBlend = Blend.InverseSrcAlpha, - SrcBlendAlpha = Blend.SrcAlpha, - DestBlendAlpha = Blend.InverseSrcAlpha + SrcFactor = BlendFactor.SrcAlpha, + DstFactor = BlendFactor.OneMinusSrcAlpha, + SrcFactorAlpha = BlendFactor.SrcAlpha, + DstFactorAlpha = BlendFactor.OneMinusSrcAlpha } }; @@ -58,7 +58,7 @@ public static class BlendStates { RenderTarget0 = Default.RenderTarget0 with { - BlendEnable = true + BlendEnable = false } }; @@ -66,8 +66,8 @@ public static class BlendStates { RenderTarget0 = Default.RenderTarget0 with { - BlendEnable = true, - WriteMask = ColorWriteMask.None + BlendEnable = false, + Writes = ColorWrites.None } }; } diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 74d05a76..ae78cbdb 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -12,7 +12,7 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public void Upload(uint offsetInBytes, BufferData data) { - if (desc.Flags.HasFlag(BufferUsageFlags.MapRead) || desc.Flags.HasFlag(BufferUsageFlags.MapWrite)) + if (desc.Access is BufferAccess.CpuWriteOnly) { MappedMemory mappedMemory = Map(); @@ -34,7 +34,7 @@ public void Upload(uint offsetInBytes, BufferData data) public void Download(uint offsetInBytes, BufferData data) { - if (desc.Flags.HasFlag(BufferUsageFlags.MapRead) || desc.Flags.HasFlag(BufferUsageFlags.MapWrite)) + if (desc.Access is BufferAccess.CpuReadOnly) { MappedMemory mappedMemory = Map(); diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 2a90ad46..bebaea3f 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -26,9 +26,9 @@ public void MemoryBarrier(Buffer buffer) MemoryBarrierImpl(buffer); } - public void Transition(Texture texture, TextureSubresource subresource, TextureLayout layout) + public void Transition(Texture texture, TextureSubresource subresource, TextureState state) { - TransitionImpl(texture, subresource, layout); + TransitionImpl(texture, subresource, state); } public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) @@ -61,29 +61,29 @@ public void Download(Texture texture, TextureSubresource subresource, Offset3D o CopyTextureToBuffer(texture, subresource, offset, extent, temporary, 0, data.Layout); } - public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) + public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { - CopyBufferImpl(src, srcOffsetInBytes, dest, destOffsetInBytes, sizeInBytes); + CopyBufferImpl(src, srcOffsetInBytes, dst, dstOffsetInBytes, sizeInBytes); } - public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent) + public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - CopyBufferToTextureImpl(src, srcOffsetInBytes, srcLayout, dest, destSubresource, destOffset, destExtent); + CopyBufferToTextureImpl(src, srcOffsetInBytes, srcLayout, dst, dstSubresource, dstOffset, dstExtent); } - public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent) + public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - CopyTextureImpl(src, srcSubresource, srcOffset, dest, destSubresource, destOffset, extent); + CopyTextureImpl(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent); } - public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout) + public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) { - CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dest, destOffsetInBytes, destLayout); + CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstLayout); } - public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dest, TextureSubresource destSubresource) + public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { - ResolveTextureImpl(src, srcSubresource, dest, destSubresource); + ResolveTextureImpl(src, srcSubresource, dst, dstSubresource); } public BottomLevelAccelerationStructure BuildAccelerationStructure(BottomLevelAccelerationStructureDesc desc) @@ -185,14 +185,14 @@ public void SetPipeline(MeshShadingPipeline pipeline) currentPipeline = pipeline; } - public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint index) + public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) { if (currentPipeline is not GraphicsPipeline pipeline) { return; } - SetVertexBufferImpl(pipeline, buffer, offsetInBytes, index); + SetVertexBufferImpl(pipeline, buffer, offsetInBytes, slot); } public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) @@ -384,17 +384,17 @@ protected override void Destroy() protected abstract void MemoryBarrierImpl(Buffer buffer); - protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout layout); + protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureState state); - protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); + protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes); - protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D destExtent); + protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent); - protected abstract void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dest, TextureSubresource destSubresource, Offset3D destOffset, Extent3D extent); + protected abstract void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent); - protected abstract void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dest, uint destOffsetInBytes, TextureDataLayout destLayout); + protected abstract void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout); - protected abstract void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dest, TextureSubresource destSubresource); + protected abstract void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource); protected abstract BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc); @@ -416,7 +416,7 @@ protected override void Destroy() protected abstract void SetPipelineImpl(MeshShadingPipeline pipeline); - protected abstract void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index); + protected abstract void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot); protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 10f11680..4bfa4ca8 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -63,7 +63,7 @@ internal void Wait(ulong value) protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); - protected abstract void WaitImpl(ulong value); + protected abstract void WaitImpl(ulong waitValue); protected override void Destroy() { diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs index 92867876..9387c324 100644 --- a/sources/Zenith.NET/DepthStencilStates.cs +++ b/sources/Zenith.NET/DepthStencilStates.cs @@ -6,29 +6,29 @@ public static class DepthStencilStates { DepthEnable = true, DepthWriteEnable = true, - DepthFunc = ComparisonFunc.LessEqual, + DepthCompare = CompareFunction.LessEqual, StencilEnable = false, StencilReadMask = 0xFF, StencilWriteMask = 0xFF, FrontFace = new() { - StencilFailOp = StencilOp.Keep, - StencilDepthFailOp = StencilOp.Keep, - StencilPassOp = StencilOp.Keep, - StencilFunc = ComparisonFunc.Always + Fail = StencilOperation.Keep, + DepthFail = StencilOperation.Keep, + Pass = StencilOperation.Keep, + Compare = CompareFunction.Always }, BackFace = new() { - StencilFailOp = StencilOp.Keep, - StencilDepthFailOp = StencilOp.Keep, - StencilPassOp = StencilOp.Keep, - StencilFunc = ComparisonFunc.Always + Fail = StencilOperation.Keep, + DepthFail = StencilOperation.Keep, + Pass = StencilOperation.Keep, + Compare = CompareFunction.Always } }; public static readonly DepthStencilState DefaultInverted = Default with { - DepthFunc = ComparisonFunc.GreaterEqual + DepthCompare = CompareFunction.GreaterEqual }; public static readonly DepthStencilState DepthRead = Default with diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index 1a9a193d..092a8355 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -21,7 +21,7 @@ public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) { SizeInBytes = data.SizeInBytes, StrideInBytes = 1, - Flags = BufferUsageFlags.MapRead + Access = BufferAccess.CpuReadOnly })); } diff --git a/sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs b/sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs index a9a31057..10d73bf0 100644 --- a/sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs +++ b/sources/Zenith.NET/Enums/AccelerationStructureBuildFlags.cs @@ -13,7 +13,5 @@ public enum AccelerationStructureBuildFlags PreferFastBuild = 1 << 3, - MinimizeMemory = 1 << 4, - - PerformUpdate = 1 << 5 + MinimizeMemory = 1 << 4 } diff --git a/sources/Zenith.NET/Enums/Blend.cs b/sources/Zenith.NET/Enums/Blend.cs deleted file mode 100644 index 51ad57cb..00000000 --- a/sources/Zenith.NET/Enums/Blend.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Zenith.NET; - -public enum Blend -{ - Zero, - - One, - - SrcColor, - - InverseSrcColor, - - DestColor, - - InverseDestColor, - - SrcAlpha, - - InverseSrcAlpha, - - DestAlpha, - - InverseDestAlpha, - - BlendFactor, - - InverseBlendFactor -} diff --git a/sources/Zenith.NET/Enums/BlendFactor.cs b/sources/Zenith.NET/Enums/BlendFactor.cs new file mode 100644 index 00000000..60bf9244 --- /dev/null +++ b/sources/Zenith.NET/Enums/BlendFactor.cs @@ -0,0 +1,28 @@ +namespace Zenith.NET; + +public enum BlendFactor +{ + Zero, + + One, + + SrcColor, + + OneMinusSrcColor, + + DstColor, + + OneMinusDstColor, + + SrcAlpha, + + OneMinusSrcAlpha, + + DstAlpha, + + OneMinusDstAlpha, + + Constant, + + OneMinusConstant +} diff --git a/sources/Zenith.NET/Enums/BlendOp.cs b/sources/Zenith.NET/Enums/BlendOperation.cs similarity index 77% rename from sources/Zenith.NET/Enums/BlendOp.cs rename to sources/Zenith.NET/Enums/BlendOperation.cs index e0f30065..382afe12 100644 --- a/sources/Zenith.NET/Enums/BlendOp.cs +++ b/sources/Zenith.NET/Enums/BlendOperation.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum BlendOp +public enum BlendOperation { Add, diff --git a/sources/Zenith.NET/Enums/BufferAccess.cs b/sources/Zenith.NET/Enums/BufferAccess.cs new file mode 100644 index 00000000..ee111387 --- /dev/null +++ b/sources/Zenith.NET/Enums/BufferAccess.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public enum BufferAccess +{ + GpuOnly, + + CpuReadOnly, + + CpuWriteOnly +} diff --git a/sources/Zenith.NET/Enums/BufferUsageFlags.cs b/sources/Zenith.NET/Enums/BufferUsageFlags.cs deleted file mode 100644 index 923b6f14..00000000 --- a/sources/Zenith.NET/Enums/BufferUsageFlags.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum BufferUsageFlags -{ - None = 0, - - Vertex = 1 << 0, - - Index = 1 << 1, - - Indirect = 1 << 2, - - AccelerationStructure = 1 << 3, - - Constant = 1 << 4, - - ShaderResource = 1 << 5, - - UnorderedAccess = 1 << 6, - - MapRead = 1 << 7, - - MapWrite = 1 << 8 -} diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs new file mode 100644 index 00000000..d38b2a49 --- /dev/null +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -0,0 +1,21 @@ +namespace Zenith.NET; + +[Flags] +public enum BufferUsages +{ + None = 0, + + Vertex = 1 << 0, + + Index = 1 << 1, + + Uniform = 1 << 2, + + StorageReadOnly = 1 << 3, + + StorageReadWrite = 1 << 4, + + Indirect = 1 << 5, + + AccelerationStructure = 1 << 6 +} diff --git a/sources/Zenith.NET/Enums/ColorWriteMask.cs b/sources/Zenith.NET/Enums/ColorWrites.cs similarity index 86% rename from sources/Zenith.NET/Enums/ColorWriteMask.cs rename to sources/Zenith.NET/Enums/ColorWrites.cs index cb5692c6..46af0abd 100644 --- a/sources/Zenith.NET/Enums/ColorWriteMask.cs +++ b/sources/Zenith.NET/Enums/ColorWrites.cs @@ -1,7 +1,7 @@ namespace Zenith.NET; [Flags] -public enum ColorWriteMask +public enum ColorWrites { None = 0, diff --git a/sources/Zenith.NET/Enums/ComparisonFunc.cs b/sources/Zenith.NET/Enums/CompareFunction.cs similarity index 83% rename from sources/Zenith.NET/Enums/ComparisonFunc.cs rename to sources/Zenith.NET/Enums/CompareFunction.cs index 067e247f..0a61b0e1 100644 --- a/sources/Zenith.NET/Enums/ComparisonFunc.cs +++ b/sources/Zenith.NET/Enums/CompareFunction.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum ComparisonFunc +public enum CompareFunction { Never, diff --git a/sources/Zenith.NET/Enums/ElementFormat.cs b/sources/Zenith.NET/Enums/ElementFormat.cs index 4e62dedd..eff6c589 100644 --- a/sources/Zenith.NET/Enums/ElementFormat.cs +++ b/sources/Zenith.NET/Enums/ElementFormat.cs @@ -14,17 +14,17 @@ public enum ElementFormat Byte4, - UByte1Normalized, + UByte1UNorm, - UByte2Normalized, + UByte2UNorm, - UByte4Normalized, + UByte4UNorm, - Byte1Normalized, + Byte1SNorm, - Byte2Normalized, + Byte2SNorm, - Byte4Normalized, + Byte4SNorm, UShort1, @@ -38,17 +38,17 @@ public enum ElementFormat Short4, - UShort1Normalized, + UShort1UNorm, - UShort2Normalized, + UShort2UNorm, - UShort4Normalized, + UShort4UNorm, - Short1Normalized, + Short1SNorm, - Short2Normalized, + Short2SNorm, - Short4Normalized, + Short4SNorm, Half1, diff --git a/sources/Zenith.NET/Enums/ElementSemantic.cs b/sources/Zenith.NET/Enums/ElementSemantic.cs index 76b28624..d5745f86 100644 --- a/sources/Zenith.NET/Enums/ElementSemantic.cs +++ b/sources/Zenith.NET/Enums/ElementSemantic.cs @@ -16,7 +16,5 @@ public enum ElementSemantic BlendIndices, - BlendWeight, - - Count + BlendWeight } diff --git a/sources/Zenith.NET/Enums/Filter.cs b/sources/Zenith.NET/Enums/Filter.cs deleted file mode 100644 index 12688eef..00000000 --- a/sources/Zenith.NET/Enums/Filter.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Zenith.NET; - -public enum Filter -{ - MinPointMagPointMipPoint, - - MinPointMagPointMipLinear, - - MinPointMagLinearMipPoint, - - MinPointMagLinearMipLinear, - - MinLinearMagPointMipPoint, - - MinLinearMagPointMipLinear, - - MinLinearMagLinearMipPoint, - - MinLinearMagLinearMipLinear, - - Anisotropic -} diff --git a/sources/Zenith.NET/Enums/FilterMode.cs b/sources/Zenith.NET/Enums/FilterMode.cs new file mode 100644 index 00000000..2f83dbdc --- /dev/null +++ b/sources/Zenith.NET/Enums/FilterMode.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public enum FilterMode +{ + Point, + + Linear +} diff --git a/sources/Zenith.NET/Enums/Backend.cs b/sources/Zenith.NET/Enums/GraphicsApi.cs similarity index 74% rename from sources/Zenith.NET/Enums/Backend.cs rename to sources/Zenith.NET/Enums/GraphicsApi.cs index 299ad2f2..fbaad376 100644 --- a/sources/Zenith.NET/Enums/Backend.cs +++ b/sources/Zenith.NET/Enums/GraphicsApi.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum Backend +public enum GraphicsApi { DirectX12, diff --git a/sources/Zenith.NET/Enums/LoadOp.cs b/sources/Zenith.NET/Enums/LoadOperation.cs similarity index 71% rename from sources/Zenith.NET/Enums/LoadOp.cs rename to sources/Zenith.NET/Enums/LoadOperation.cs index ccf14d19..3ef43181 100644 --- a/sources/Zenith.NET/Enums/LoadOp.cs +++ b/sources/Zenith.NET/Enums/LoadOperation.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum LoadOp +public enum LoadOperation { Load, diff --git a/sources/Zenith.NET/Enums/MessageSource.cs b/sources/Zenith.NET/Enums/MessageSource.cs index a2bc5f42..c0255dbb 100644 --- a/sources/Zenith.NET/Enums/MessageSource.cs +++ b/sources/Zenith.NET/Enums/MessageSource.cs @@ -4,5 +4,5 @@ public enum MessageSource { Framework, - GraphicsAPI + GraphicsApi } diff --git a/sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs b/sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs deleted file mode 100644 index 048a0a97..00000000 --- a/sources/Zenith.NET/Enums/RayTracingGeometryFlags.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum RayTracingGeometryFlags -{ - None = 0, - - Opaque = 1 << 0 -} diff --git a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs index 6d1d859f..50d9429b 100644 --- a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs +++ b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs @@ -5,9 +5,9 @@ public enum RayTracingInstanceFlags { None = 0, - TriangleCullDisable = 1 << 0, + CullDisable = 1 << 0, - TriangleFrontCounterClockwise = 1 << 1, + FrontCounterClockwise = 1 << 1, ForceOpaque = 1 << 2, diff --git a/sources/Zenith.NET/Enums/ResourceType.cs b/sources/Zenith.NET/Enums/ResourceType.cs index 8064f5bc..7a20dce5 100644 --- a/sources/Zenith.NET/Enums/ResourceType.cs +++ b/sources/Zenith.NET/Enums/ResourceType.cs @@ -2,15 +2,15 @@ public enum ResourceType { - ConstantBuffer, + UniformBuffer, - StructuredBuffer, + StorageReadOnlyBuffer, - StructuredBufferReadWrite, + StorageReadWriteBuffer, - Texture, + SampledTexture, - TextureReadWrite, + StorageTexture, Sampler, diff --git a/sources/Zenith.NET/Enums/ShaderStageFlags.cs b/sources/Zenith.NET/Enums/ShaderStages.cs similarity index 60% rename from sources/Zenith.NET/Enums/ShaderStageFlags.cs rename to sources/Zenith.NET/Enums/ShaderStages.cs index 29d1f842..38f94a19 100644 --- a/sources/Zenith.NET/Enums/ShaderStageFlags.cs +++ b/sources/Zenith.NET/Enums/ShaderStages.cs @@ -1,17 +1,17 @@ namespace Zenith.NET; [Flags] -public enum ShaderStageFlags +public enum ShaderStages { None = 0, Vertex = 1 << 0, - Pixel = 1 << 1, + Fragment = 1 << 1, Compute = 1 << 2, - Amplification = 1 << 3, + Task = 1 << 3, Mesh = 1 << 4 } \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/StencilOp.cs b/sources/Zenith.NET/Enums/StencilOperation.cs similarity index 85% rename from sources/Zenith.NET/Enums/StencilOp.cs rename to sources/Zenith.NET/Enums/StencilOperation.cs index f5490e38..61cfa89d 100644 --- a/sources/Zenith.NET/Enums/StencilOp.cs +++ b/sources/Zenith.NET/Enums/StencilOperation.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum StencilOp +public enum StencilOperation { Keep, diff --git a/sources/Zenith.NET/Enums/StoreOp.cs b/sources/Zenith.NET/Enums/StoreOperation.cs similarity index 66% rename from sources/Zenith.NET/Enums/StoreOp.cs rename to sources/Zenith.NET/Enums/StoreOperation.cs index 55a8eb45..256ff4f5 100644 --- a/sources/Zenith.NET/Enums/StoreOp.cs +++ b/sources/Zenith.NET/Enums/StoreOperation.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum StoreOp +public enum StoreOperation { Store, diff --git a/sources/Zenith.NET/Enums/TextureLayout.cs b/sources/Zenith.NET/Enums/TextureState.cs similarity index 59% rename from sources/Zenith.NET/Enums/TextureLayout.cs rename to sources/Zenith.NET/Enums/TextureState.cs index eb091656..5ff0db67 100644 --- a/sources/Zenith.NET/Enums/TextureLayout.cs +++ b/sources/Zenith.NET/Enums/TextureState.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum TextureLayout +public enum TextureState { RenderTarget, @@ -8,17 +8,17 @@ public enum TextureLayout DepthStencilReadOnly, - ShaderResource, + Sampled, - UnorderedAccess, + Storage, CopySrc, - CopyDest, + CopyDst, ResolveSrc, - ResolveDest, + ResolveDst, Present } diff --git a/sources/Zenith.NET/Enums/TextureUsageFlags.cs b/sources/Zenith.NET/Enums/TextureUsages.cs similarity index 55% rename from sources/Zenith.NET/Enums/TextureUsageFlags.cs rename to sources/Zenith.NET/Enums/TextureUsages.cs index 61835550..b3831cbf 100644 --- a/sources/Zenith.NET/Enums/TextureUsageFlags.cs +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -1,7 +1,7 @@ namespace Zenith.NET; [Flags] -public enum TextureUsageFlags +public enum TextureUsages { None = 0, @@ -9,7 +9,7 @@ public enum TextureUsageFlags DepthStencil = 1 << 1, - ShaderResource = 1 << 2, + Sampled = 1 << 2, - UnorderedAccess = 1 << 3 + Storage = 1 << 3 } diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 40bdf2e1..5313d0d2 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -2,11 +2,9 @@ public abstract class GraphicsContext : DisposableObject, INativeObject { - public const uint ConstantBufferAlignment = 256; - - protected GraphicsContext(Backend backend, bool useValidationLayer) + protected GraphicsContext(GraphicsApi api, bool useValidationLayer) { - Backend = backend; + Api = api; Initialize(useValidationLayer, out Capabilities capabilities, @@ -25,7 +23,7 @@ protected GraphicsContext(Backend backend, bool useValidationLayer) Downloader = new(this); } - public Backend Backend { get; } + public GraphicsApi Api { get; } public Capabilities Capabilities { get; } diff --git a/sources/Zenith.NET/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs index 039bae6e..b3772e21 100644 --- a/sources/Zenith.NET/RasterizerStates.cs +++ b/sources/Zenith.NET/RasterizerStates.cs @@ -9,7 +9,7 @@ public static class RasterizerStates FrontFace = FrontFace.CounterClockwise, DepthBias = 0, DepthBiasClamp = 0.0f, - SlopeScaledDepthBias = 0.0f, + DepthBiasSlopeScale = 0.0f, DepthClipEnable = true, ScissorEnable = false }; diff --git a/sources/Zenith.NET/Structs/AttachmentFormats.cs b/sources/Zenith.NET/Structs/AttachmentFormats.cs new file mode 100644 index 00000000..c00301b9 --- /dev/null +++ b/sources/Zenith.NET/Structs/AttachmentFormats.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public record struct AttachmentFormats +{ + public PixelFormat[] Colors; + + public PixelFormat? DepthStencil; + + public SampleCount SampleCount; +} diff --git a/sources/Zenith.NET/Structs/BlendState.cs b/sources/Zenith.NET/Structs/BlendState.cs index 5e11d7f5..e4b619bc 100644 --- a/sources/Zenith.NET/Structs/BlendState.cs +++ b/sources/Zenith.NET/Structs/BlendState.cs @@ -21,4 +21,4 @@ public record struct BlendState public BlendStateRenderTarget RenderTarget6; public BlendStateRenderTarget RenderTarget7; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs index f7330c38..4187a097 100644 --- a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs +++ b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs @@ -4,17 +4,17 @@ public record struct BlendStateRenderTarget { public bool BlendEnable; - public Blend SrcBlend; + public BlendFactor SrcFactor; - public Blend DestBlend; + public BlendFactor DstFactor; - public BlendOp BlendOp; + public BlendOperation Operation; - public Blend SrcBlendAlpha; + public BlendFactor SrcFactorAlpha; - public Blend DestBlendAlpha; + public BlendFactor DstFactorAlpha; - public BlendOp BlendOpAlpha; + public BlendOperation OperationAlpha; - public ColorWriteMask WriteMask; + public ColorWrites Writes; } diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index de4a5743..cd67d952 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -6,5 +6,7 @@ public record struct BufferDesc public uint StrideInBytes; - public BufferUsageFlags Flags; + public BufferAccess Access; + + public BufferUsages Usages; } diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 8a690199..864f821c 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -12,9 +12,9 @@ public record struct ColorAttachment public TextureSubresource ResolveSubresource; - public LoadOp LoadOp; + public LoadOperation Load; - public StoreOp StoreOp; + public StoreOperation Store; public Vector4 ClearColor; } diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index 1011458f..35c80440 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -4,11 +4,5 @@ public record struct ComputePipelineDesc { public Shader Compute; - public ResourceBinding[] ResourceBindings; - - public uint ThreadGroupSizeX; - - public uint ThreadGroupSizeY; - - public uint ThreadGroupSizeZ; + public ResourceBinding[] Bindings; } diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs index b0a9bf19..5e702961 100644 --- a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -6,13 +6,13 @@ public record struct DepthStencilAttachment public TextureSubresource Subresource; - public LoadOp DepthLoadOp; + public LoadOperation DepthLoad; - public StoreOp DepthStoreOp; + public StoreOperation DepthStore; - public LoadOp StencilLoadOp; + public LoadOperation StencilLoad; - public StoreOp StencilStoreOp; + public StoreOperation StencilStore; public float ClearDepth; diff --git a/sources/Zenith.NET/Structs/DepthStencilState.cs b/sources/Zenith.NET/Structs/DepthStencilState.cs index d96c15e5..d569fce7 100644 --- a/sources/Zenith.NET/Structs/DepthStencilState.cs +++ b/sources/Zenith.NET/Structs/DepthStencilState.cs @@ -6,7 +6,7 @@ public record struct DepthStencilState public bool DepthWriteEnable; - public ComparisonFunc DepthFunc; + public CompareFunction DepthCompare; public bool StencilEnable; @@ -14,7 +14,7 @@ public record struct DepthStencilState public byte StencilWriteMask; - public DepthStencilStateOp FrontFace; + public StencilFaceState FrontFace; - public DepthStencilStateOp BackFace; + public StencilFaceState BackFace; } diff --git a/sources/Zenith.NET/Structs/DepthStencilStateOp.cs b/sources/Zenith.NET/Structs/DepthStencilStateOp.cs deleted file mode 100644 index 97dc42d3..00000000 --- a/sources/Zenith.NET/Structs/DepthStencilStateOp.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Zenith.NET; - -public record struct DepthStencilStateOp -{ - public StencilOp StencilFailOp; - - public StencilOp StencilDepthFailOp; - - public StencilOp StencilPassOp; - - public ComparisonFunc StencilFunc; -} diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index d1d2ceb4..17005e09 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -6,13 +6,13 @@ public record struct GraphicsPipelineDesc public Shader Vertex; - public Shader Pixel; + public Shader Fragment; - public ResourceBinding[] ResourceBindings; + public ResourceBinding[] Bindings; - public InputLayout[] InputLayouts; + public InputLayout[] Layouts; public PrimitiveTopology PrimitiveTopology; - public Output Output; + public AttachmentFormats Formats; } diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index ab2cd004..f44ba6f1 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -4,27 +4,15 @@ public record struct MeshShadingPipelineDesc { public RenderStates RenderStates; - public Shader? Amplification; + public Shader? Task; public Shader Mesh; - public Shader Pixel; + public Shader Fragment; - public ResourceBinding[] ResourceBindings; + public ResourceBinding[] Bindings; public PrimitiveTopology PrimitiveTopology; - public Output Output; - - public uint AmplificationThreadGroupSizeX; - - public uint AmplificationThreadGroupSizeY; - - public uint AmplificationThreadGroupSizeZ; - - public uint MeshThreadGroupSizeX; - - public uint MeshThreadGroupSizeY; - - public uint MeshThreadGroupSizeZ; + public AttachmentFormats Formats; } diff --git a/sources/Zenith.NET/Structs/Output.cs b/sources/Zenith.NET/Structs/Output.cs deleted file mode 100644 index a46cac92..00000000 --- a/sources/Zenith.NET/Structs/Output.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Zenith.NET; - -public record struct Output -{ - public PixelFormat[] ColorAttachments; - - public PixelFormat? DepthStencilAttachment; - - public SampleCount SampleCount; -} diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index 96051992..5b3a6644 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -12,7 +12,7 @@ public record struct RasterizerState public float DepthBiasClamp; - public float SlopeScaledDepthBias; + public float DepthBiasSlopeScale; public bool DepthClipEnable; diff --git a/sources/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Zenith.NET/Structs/RayTracingGeometry.cs index 3ad5e841..660506bb 100644 --- a/sources/Zenith.NET/Structs/RayTracingGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingGeometry.cs @@ -8,5 +8,5 @@ public record struct RayTracingGeometry public RayTracingAABBs AABBs; - public RayTracingGeometryFlags Flags; + public bool Opaque; } diff --git a/sources/Zenith.NET/Structs/RayTracingInstance.cs b/sources/Zenith.NET/Structs/RayTracingInstance.cs index 599c7ead..9e294c32 100644 --- a/sources/Zenith.NET/Structs/RayTracingInstance.cs +++ b/sources/Zenith.NET/Structs/RayTracingInstance.cs @@ -6,9 +6,9 @@ public record struct RayTracingInstance { public BottomLevelAccelerationStructure AccelerationStructure; - public uint ID; + public uint Id; - public byte Mask; + public byte VisibilityMask; public Matrix4x4 Transform; diff --git a/sources/Zenith.NET/Structs/RenderStates.cs b/sources/Zenith.NET/Structs/RenderStates.cs index 964ba5be..5feda2ef 100644 --- a/sources/Zenith.NET/Structs/RenderStates.cs +++ b/sources/Zenith.NET/Structs/RenderStates.cs @@ -12,5 +12,5 @@ public record struct RenderStates public uint StencilReference; - public Vector4? BlendFactor; + public Vector4? BlendConstant; } diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 1a08e29f..6245f77a 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -2,15 +2,19 @@ public record struct SamplerDesc { - public Filter Filter; + public FilterMode MinFilter; - public AddressMode U; + public FilterMode MagFilter; - public AddressMode V; + public FilterMode MipmapFilter; - public AddressMode W; + public AddressMode AddressU; - public ComparisonFunc ComparisonFunc; + public AddressMode AddressV; + + public AddressMode AddressW; + + public CompareFunction Compare; public uint MaxAnisotropy; diff --git a/sources/Zenith.NET/Structs/ShaderDesc.cs b/sources/Zenith.NET/Structs/ShaderDesc.cs index 206cfb0c..958d780e 100644 --- a/sources/Zenith.NET/Structs/ShaderDesc.cs +++ b/sources/Zenith.NET/Structs/ShaderDesc.cs @@ -2,9 +2,9 @@ public record struct ShaderDesc { - public byte[] ShaderBytes; + public byte[] Bytecode; public string EntryPoint; - public ShaderStageFlags Stage; + public ShaderStages Stage; } diff --git a/sources/Zenith.NET/Structs/StencilFaceState.cs b/sources/Zenith.NET/Structs/StencilFaceState.cs new file mode 100644 index 00000000..6e31fc56 --- /dev/null +++ b/sources/Zenith.NET/Structs/StencilFaceState.cs @@ -0,0 +1,12 @@ +namespace Zenith.NET; + +public record struct StencilFaceState +{ + public StencilOperation Fail; + + public StencilOperation DepthFail; + + public StencilOperation Pass; + + public CompareFunction Compare; +} diff --git a/sources/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Zenith.NET/Structs/SwapChainDesc.cs index f2d5eb46..5abdcb49 100644 --- a/sources/Zenith.NET/Structs/SwapChainDesc.cs +++ b/sources/Zenith.NET/Structs/SwapChainDesc.cs @@ -4,7 +4,7 @@ public record struct SwapChainDesc { public Surface Surface; - public PixelFormat ColorTargetFormat; + public PixelFormat ColorFormat; - public PixelFormat? DepthStencilTargetFormat; + public PixelFormat? DepthStencilFormat; } diff --git a/sources/Zenith.NET/Structs/TextureData.cs b/sources/Zenith.NET/Structs/TextureData.cs index 6d74d0fa..f6f866dd 100644 --- a/sources/Zenith.NET/Structs/TextureData.cs +++ b/sources/Zenith.NET/Structs/TextureData.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public record struct TextureData { diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index 770c42b3..4b14f619 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -18,5 +18,5 @@ public record struct TextureDesc public SampleCount SampleCount; - public TextureUsageFlags Flags; + public TextureUsages Usages; } diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index 30f3b454..8293b3df 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -21,7 +21,7 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 1, - Flags = BufferUsageFlags.MapWrite + Access = BufferAccess.CpuWriteOnly })); } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 207f0343..353d47d5 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -23,11 +23,11 @@ internal void ValidateDesc(SwapChainDesc desc) { CheckSurface("SwapChainDesc.Surface", desc.Surface); - CheckEnum("SwapChainDesc.ColorTargetFormat", desc.ColorTargetFormat); + CheckEnum("SwapChainDesc.ColorFormat", desc.ColorFormat); - if (desc.DepthStencilTargetFormat is { } format) + if (desc.DepthStencilFormat is { } format) { - CheckEnum("SwapChainDesc.DepthStencilTargetFormat", format); + CheckEnum("SwapChainDesc.DepthStencilFormat", format); } } @@ -40,9 +40,20 @@ internal void ValidateDesc(BufferDesc desc) ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); } - if (desc.Flags is BufferUsageFlags.None) + CheckEnum("BufferDesc.Access", desc.Access); + + if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) { - ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "BufferDesc.Flags")); + const BufferUsages GpuOnlyUsages = BufferUsages.StorageReadWrite + | BufferUsages.Indirect + | BufferUsages.AccelerationStructure; + + BufferUsages forbidden = desc.Usages & GpuOnlyUsages; + + if (forbidden is not BufferUsages.None) + { + ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbidden, desc.Access)); + } } } @@ -76,9 +87,9 @@ internal void ValidateDesc(TextureDesc desc) CheckEnum("TextureDesc.SampleCount", desc.SampleCount); - if (desc.Flags is TextureUsageFlags.None) + if (desc.Usages is TextureUsages.None) { - ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Flags")); + ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Usages")); } } @@ -141,16 +152,13 @@ internal void ValidateDesc(TextureViewDesc desc) internal void ValidateDesc(SamplerDesc desc) { - CheckEnum("SamplerDesc.Filter", desc.Filter); - CheckEnum("SamplerDesc.U", desc.U); - CheckEnum("SamplerDesc.V", desc.V); - CheckEnum("SamplerDesc.W", desc.W); - CheckEnum("SamplerDesc.ComparisonFunc", desc.ComparisonFunc); - - if (desc.Filter is Filter.Anisotropic && desc.MaxAnisotropy is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "SamplerDesc.MaxAnisotropy")); - } + CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); + CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); + CheckEnum("SamplerDesc.MipmapFilter", desc.MipmapFilter); + CheckEnum("SamplerDesc.AddressU", desc.AddressU); + CheckEnum("SamplerDesc.AddressV", desc.AddressV); + CheckEnum("SamplerDesc.AddressW", desc.AddressW); + CheckEnum("SamplerDesc.Compare", desc.Compare); if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) { @@ -180,7 +188,7 @@ internal void ValidateDesc(ResourceTableDesc desc) internal void ValidateDesc(ShaderDesc desc) { - CheckArrayNotEmpty("ShaderDesc.ShaderBytes", desc.ShaderBytes); + CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); CheckEnum("ShaderDesc.Stage", desc.Stage); } @@ -190,68 +198,53 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) CheckRenderStates("GraphicsPipelineDesc.RenderStates", desc.RenderStates); CheckResource("GraphicsPipelineDesc.Vertex", desc.Vertex); - CheckResource("GraphicsPipelineDesc.Pixel", desc.Pixel); + CheckResource("GraphicsPipelineDesc.Fragment", desc.Fragment); - CheckResourceBindings("GraphicsPipelineDesc.ResourceBindings", desc.ResourceBindings); + CheckResourceBindings("GraphicsPipelineDesc.Bindings", desc.Bindings); - if (desc.InputLayouts is null) + if (desc.Layouts is null) { - ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); + ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.Layouts")); } else { - for (int i = 0; i < desc.InputLayouts.Length; i++) + for (int i = 0; i < desc.Layouts.Length; i++) { - CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{i}]", desc.InputLayouts[i]); + CheckInputLayout($"GraphicsPipelineDesc.Layouts[{i}]", desc.Layouts[i]); } } CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); - CheckOutput("GraphicsPipelineDesc.Output", desc.Output); + CheckAttachmentFormats("GraphicsPipelineDesc.Formats", desc.Formats); } internal void ValidateDesc(ComputePipelineDesc desc) { CheckResource("ComputePipelineDesc.Compute", desc.Compute); - CheckResourceBindings("ComputePipelineDesc.ResourceBindings", desc.ResourceBindings); - - if (desc.ThreadGroupSizeX is 0 || desc.ThreadGroupSizeY is 0 || desc.ThreadGroupSizeZ is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "ComputePipelineDesc thread group sizes (ThreadGroupSizeX, ThreadGroupSizeY, ThreadGroupSizeZ)")); - } + CheckResourceBindings("ComputePipelineDesc.Bindings", desc.Bindings); } internal void ValidateDesc(MeshShadingPipelineDesc desc) { CheckRenderStates("MeshShadingPipelineDesc.RenderStates", desc.RenderStates); - if (desc.Amplification is not null) + if (desc.Task is not null) { - CheckResource("MeshShadingPipelineDesc.Amplification", desc.Amplification); + CheckResource("MeshShadingPipelineDesc.Task", desc.Task); } CheckResource("MeshShadingPipelineDesc.Mesh", desc.Mesh); - CheckResource("MeshShadingPipelineDesc.Pixel", desc.Pixel); + CheckResource("MeshShadingPipelineDesc.Fragment", desc.Fragment); - CheckResourceBindings("MeshShadingPipelineDesc.ResourceBindings", desc.ResourceBindings); + CheckResourceBindings("MeshShadingPipelineDesc.Bindings", desc.Bindings); if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) { ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); } - CheckOutput("MeshShadingPipelineDesc.Output", desc.Output); - - if (desc.Amplification is not null && (desc.AmplificationThreadGroupSizeX is 0 || desc.AmplificationThreadGroupSizeY is 0 || desc.AmplificationThreadGroupSizeZ is 0)) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc amplification thread group sizes (AmplificationThreadGroupSizeX, AmplificationThreadGroupSizeY, AmplificationThreadGroupSizeZ)")); - } - - if (desc.MeshThreadGroupSizeX is 0 || desc.MeshThreadGroupSizeY is 0 || desc.MeshThreadGroupSizeZ is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc mesh thread group sizes (MeshThreadGroupSizeX, MeshThreadGroupSizeY, MeshThreadGroupSizeZ)")); - } + CheckAttachmentFormats("MeshShadingPipelineDesc.Formats", desc.Formats); } internal void ValidateDesc(QueryHeapDesc desc) @@ -451,64 +444,77 @@ private void CheckInputLayout(string name, InputLayout inputLayout) private void CheckRenderStates(string name, RenderStates renderStates) { - CheckEnum($"{name}.RasterizerState.FillMode", renderStates.RasterizerState.FillMode); - CheckEnum($"{name}.RasterizerState.CullMode", renderStates.RasterizerState.CullMode); - CheckEnum($"{name}.RasterizerState.FrontFace", renderStates.RasterizerState.FrontFace); + CheckRasterizerState($"{name}.RasterizerState", renderStates.RasterizerState); + CheckDepthStencilState($"{name}.DepthStencilState", renderStates.DepthStencilState); + CheckBlendState($"{name}.BlendState", renderStates.BlendState); + } + + private void CheckRasterizerState(string name, RasterizerState rasterizerState) + { + CheckEnum($"{name}.FillMode", rasterizerState.FillMode); + CheckEnum($"{name}.CullMode", rasterizerState.CullMode); + CheckEnum($"{name}.FrontFace", rasterizerState.FrontFace); + } - CheckEnum($"{name}.DepthStencilState.DepthFunc", renderStates.DepthStencilState.DepthFunc); - CheckDepthStencilStateOp($"{name}.DepthStencilState.FrontFace", renderStates.DepthStencilState.FrontFace); - CheckDepthStencilStateOp($"{name}.DepthStencilState.BackFace", renderStates.DepthStencilState.BackFace); + private void CheckDepthStencilState(string name, DepthStencilState depthStencilState) + { + CheckEnum($"{name}.DepthCompare", depthStencilState.DepthCompare); + CheckStencilFaceState($"{name}.FrontFace", depthStencilState.FrontFace); + CheckStencilFaceState($"{name}.BackFace", depthStencilState.BackFace); + } - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget0", renderStates.BlendState.RenderTarget0); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget1", renderStates.BlendState.RenderTarget1); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget2", renderStates.BlendState.RenderTarget2); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget3", renderStates.BlendState.RenderTarget3); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget4", renderStates.BlendState.RenderTarget4); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget5", renderStates.BlendState.RenderTarget5); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget6", renderStates.BlendState.RenderTarget6); - CheckBlendStateRenderTarget($"{name}.BlendState.RenderTarget7", renderStates.BlendState.RenderTarget7); + private void CheckBlendState(string name, BlendState blendState) + { + CheckBlendStateRenderTarget($"{name}.RenderTarget0", blendState.RenderTarget0); + CheckBlendStateRenderTarget($"{name}.RenderTarget1", blendState.RenderTarget1); + CheckBlendStateRenderTarget($"{name}.RenderTarget2", blendState.RenderTarget2); + CheckBlendStateRenderTarget($"{name}.RenderTarget3", blendState.RenderTarget3); + CheckBlendStateRenderTarget($"{name}.RenderTarget4", blendState.RenderTarget4); + CheckBlendStateRenderTarget($"{name}.RenderTarget5", blendState.RenderTarget5); + CheckBlendStateRenderTarget($"{name}.RenderTarget6", blendState.RenderTarget6); + CheckBlendStateRenderTarget($"{name}.RenderTarget7", blendState.RenderTarget7); } - private void CheckDepthStencilStateOp(string name, DepthStencilStateOp op) + private void CheckStencilFaceState(string name, StencilFaceState face) { - CheckEnum($"{name}.StencilFailOp", op.StencilFailOp); - CheckEnum($"{name}.StencilDepthFailOp", op.StencilDepthFailOp); - CheckEnum($"{name}.StencilPassOp", op.StencilPassOp); - CheckEnum($"{name}.StencilFunc", op.StencilFunc); + CheckEnum($"{name}.Fail", face.Fail); + CheckEnum($"{name}.DepthFail", face.DepthFail); + CheckEnum($"{name}.Pass", face.Pass); + CheckEnum($"{name}.Compare", face.Compare); } private void CheckBlendStateRenderTarget(string name, BlendStateRenderTarget renderTarget) { - CheckEnum($"{name}.SrcBlend", renderTarget.SrcBlend); - CheckEnum($"{name}.DestBlend", renderTarget.DestBlend); - CheckEnum($"{name}.BlendOp", renderTarget.BlendOp); - CheckEnum($"{name}.SrcBlendAlpha", renderTarget.SrcBlendAlpha); - CheckEnum($"{name}.DestBlendAlpha", renderTarget.DestBlendAlpha); - CheckEnum($"{name}.BlendOpAlpha", renderTarget.BlendOpAlpha); + CheckEnum($"{name}.SrcFactor", renderTarget.SrcFactor); + CheckEnum($"{name}.DstFactor", renderTarget.DstFactor); + CheckEnum($"{name}.Operation", renderTarget.Operation); + CheckEnum($"{name}.SrcFactorAlpha", renderTarget.SrcFactorAlpha); + CheckEnum($"{name}.DstFactorAlpha", renderTarget.DstFactorAlpha); + CheckEnum($"{name}.OperationAlpha", renderTarget.OperationAlpha); } - private void CheckOutput(string name, Output output) + private void CheckAttachmentFormats(string name, AttachmentFormats layout) { - if (output.ColorAttachments is null) + if (layout.Colors is null) { ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorAttachments")); return; } - for (int i = 0; i < output.ColorAttachments.Length; i++) + for (int i = 0; i < layout.Colors.Length; i++) { - CheckEnum($"{name}.ColorAttachments[{i}]", output.ColorAttachments[i]); + CheckEnum($"{name}.ColorAttachments[{i}]", layout.Colors[i]); } - if (output.DepthStencilAttachment is { } format) + if (layout.DepthStencil is { } format) { CheckEnum($"{name}.DepthStencilAttachment", format); } - CheckEnum($"{name}.SampleCount", output.SampleCount); + CheckEnum($"{name}.SampleCount", layout.SampleCount); - if (output.ColorAttachments.Length is 0 && output.DepthStencilAttachment is null) + if (layout.Colors.Length is 0 && layout.DepthStencil is null) { ReportWarning(string.Format(ValidationMessages.HasNoAttachments, name)); } @@ -597,9 +603,9 @@ private void CheckRayTracingInstance(string name, RayTracingInstance instance) return; } - if (instance.ID > ValidationConstants.MaxRayTracingInstanceID) + if (instance.Id > ValidationConstants.MaxRayTracingInstanceId) { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.ID", ValidationConstants.MaxRayTracingInstanceID)); + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.Id", ValidationConstants.MaxRayTracingInstanceId)); } } @@ -624,7 +630,7 @@ file static class ValidationConstants public const int IndexSizeUInt32 = 4; - public const int MaxRayTracingInstanceID = 16777215; + public const int MaxRayTracingInstanceId = 16777215; } file static class ValidationMessages @@ -672,4 +678,6 @@ file static class ValidationMessages public const string MustHaveFlag = "{0} must have the flag '{1}' set."; public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; + + public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; } diff --git a/sources/Zenith.NET/ZenithHelper.cs b/sources/Zenith.NET/ZenithHelper.cs index d5555351..c6f4d29d 100644 --- a/sources/Zenith.NET/ZenithHelper.cs +++ b/sources/Zenith.NET/ZenithHelper.cs @@ -213,27 +213,27 @@ public static uint SizeInBytes(ElementFormat elementFormat) { ElementFormat.UByte1 or ElementFormat.Byte1 or - ElementFormat.UByte1Normalized or - ElementFormat.Byte1Normalized => 1, + ElementFormat.UByte1UNorm or + ElementFormat.Byte1SNorm => 1, ElementFormat.UByte2 or ElementFormat.Byte2 or - ElementFormat.UByte2Normalized or - ElementFormat.Byte2Normalized or + ElementFormat.UByte2UNorm or + ElementFormat.Byte2SNorm or ElementFormat.UShort1 or ElementFormat.Short1 or - ElementFormat.UShort1Normalized or - ElementFormat.Short1Normalized or + ElementFormat.UShort1UNorm or + ElementFormat.Short1SNorm or ElementFormat.Half1 => 2, ElementFormat.UByte4 or ElementFormat.Byte4 or - ElementFormat.UByte4Normalized or - ElementFormat.Byte4Normalized or + ElementFormat.UByte4UNorm or + ElementFormat.Byte4SNorm or ElementFormat.UShort2 or ElementFormat.Short2 or - ElementFormat.UShort2Normalized or - ElementFormat.Short2Normalized or + ElementFormat.UShort2UNorm or + ElementFormat.Short2SNorm or ElementFormat.Half2 or ElementFormat.Float1 or ElementFormat.UInt1 or @@ -241,8 +241,8 @@ ElementFormat.UInt1 or ElementFormat.UShort4 or ElementFormat.Short4 or - ElementFormat.UShort4Normalized or - ElementFormat.Short4Normalized or + ElementFormat.UShort4UNorm or + ElementFormat.Short4SNorm or ElementFormat.Half4 or ElementFormat.Float2 or ElementFormat.UInt2 or From f801a96c19db70b16f9906e2581c07a7957f29ec Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 2 May 2026 14:21:20 +0800 Subject: [PATCH 053/466] Refactor Zenith.NET API for clarity and consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Large-scale API overhaul: - Renamed many structs and properties for clarity (e.g., RenderTarget0 → ColorAttachment0, SrcFactor → SourceRgbBlendFactor). - Replaced BlendStateRenderTarget with ColorAttachmentBlendState. - Switched from ResourceBinding to ResourceLayout throughout. - Updated ray tracing geometry types and related enums. - Renamed Surface.Handles to NativeHandles and updated validation. - Improved validation logic for new/renamed properties and flag enums. - Renamed properties in attachment and pipeline descriptors for consistency. - Updated buffer/texture upload/download to use CopyQueue.AcquireCommandBuffer(). - Replaced ValidationMessageArgs with ValidationMessageEventArgs. - Added new struct files: ColorAttachmentBlendState, RayTracingAabbGeometry, RayTracingTriangleGeometry, RenderState, ResourceLayout. - General modernization and improved type safety across the API. --- sources/Zenith.NET/BlendStates.cs | 66 ++-- sources/Zenith.NET/Buffer.cs | 4 +- sources/Zenith.NET/Capabilities.cs | 4 +- sources/Zenith.NET/CommandQueue.cs | 2 +- sources/Zenith.NET/DepthStencilStates.cs | 32 +- sources/Zenith.NET/Enums/BlendFactor.cs | 16 +- .../Enums/RayTracingGeometryType.cs | 4 +- sources/Zenith.NET/Enums/TextureState.cs | 2 +- sources/Zenith.NET/Enums/TextureUsages.cs | 2 +- sources/Zenith.NET/GraphicsContext.cs | 34 +- sources/Zenith.NET/RasterizerStates.cs | 4 +- .../Zenith.NET/Structs/AttachmentFormats.cs | 4 +- sources/Zenith.NET/Structs/BlendState.cs | 20 +- .../Structs/BlendStateRenderTarget.cs | 20 -- .../BottomLevelAccelerationStructureDesc.cs | 2 +- sources/Zenith.NET/Structs/ColorAttachment.cs | 4 +- .../Structs/ColorAttachmentBlendState.cs | 20 ++ .../Zenith.NET/Structs/ComputePipelineDesc.cs | 4 +- .../Structs/DepthStencilAttachment.cs | 8 +- .../Zenith.NET/Structs/DepthStencilState.cs | 8 +- .../Structs/GraphicsPipelineDesc.cs | 12 +- sources/Zenith.NET/Structs/InputElement.cs | 2 +- sources/Zenith.NET/Structs/InputLayout.cs | 10 +- .../Structs/MeshShadingPipelineDesc.cs | 12 +- sources/Zenith.NET/Structs/RasterizerState.cs | 4 +- ...cingAABBs.cs => RayTracingAabbGeometry.cs} | 6 +- .../Zenith.NET/Structs/RayTracingGeometry.cs | 6 +- .../Zenith.NET/Structs/RayTracingInstance.cs | 2 +- ...ngles.cs => RayTracingTriangleGeometry.cs} | 6 +- .../{RenderStates.cs => RenderState.cs} | 6 +- sources/Zenith.NET/Structs/ResourceBinding.cs | 8 - sources/Zenith.NET/Structs/ResourceLayout.cs | 8 + .../Zenith.NET/Structs/ResourceTableDesc.cs | 2 +- sources/Zenith.NET/Structs/SamplerDesc.cs | 2 +- .../Zenith.NET/Structs/StencilFaceState.cs | 8 +- sources/Zenith.NET/Structs/Surface.cs | 14 +- .../Zenith.NET/Structs/TextureDataLayout.cs | 4 +- .../TopLevelAccelerationStructureDesc.cs | 2 +- sources/Zenith.NET/SwapChain.cs | 4 +- sources/Zenith.NET/Texture.cs | 4 +- sources/Zenith.NET/ValidationLayer.cs | 300 ++++++++++-------- ...eArgs.cs => ValidationMessageEventArgs.cs} | 4 +- sources/Zenith.NET/ZenithHelper.cs | 6 +- 43 files changed, 359 insertions(+), 333 deletions(-) delete mode 100644 sources/Zenith.NET/Structs/BlendStateRenderTarget.cs create mode 100644 sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs rename sources/Zenith.NET/Structs/{RayTracingAABBs.cs => RayTracingAabbGeometry.cs} (63%) rename sources/Zenith.NET/Structs/{RayTracingTriangles.cs => RayTracingTriangleGeometry.cs} (83%) rename sources/Zenith.NET/Structs/{RenderStates.cs => RenderState.cs} (78%) delete mode 100644 sources/Zenith.NET/Structs/ResourceBinding.cs create mode 100644 sources/Zenith.NET/Structs/ResourceLayout.cs rename sources/Zenith.NET/{ValidationMessageArgs.cs => ValidationMessageEventArgs.cs} (62%) diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs index 6de604be..da8d1666 100644 --- a/sources/Zenith.NET/BlendStates.cs +++ b/sources/Zenith.NET/BlendStates.cs @@ -4,70 +4,70 @@ public static class BlendStates { public static readonly BlendState Default = new() { - AlphaToCoverageEnable = false, - IndependentBlendEnable = false, - RenderTarget0 = new() + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, + ColorAttachment0 = new() { - SrcFactor = BlendFactor.One, - DstFactor = BlendFactor.Zero, - Operation = BlendOperation.Add, - SrcFactorAlpha = BlendFactor.One, - DstFactorAlpha = BlendFactor.Zero, - OperationAlpha = BlendOperation.Add, - Writes = ColorWrites.All + SourceRgbBlendFactor = BlendFactor.One, + DestinationRgbBlendFactor = BlendFactor.Zero, + RgbBlendOperation = BlendOperation.Add, + SourceAlphaBlendFactor = BlendFactor.One, + DestinationAlphaBlendFactor = BlendFactor.Zero, + AlphaBlendOperation = BlendOperation.Add, + ColorWrites = ColorWrites.All } }; public static readonly BlendState Additive = new() { - RenderTarget0 = Default.RenderTarget0 with + ColorAttachment0 = Default.ColorAttachment0 with { - BlendEnable = true, - SrcFactor = BlendFactor.SrcAlpha, - DstFactor = BlendFactor.One, - SrcFactorAlpha = BlendFactor.SrcAlpha, - DstFactorAlpha = BlendFactor.One + IsBlendingEnabled = true, + SourceRgbBlendFactor = BlendFactor.SourceAlpha, + DestinationRgbBlendFactor = BlendFactor.One, + SourceAlphaBlendFactor = BlendFactor.SourceAlpha, + DestinationAlphaBlendFactor = BlendFactor.One } }; public static readonly BlendState AlphaBlend = new() { - RenderTarget0 = Default.RenderTarget0 with + ColorAttachment0 = Default.ColorAttachment0 with { - BlendEnable = true, - SrcFactor = BlendFactor.One, - DstFactor = BlendFactor.OneMinusSrcAlpha, - SrcFactorAlpha = BlendFactor.One, - DstFactorAlpha = BlendFactor.OneMinusSrcAlpha + IsBlendingEnabled = true, + SourceRgbBlendFactor = BlendFactor.One, + DestinationRgbBlendFactor = BlendFactor.OneMinusSourceAlpha, + SourceAlphaBlendFactor = BlendFactor.One, + DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha } }; public static readonly BlendState NonPremultiplied = new() { - RenderTarget0 = Default.RenderTarget0 with + ColorAttachment0 = Default.ColorAttachment0 with { - BlendEnable = true, - SrcFactor = BlendFactor.SrcAlpha, - DstFactor = BlendFactor.OneMinusSrcAlpha, - SrcFactorAlpha = BlendFactor.SrcAlpha, - DstFactorAlpha = BlendFactor.OneMinusSrcAlpha + IsBlendingEnabled = true, + SourceRgbBlendFactor = BlendFactor.SourceAlpha, + DestinationRgbBlendFactor = BlendFactor.OneMinusSourceAlpha, + SourceAlphaBlendFactor = BlendFactor.SourceAlpha, + DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha } }; public static readonly BlendState Opaque = new() { - RenderTarget0 = Default.RenderTarget0 with + ColorAttachment0 = Default.ColorAttachment0 with { - BlendEnable = false + IsBlendingEnabled = false } }; public static readonly BlendState ColorDisabled = new() { - RenderTarget0 = Default.RenderTarget0 with + ColorAttachment0 = Default.ColorAttachment0 with { - BlendEnable = false, - Writes = ColorWrites.None + IsBlendingEnabled = false, + ColorWrites = ColorWrites.None } }; } diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index ae78cbdb..ab360017 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -25,7 +25,7 @@ public void Upload(uint offsetInBytes, BufferData data) } else { - CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); commandBuffer.Upload(this, offsetInBytes, data); commandBuffer.Submit().Wait(); @@ -47,7 +47,7 @@ public void Download(uint offsetInBytes, BufferData data) } else { - CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); commandBuffer.Download(this, offsetInBytes, data); commandBuffer.Submit().Wait(); diff --git a/sources/Zenith.NET/Capabilities.cs b/sources/Zenith.NET/Capabilities.cs index 49070ed8..2fb03389 100644 --- a/sources/Zenith.NET/Capabilities.cs +++ b/sources/Zenith.NET/Capabilities.cs @@ -4,7 +4,7 @@ public abstract class Capabilities { public abstract string DeviceName { get; } - public abstract bool RayTracingSupported { get; } + public abstract bool SupportsRayTracing { get; } - public abstract bool MeshShadingSupported { get; } + public abstract bool SupportsMeshShading { get; } } diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 4bfa4ca8..71200616 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -10,7 +10,7 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ public CommandQueueType Type { get; } = type; - public CommandBuffer CommandBuffer() + public CommandBuffer AcquireCommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs index 9387c324..5f631e0d 100644 --- a/sources/Zenith.NET/DepthStencilStates.cs +++ b/sources/Zenith.NET/DepthStencilStates.cs @@ -4,41 +4,41 @@ public static class DepthStencilStates { public static readonly DepthStencilState Default = new() { - DepthEnable = true, - DepthWriteEnable = true, - DepthCompare = CompareFunction.LessEqual, - StencilEnable = false, + IsDepthEnabled = true, + IsDepthWriteEnabled = true, + DepthCompareFunction = CompareFunction.LessEqual, + IsStencilEnabled = false, StencilReadMask = 0xFF, StencilWriteMask = 0xFF, FrontFace = new() { - Fail = StencilOperation.Keep, - DepthFail = StencilOperation.Keep, - Pass = StencilOperation.Keep, - Compare = CompareFunction.Always + FailOperation = StencilOperation.Keep, + DepthFailOperation = StencilOperation.Keep, + PassOperation = StencilOperation.Keep, + CompareFunction = CompareFunction.Always }, BackFace = new() { - Fail = StencilOperation.Keep, - DepthFail = StencilOperation.Keep, - Pass = StencilOperation.Keep, - Compare = CompareFunction.Always + FailOperation = StencilOperation.Keep, + DepthFailOperation = StencilOperation.Keep, + PassOperation = StencilOperation.Keep, + CompareFunction = CompareFunction.Always } }; public static readonly DepthStencilState DefaultInverted = Default with { - DepthCompare = CompareFunction.GreaterEqual + DepthCompareFunction = CompareFunction.GreaterEqual }; public static readonly DepthStencilState DepthRead = Default with { - DepthWriteEnable = false + IsDepthWriteEnabled = false }; public static readonly DepthStencilState None = Default with { - DepthEnable = false, - DepthWriteEnable = false + IsDepthEnabled = false, + IsDepthWriteEnabled = false }; } diff --git a/sources/Zenith.NET/Enums/BlendFactor.cs b/sources/Zenith.NET/Enums/BlendFactor.cs index 60bf9244..dc293852 100644 --- a/sources/Zenith.NET/Enums/BlendFactor.cs +++ b/sources/Zenith.NET/Enums/BlendFactor.cs @@ -6,21 +6,21 @@ public enum BlendFactor One, - SrcColor, + SourceColor, - OneMinusSrcColor, + OneMinusSourceColor, - DstColor, + DestinationColor, - OneMinusDstColor, + OneMinusDestinationColor, - SrcAlpha, + SourceAlpha, - OneMinusSrcAlpha, + OneMinusSourceAlpha, - DstAlpha, + DestinationAlpha, - OneMinusDstAlpha, + OneMinusDestinationAlpha, Constant, diff --git a/sources/Zenith.NET/Enums/RayTracingGeometryType.cs b/sources/Zenith.NET/Enums/RayTracingGeometryType.cs index 454a9115..c6f51d21 100644 --- a/sources/Zenith.NET/Enums/RayTracingGeometryType.cs +++ b/sources/Zenith.NET/Enums/RayTracingGeometryType.cs @@ -2,7 +2,7 @@ public enum RayTracingGeometryType { - Triangles, + Triangle, - AABBs + Aabb } diff --git a/sources/Zenith.NET/Enums/TextureState.cs b/sources/Zenith.NET/Enums/TextureState.cs index 5ff0db67..56b5eba7 100644 --- a/sources/Zenith.NET/Enums/TextureState.cs +++ b/sources/Zenith.NET/Enums/TextureState.cs @@ -2,7 +2,7 @@ public enum TextureState { - RenderTarget, + ColorAttachment, DepthStencil, diff --git a/sources/Zenith.NET/Enums/TextureUsages.cs b/sources/Zenith.NET/Enums/TextureUsages.cs index b3831cbf..f50e0a28 100644 --- a/sources/Zenith.NET/Enums/TextureUsages.cs +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -5,7 +5,7 @@ public enum TextureUsages { None = 0, - RenderTarget = 1 << 0, + ColorAttachment = 1 << 0, DepthStencil = 1 << 1, diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 5313d0d2..918d58b1 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -8,15 +8,15 @@ protected GraphicsContext(GraphicsApi api, bool useValidationLayer) Initialize(useValidationLayer, out Capabilities capabilities, - out CommandQueue graphics, - out CommandQueue compute, - out CommandQueue copy, + out CommandQueue graphicsQueue, + out CommandQueue computeQueue, + out CommandQueue copyQueue, out ValidationLayer? validationLayer); Capabilities = capabilities; - Graphics = graphics; - Compute = compute; - Copy = copy; + GraphicsQueue = graphicsQueue; + ComputeQueue = computeQueue; + CopyQueue = copyQueue; ValidationLayer = validationLayer; Uploader = new(this); @@ -27,11 +27,11 @@ protected GraphicsContext(GraphicsApi api, bool useValidationLayer) public Capabilities Capabilities { get; } - public CommandQueue Graphics { get; } + public CommandQueue GraphicsQueue { get; } - public CommandQueue Compute { get; } + public CommandQueue ComputeQueue { get; } - public CommandQueue Copy { get; } + public CommandQueue CopyQueue { get; } internal ValidationLayer? ValidationLayer { get; } @@ -39,7 +39,7 @@ protected GraphicsContext(GraphicsApi api, bool useValidationLayer) internal Downloader Downloader { get; } - public event EventHandler? ValidationMessage; + public event EventHandler? ValidationMessage; public SwapChain CreateSwapChain(SwapChainDesc desc) { @@ -122,9 +122,9 @@ public QueryHeap CreateQueryHeap(QueryHeapDesc desc) protected override void Destroy() { - Graphics.Dispose(); - Compute.Dispose(); - Copy.Dispose(); + GraphicsQueue.Dispose(); + ComputeQueue.Dispose(); + CopyQueue.Dispose(); ValidationLayer?.Dispose(); Uploader.Dispose(); @@ -133,9 +133,9 @@ protected override void Destroy() protected abstract void Initialize(bool useValidationLayer, out Capabilities capabilities, - out CommandQueue graphics, - out CommandQueue compute, - out CommandQueue copy, + out CommandQueue graphicsQueue, + out CommandQueue computeQueue, + out CommandQueue copyQueue, out ValidationLayer? validationLayer); protected abstract SwapChain CreateSwapChainImpl(SwapChainDesc desc); @@ -160,7 +160,7 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc); - internal void OnValidationMessage(ValidationMessageArgs args) + internal void OnValidationMessage(ValidationMessageEventArgs args) { ValidationMessage?.Invoke(this, args); } diff --git a/sources/Zenith.NET/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs index b3772e21..00cf959d 100644 --- a/sources/Zenith.NET/RasterizerStates.cs +++ b/sources/Zenith.NET/RasterizerStates.cs @@ -10,8 +10,8 @@ public static class RasterizerStates DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - DepthClipEnable = true, - ScissorEnable = false + IsDepthClipEnabled = true, + IsScissorEnabled = false }; public static readonly RasterizerState CullFront = Default with diff --git a/sources/Zenith.NET/Structs/AttachmentFormats.cs b/sources/Zenith.NET/Structs/AttachmentFormats.cs index c00301b9..69b53784 100644 --- a/sources/Zenith.NET/Structs/AttachmentFormats.cs +++ b/sources/Zenith.NET/Structs/AttachmentFormats.cs @@ -2,9 +2,9 @@ public record struct AttachmentFormats { - public PixelFormat[] Colors; + public PixelFormat[] ColorFormats; - public PixelFormat? DepthStencil; + public PixelFormat? DepthStencilFormat; public SampleCount SampleCount; } diff --git a/sources/Zenith.NET/Structs/BlendState.cs b/sources/Zenith.NET/Structs/BlendState.cs index e4b619bc..3bee595c 100644 --- a/sources/Zenith.NET/Structs/BlendState.cs +++ b/sources/Zenith.NET/Structs/BlendState.cs @@ -2,23 +2,23 @@ public record struct BlendState { - public bool AlphaToCoverageEnable; + public bool IsAlphaToCoverageEnabled; - public bool IndependentBlendEnable; + public bool IsIndependentBlendEnabled; - public BlendStateRenderTarget RenderTarget0; + public ColorAttachmentBlendState ColorAttachment0; - public BlendStateRenderTarget RenderTarget1; + public ColorAttachmentBlendState ColorAttachment1; - public BlendStateRenderTarget RenderTarget2; + public ColorAttachmentBlendState ColorAttachment2; - public BlendStateRenderTarget RenderTarget3; + public ColorAttachmentBlendState ColorAttachment3; - public BlendStateRenderTarget RenderTarget4; + public ColorAttachmentBlendState ColorAttachment4; - public BlendStateRenderTarget RenderTarget5; + public ColorAttachmentBlendState ColorAttachment5; - public BlendStateRenderTarget RenderTarget6; + public ColorAttachmentBlendState ColorAttachment6; - public BlendStateRenderTarget RenderTarget7; + public ColorAttachmentBlendState ColorAttachment7; } diff --git a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs deleted file mode 100644 index 4187a097..00000000 --- a/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Zenith.NET; - -public record struct BlendStateRenderTarget -{ - public bool BlendEnable; - - public BlendFactor SrcFactor; - - public BlendFactor DstFactor; - - public BlendOperation Operation; - - public BlendFactor SrcFactorAlpha; - - public BlendFactor DstFactorAlpha; - - public BlendOperation OperationAlpha; - - public ColorWrites Writes; -} diff --git a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs index b6e38f58..8eb03634 100644 --- a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs +++ b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs @@ -4,5 +4,5 @@ public record struct BottomLevelAccelerationStructureDesc { public RayTracingGeometry[] Geometries; - public AccelerationStructureBuildFlags Flags; + public AccelerationStructureBuildFlags BuildFlags; } diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 864f821c..314f0170 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -12,9 +12,9 @@ public record struct ColorAttachment public TextureSubresource ResolveSubresource; - public LoadOperation Load; + public LoadOperation LoadOperation; - public StoreOperation Store; + public StoreOperation StoreOperation; public Vector4 ClearColor; } diff --git a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs new file mode 100644 index 00000000..7ac1bdff --- /dev/null +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -0,0 +1,20 @@ +namespace Zenith.NET; + +public record struct ColorAttachmentBlendState +{ + public bool IsBlendingEnabled; + + public BlendFactor SourceRgbBlendFactor; + + public BlendFactor DestinationRgbBlendFactor; + + public BlendOperation RgbBlendOperation; + + public BlendFactor SourceAlphaBlendFactor; + + public BlendFactor DestinationAlphaBlendFactor; + + public BlendOperation AlphaBlendOperation; + + public ColorWrites ColorWrites; +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index 35c80440..1070a781 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -2,7 +2,7 @@ public record struct ComputePipelineDesc { - public Shader Compute; + public Shader ComputeShader; - public ResourceBinding[] Bindings; + public ResourceLayout[] ResourceLayouts; } diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs index 5e702961..e050ca08 100644 --- a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -6,13 +6,13 @@ public record struct DepthStencilAttachment public TextureSubresource Subresource; - public LoadOperation DepthLoad; + public LoadOperation DepthLoadOperation; - public StoreOperation DepthStore; + public StoreOperation DepthStoreOperation; - public LoadOperation StencilLoad; + public LoadOperation StencilLoadOperation; - public StoreOperation StencilStore; + public StoreOperation StencilStoreOperation; public float ClearDepth; diff --git a/sources/Zenith.NET/Structs/DepthStencilState.cs b/sources/Zenith.NET/Structs/DepthStencilState.cs index d569fce7..00d5a307 100644 --- a/sources/Zenith.NET/Structs/DepthStencilState.cs +++ b/sources/Zenith.NET/Structs/DepthStencilState.cs @@ -2,13 +2,13 @@ public record struct DepthStencilState { - public bool DepthEnable; + public bool IsDepthEnabled; - public bool DepthWriteEnable; + public bool IsDepthWriteEnabled; - public CompareFunction DepthCompare; + public CompareFunction DepthCompareFunction; - public bool StencilEnable; + public bool IsStencilEnabled; public byte StencilReadMask; diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 17005e09..34a5baf0 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -2,17 +2,17 @@ public record struct GraphicsPipelineDesc { - public RenderStates RenderStates; + public RenderState RenderState; - public Shader Vertex; + public Shader VertexShader; - public Shader Fragment; + public Shader FragmentShader; - public ResourceBinding[] Bindings; + public ResourceLayout[] ResourceLayouts; - public InputLayout[] Layouts; + public InputLayout[] InputLayouts; public PrimitiveTopology PrimitiveTopology; - public AttachmentFormats Formats; + public AttachmentFormats AttachmentFormats; } diff --git a/sources/Zenith.NET/Structs/InputElement.cs b/sources/Zenith.NET/Structs/InputElement.cs index ccb880c8..30ac9d76 100644 --- a/sources/Zenith.NET/Structs/InputElement.cs +++ b/sources/Zenith.NET/Structs/InputElement.cs @@ -6,7 +6,7 @@ public record struct InputElement public ElementSemantic Semantic; - public uint Index; + public uint SemanticIndex; public uint OffsetInBytes; } diff --git a/sources/Zenith.NET/Structs/InputLayout.cs b/sources/Zenith.NET/Structs/InputLayout.cs index 9cfbb364..b8d20e43 100644 --- a/sources/Zenith.NET/Structs/InputLayout.cs +++ b/sources/Zenith.NET/Structs/InputLayout.cs @@ -2,7 +2,7 @@ public record struct InputLayout { - public InputElement[] Elements; + public InputElement[] InputElements; public uint StrideInBytes; @@ -10,15 +10,15 @@ public void Add(InputElement element) { element.OffsetInBytes = StrideInBytes; - if (Elements is null) + if (InputElements is null) { - Elements = [element]; + InputElements = [element]; } else { - Array.Resize(ref Elements, Elements.Length + 1); + Array.Resize(ref InputElements, InputElements.Length + 1); - Elements[^1] = element; + InputElements[^1] = element; } StrideInBytes += ZenithHelper.SizeInBytes(element.Format); diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index f44ba6f1..fec7acc9 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -2,17 +2,17 @@ public record struct MeshShadingPipelineDesc { - public RenderStates RenderStates; + public RenderState RenderState; - public Shader? Task; + public Shader? TaskShader; - public Shader Mesh; + public Shader MeshShader; - public Shader Fragment; + public Shader FragmentShader; - public ResourceBinding[] Bindings; + public ResourceLayout[] ResourceLayouts; public PrimitiveTopology PrimitiveTopology; - public AttachmentFormats Formats; + public AttachmentFormats AttachmentFormats; } diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index 5b3a6644..c9f087c6 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -14,7 +14,7 @@ public record struct RasterizerState public float DepthBiasSlopeScale; - public bool DepthClipEnable; + public bool IsDepthClipEnabled; - public bool ScissorEnable; + public bool IsScissorEnabled; } diff --git a/sources/Zenith.NET/Structs/RayTracingAABBs.cs b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs similarity index 63% rename from sources/Zenith.NET/Structs/RayTracingAABBs.cs rename to sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs index b8470a79..e29f9246 100644 --- a/sources/Zenith.NET/Structs/RayTracingAABBs.cs +++ b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs @@ -1,6 +1,6 @@ -namespace Zenith.NET; +namespace Zenith.NET; -public record struct RayTracingAABBs +public record struct RayTracingAabbGeometry { public Buffer Buffer; @@ -9,4 +9,4 @@ public record struct RayTracingAABBs public uint StrideInBytes; public uint OffsetInBytes; -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Zenith.NET/Structs/RayTracingGeometry.cs index 660506bb..17cb4726 100644 --- a/sources/Zenith.NET/Structs/RayTracingGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingGeometry.cs @@ -4,9 +4,9 @@ public record struct RayTracingGeometry { public RayTracingGeometryType Type; - public RayTracingTriangles Triangles; + public RayTracingTriangleGeometry TriangleGeometry; - public RayTracingAABBs AABBs; + public RayTracingAabbGeometry AabbGeometry; - public bool Opaque; + public bool IsOpaque; } diff --git a/sources/Zenith.NET/Structs/RayTracingInstance.cs b/sources/Zenith.NET/Structs/RayTracingInstance.cs index 9e294c32..27405161 100644 --- a/sources/Zenith.NET/Structs/RayTracingInstance.cs +++ b/sources/Zenith.NET/Structs/RayTracingInstance.cs @@ -6,7 +6,7 @@ public record struct RayTracingInstance { public BottomLevelAccelerationStructure AccelerationStructure; - public uint Id; + public uint InstanceId; public byte VisibilityMask; diff --git a/sources/Zenith.NET/Structs/RayTracingTriangles.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs similarity index 83% rename from sources/Zenith.NET/Structs/RayTracingTriangles.cs rename to sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index 217dd673..949b4d6d 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangles.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -1,8 +1,8 @@ -using System.Numerics; +using System.Numerics; namespace Zenith.NET; -public record struct RayTracingTriangles +public record struct RayTracingTriangleGeometry { public Buffer VertexBuffer; @@ -23,4 +23,4 @@ public record struct RayTracingTriangles public uint IndexOffsetInBytes; public Matrix4x4 Transform; -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/RenderStates.cs b/sources/Zenith.NET/Structs/RenderState.cs similarity index 78% rename from sources/Zenith.NET/Structs/RenderStates.cs rename to sources/Zenith.NET/Structs/RenderState.cs index 5feda2ef..c24a3bbc 100644 --- a/sources/Zenith.NET/Structs/RenderStates.cs +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -1,8 +1,8 @@ -using System.Numerics; +using System.Numerics; namespace Zenith.NET; -public record struct RenderStates +public record struct RenderState { public RasterizerState RasterizerState; @@ -13,4 +13,4 @@ public record struct RenderStates public uint StencilReference; public Vector4? BlendConstant; -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/ResourceBinding.cs b/sources/Zenith.NET/Structs/ResourceBinding.cs deleted file mode 100644 index 71be2607..00000000 --- a/sources/Zenith.NET/Structs/ResourceBinding.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public record struct ResourceBinding -{ - public ResourceType Type; - - public uint Count; -} diff --git a/sources/Zenith.NET/Structs/ResourceLayout.cs b/sources/Zenith.NET/Structs/ResourceLayout.cs new file mode 100644 index 00000000..026c748b --- /dev/null +++ b/sources/Zenith.NET/Structs/ResourceLayout.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public record struct ResourceLayout +{ + public ResourceType Type; + + public uint Count; +} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/ResourceTableDesc.cs b/sources/Zenith.NET/Structs/ResourceTableDesc.cs index d63357c6..3328dae7 100644 --- a/sources/Zenith.NET/Structs/ResourceTableDesc.cs +++ b/sources/Zenith.NET/Structs/ResourceTableDesc.cs @@ -2,5 +2,5 @@ public record struct ResourceTableDesc { - public ResourceBinding[] Bindings; + public ResourceLayout[] ResourceLayouts; } diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 6245f77a..1a207de8 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -14,7 +14,7 @@ public record struct SamplerDesc public AddressMode AddressW; - public CompareFunction Compare; + public CompareFunction CompareFunction; public uint MaxAnisotropy; diff --git a/sources/Zenith.NET/Structs/StencilFaceState.cs b/sources/Zenith.NET/Structs/StencilFaceState.cs index 6e31fc56..4fd16e01 100644 --- a/sources/Zenith.NET/Structs/StencilFaceState.cs +++ b/sources/Zenith.NET/Structs/StencilFaceState.cs @@ -2,11 +2,11 @@ public record struct StencilFaceState { - public StencilOperation Fail; + public StencilOperation FailOperation; - public StencilOperation DepthFail; + public StencilOperation DepthFailOperation; - public StencilOperation Pass; + public StencilOperation PassOperation; - public CompareFunction Compare; + public CompareFunction CompareFunction; } diff --git a/sources/Zenith.NET/Structs/Surface.cs b/sources/Zenith.NET/Structs/Surface.cs index c1d6fc00..399c5f58 100644 --- a/sources/Zenith.NET/Structs/Surface.cs +++ b/sources/Zenith.NET/Structs/Surface.cs @@ -4,7 +4,7 @@ public record struct Surface { public SurfaceType Type; - public nint[] Handles; + public nint[] NativeHandles; public uint Width; @@ -15,7 +15,7 @@ public static Surface Win32(nint hwnd, uint width, uint height) return new() { Type = SurfaceType.Win32, - Handles = [hwnd], + NativeHandles = [hwnd], Width = width, Height = height }; @@ -26,7 +26,7 @@ public static Surface Wayland(nint display, nint surface, uint width, uint heigh return new() { Type = SurfaceType.Wayland, - Handles = [display, surface], + NativeHandles = [display, surface], Width = width, Height = height }; @@ -37,7 +37,7 @@ public static Surface Xlib(nint display, nint window, uint width, uint height) return new() { Type = SurfaceType.Xlib, - Handles = [display, window], + NativeHandles = [display, window], Width = width, Height = height }; @@ -48,7 +48,7 @@ public static Surface Android(nint nativeWindow, uint width, uint height) return new() { Type = SurfaceType.Android, - Handles = [nativeWindow], + NativeHandles = [nativeWindow], Width = width, Height = height }; @@ -59,7 +59,7 @@ public static Surface Apple(nint layer, uint width, uint height) return new() { Type = SurfaceType.Apple, - Handles = [layer], + NativeHandles = [layer], Width = width, Height = height }; @@ -70,7 +70,7 @@ public static Surface D3D11Interop(nint sharedHandle, uint width, uint height) return new() { Type = SurfaceType.D3D11Interop, - Handles = [sharedHandle], + NativeHandles = [sharedHandle], Width = width, Height = height }; diff --git a/sources/Zenith.NET/Structs/TextureDataLayout.cs b/sources/Zenith.NET/Structs/TextureDataLayout.cs index 6bf72b7f..e3ecb5c1 100644 --- a/sources/Zenith.NET/Structs/TextureDataLayout.cs +++ b/sources/Zenith.NET/Structs/TextureDataLayout.cs @@ -4,7 +4,7 @@ public record struct TextureDataLayout { public uint SizeInBytes; - public uint RowPitchInBytes; + public uint RowStrideInBytes; - public uint SlicePitchInBytes; + public uint SliceStrideInBytes; } \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs index 00e54715..883f6fe5 100644 --- a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs +++ b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs @@ -4,5 +4,5 @@ public record struct TopLevelAccelerationStructureDesc { public RayTracingInstance[] Instances; - public AccelerationStructureBuildFlags Flags; + public AccelerationStructureBuildFlags BuildFlags; } diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 8f96c261..34cc8269 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -6,9 +6,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public ref readonly SwapChainDesc Desc => ref desc; - public abstract Texture CurrentColorTarget { get; } + public abstract Texture CurrentColorAttachment { get; } - public abstract Texture? CurrentDepthStencilTarget { get; } + public abstract Texture? CurrentDepthStencilAttachment { get; } public abstract CommandSubmission Acquire(); diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index 96ccdb74..6ee142b6 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -8,7 +8,7 @@ public abstract class Texture(GraphicsContext context, TextureDesc desc) : Graph public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); commandBuffer.Upload(this, subresource, offset, extent, data); commandBuffer.Submit().Wait(); @@ -16,7 +16,7 @@ public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D ext public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); commandBuffer.Download(this, subresource, offset, extent, data); commandBuffer.Submit().Wait(); diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 353d47d5..76aef340 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -41,6 +41,7 @@ internal void ValidateDesc(BufferDesc desc) } CheckEnum("BufferDesc.Access", desc.Access); + CheckFlags("BufferDesc.Usages", desc.Usages); if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) { @@ -86,6 +87,7 @@ internal void ValidateDesc(TextureDesc desc) } CheckEnum("TextureDesc.SampleCount", desc.SampleCount); + CheckFlags("TextureDesc.Usages", desc.Usages); if (desc.Usages is TextureUsages.None) { @@ -158,7 +160,7 @@ internal void ValidateDesc(SamplerDesc desc) CheckEnum("SamplerDesc.AddressU", desc.AddressU); CheckEnum("SamplerDesc.AddressV", desc.AddressV); CheckEnum("SamplerDesc.AddressW", desc.AddressW); - CheckEnum("SamplerDesc.Compare", desc.Compare); + CheckEnum("SamplerDesc.CompareFunction", desc.CompareFunction); if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) { @@ -175,14 +177,14 @@ internal void ValidateDesc(SamplerDesc desc) internal void ValidateDesc(ResourceTableDesc desc) { - if (!CheckArrayNotEmpty("ResourceTableDesc.Bindings", desc.Bindings)) + if (!CheckArrayNotEmpty("ResourceTableDesc.ResourceLayouts", desc.ResourceLayouts)) { return; } - for (int i = 0; i < desc.Bindings.Length; i++) + for (int i = 0; i < desc.ResourceLayouts.Length; i++) { - CheckResourceBinding($"ResourceTableDesc.Bindings[{i}]", desc.Bindings[i]); + CheckResourceLayout($"ResourceTableDesc.ResourceLayouts[{i}]", desc.ResourceLayouts[i]); } } @@ -195,56 +197,56 @@ internal void ValidateDesc(ShaderDesc desc) internal void ValidateDesc(GraphicsPipelineDesc desc) { - CheckRenderStates("GraphicsPipelineDesc.RenderStates", desc.RenderStates); + CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); - CheckResource("GraphicsPipelineDesc.Vertex", desc.Vertex); - CheckResource("GraphicsPipelineDesc.Fragment", desc.Fragment); + CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); + CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); - CheckResourceBindings("GraphicsPipelineDesc.Bindings", desc.Bindings); + CheckResourceLayouts("GraphicsPipelineDesc.ResourceLayouts", desc.ResourceLayouts); - if (desc.Layouts is null) + if (desc.InputLayouts is null) { - ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.Layouts")); + ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); } else { - for (int i = 0; i < desc.Layouts.Length; i++) + for (int i = 0; i < desc.InputLayouts.Length; i++) { - CheckInputLayout($"GraphicsPipelineDesc.Layouts[{i}]", desc.Layouts[i]); + CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{i}]", desc.InputLayouts[i]); } } CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); - CheckAttachmentFormats("GraphicsPipelineDesc.Formats", desc.Formats); + CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); } internal void ValidateDesc(ComputePipelineDesc desc) { - CheckResource("ComputePipelineDesc.Compute", desc.Compute); + CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); - CheckResourceBindings("ComputePipelineDesc.Bindings", desc.Bindings); + CheckResourceLayouts("ComputePipelineDesc.ResourceLayouts", desc.ResourceLayouts); } internal void ValidateDesc(MeshShadingPipelineDesc desc) { - CheckRenderStates("MeshShadingPipelineDesc.RenderStates", desc.RenderStates); + CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); - if (desc.Task is not null) + if (desc.TaskShader is not null) { - CheckResource("MeshShadingPipelineDesc.Task", desc.Task); + CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); } - CheckResource("MeshShadingPipelineDesc.Mesh", desc.Mesh); - CheckResource("MeshShadingPipelineDesc.Fragment", desc.Fragment); + CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); + CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); - CheckResourceBindings("MeshShadingPipelineDesc.Bindings", desc.Bindings); + CheckResourceLayouts("MeshShadingPipelineDesc.ResourceLayouts", desc.ResourceLayouts); if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) { ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); } - CheckAttachmentFormats("MeshShadingPipelineDesc.Formats", desc.Formats); + CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); } internal void ValidateDesc(QueryHeapDesc desc) @@ -255,35 +257,35 @@ internal void ValidateDesc(QueryHeapDesc desc) internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) { - if (!CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) + if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) { - return; + for (int i = 0; i < desc.Geometries.Length; i++) + { + CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{i}]", desc.Geometries[i]); + } } - for (int i = 0; i < desc.Geometries.Length; i++) - { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{i}]", desc.Geometries[i]); - } + CheckFlags("BottomLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); } internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) { - if (!CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) + if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) { - return; + for (int i = 0; i < desc.Instances.Length; i++) + { + CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{i}]", desc.Instances[i]); + } } - for (int i = 0; i < desc.Instances.Length; i++) - { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{i}]", desc.Instances[i]); - } + CheckFlags("TopLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); } internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) { - if (!oldDesc.Flags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + if (!oldDesc.BuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.Flags", AccelerationStructureBuildFlags.AllowUpdate)); + ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.BuildFlags", AccelerationStructureBuildFlags.AllowUpdate)); } ValidateDesc(newDesc); @@ -354,6 +356,26 @@ private bool CheckEnum(string name, T value) where T : struct, Enum return true; } + private bool CheckFlags(string name, T value) where T : struct, Enum + { + ulong valueBits = unchecked((ulong)Convert.ToInt64(value)); + ulong validBits = 0; + + foreach (T definedValue in Enum.GetValues()) + { + validBits |= unchecked((ulong)Convert.ToInt64(definedValue)); + } + + if ((valueBits & ~validBits) is not 0) + { + ReportError(string.Format(ValidationMessages.HasInvalidValue, name, value)); + + return false; + } + + return true; + } + private bool CheckGreaterThanZero(string name, T value) where T : struct, INumber { if (value <= T.Zero) @@ -368,10 +390,8 @@ private bool CheckGreaterThanZero(string name, T value) where T : struct, INu private void CheckSurface(string name, Surface surface) { - if (surface.Handles is null) + if (!CheckEnum($"{name}.Type", surface.Type)) { - ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.Handles")); - return; } @@ -382,71 +402,86 @@ private void CheckSurface(string name, Surface surface) return; } - if (surface.Handles.Length != expected) + if (surface.NativeHandles is null) { - ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.Handles", expected, surface.Type)); + ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.NativeHandles")); return; } - for (int i = 0; i < surface.Handles.Length; i++) + if (surface.NativeHandles.Length != expected) { - if (surface.Handles[i] is 0) + ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.NativeHandles", expected, surface.Type)); + + return; + } + + for (int i = 0; i < surface.NativeHandles.Length; i++) + { + if (surface.NativeHandles[i] is 0) { if (expected is 1) { - ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.Handles[0]", surface.Type)); + ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.NativeHandles[0]", surface.Type)); } else { - ReportError(string.Format(ValidationMessages.MustBeValidHandles, $"{name}.Handles", surface.Type)); + ReportError(string.Format(ValidationMessages.MustBeValidHandles, $"{name}.NativeHandles", surface.Type)); } return; } } + + CheckGreaterThanZero($"{name}.Width", surface.Width); + CheckGreaterThanZero($"{name}.Height", surface.Height); } - private void CheckResourceBindings(string name, ResourceBinding[]? bindings) + private void CheckResourceLayouts(string name, ResourceLayout[]? resourceLayouts) { - if (bindings is null) + if (resourceLayouts is null) { ReportError(string.Format(ValidationMessages.MustNotBeNull, name)); return; } - for (int i = 0; i < bindings.Length; i++) + for (int i = 0; i < resourceLayouts.Length; i++) { - CheckResourceBinding($"{name}[{i}]", bindings[i]); + CheckResourceLayout($"{name}[{i}]", resourceLayouts[i]); } } - private void CheckResourceBinding(string name, ResourceBinding binding) + private void CheckResourceLayout(string name, ResourceLayout resourceLayout) { - CheckEnum($"{name}.Type", binding.Type); - CheckGreaterThanZero($"{name}.Count", binding.Count); + CheckEnum($"{name}.Type", resourceLayout.Type); + CheckGreaterThanZero($"{name}.Count", resourceLayout.Count); } private void CheckInputLayout(string name, InputLayout inputLayout) { - if (!CheckArrayNotEmpty($"{name}.Elements", inputLayout.Elements)) + if (CheckArrayNotEmpty($"{name}.InputElements", inputLayout.InputElements)) { - return; + for (int i = 0; i < inputLayout.InputElements.Length; i++) + { + CheckInputElement($"{name}.InputElements[{i}]", inputLayout.InputElements[i]); + } } - foreach (InputElement element in inputLayout.Elements) - { - CheckEnum($"{name}.Elements.Format", element.Format); - CheckEnum($"{name}.Elements.Semantic", element.Semantic); - } + CheckGreaterThanZero($"{name}.StrideInBytes", inputLayout.StrideInBytes); } - private void CheckRenderStates(string name, RenderStates renderStates) + private void CheckInputElement(string name, InputElement inputElement) { - CheckRasterizerState($"{name}.RasterizerState", renderStates.RasterizerState); - CheckDepthStencilState($"{name}.DepthStencilState", renderStates.DepthStencilState); - CheckBlendState($"{name}.BlendState", renderStates.BlendState); + CheckEnum($"{name}.Format", inputElement.Format); + CheckEnum($"{name}.Semantic", inputElement.Semantic); + } + + private void CheckRenderState(string name, RenderState renderState) + { + CheckRasterizerState($"{name}.RasterizerState", renderState.RasterizerState); + CheckDepthStencilState($"{name}.DepthStencilState", renderState.DepthStencilState); + CheckBlendState($"{name}.BlendState", renderState.BlendState); } private void CheckRasterizerState(string name, RasterizerState rasterizerState) @@ -458,63 +493,64 @@ private void CheckRasterizerState(string name, RasterizerState rasterizerState) private void CheckDepthStencilState(string name, DepthStencilState depthStencilState) { - CheckEnum($"{name}.DepthCompare", depthStencilState.DepthCompare); + CheckEnum($"{name}.DepthCompareFunction", depthStencilState.DepthCompareFunction); CheckStencilFaceState($"{name}.FrontFace", depthStencilState.FrontFace); CheckStencilFaceState($"{name}.BackFace", depthStencilState.BackFace); } private void CheckBlendState(string name, BlendState blendState) { - CheckBlendStateRenderTarget($"{name}.RenderTarget0", blendState.RenderTarget0); - CheckBlendStateRenderTarget($"{name}.RenderTarget1", blendState.RenderTarget1); - CheckBlendStateRenderTarget($"{name}.RenderTarget2", blendState.RenderTarget2); - CheckBlendStateRenderTarget($"{name}.RenderTarget3", blendState.RenderTarget3); - CheckBlendStateRenderTarget($"{name}.RenderTarget4", blendState.RenderTarget4); - CheckBlendStateRenderTarget($"{name}.RenderTarget5", blendState.RenderTarget5); - CheckBlendStateRenderTarget($"{name}.RenderTarget6", blendState.RenderTarget6); - CheckBlendStateRenderTarget($"{name}.RenderTarget7", blendState.RenderTarget7); + CheckColorAttachmentBlendState($"{name}.ColorAttachment0", blendState.ColorAttachment0); + CheckColorAttachmentBlendState($"{name}.ColorAttachment1", blendState.ColorAttachment1); + CheckColorAttachmentBlendState($"{name}.ColorAttachment2", blendState.ColorAttachment2); + CheckColorAttachmentBlendState($"{name}.ColorAttachment3", blendState.ColorAttachment3); + CheckColorAttachmentBlendState($"{name}.ColorAttachment4", blendState.ColorAttachment4); + CheckColorAttachmentBlendState($"{name}.ColorAttachment5", blendState.ColorAttachment5); + CheckColorAttachmentBlendState($"{name}.ColorAttachment6", blendState.ColorAttachment6); + CheckColorAttachmentBlendState($"{name}.ColorAttachment7", blendState.ColorAttachment7); } private void CheckStencilFaceState(string name, StencilFaceState face) { - CheckEnum($"{name}.Fail", face.Fail); - CheckEnum($"{name}.DepthFail", face.DepthFail); - CheckEnum($"{name}.Pass", face.Pass); - CheckEnum($"{name}.Compare", face.Compare); + CheckEnum($"{name}.FailOperation", face.FailOperation); + CheckEnum($"{name}.DepthFailOperation", face.DepthFailOperation); + CheckEnum($"{name}.PassOperation", face.PassOperation); + CheckEnum($"{name}.CompareFunction", face.CompareFunction); } - private void CheckBlendStateRenderTarget(string name, BlendStateRenderTarget renderTarget) + private void CheckColorAttachmentBlendState(string name, ColorAttachmentBlendState colorAttachment) { - CheckEnum($"{name}.SrcFactor", renderTarget.SrcFactor); - CheckEnum($"{name}.DstFactor", renderTarget.DstFactor); - CheckEnum($"{name}.Operation", renderTarget.Operation); - CheckEnum($"{name}.SrcFactorAlpha", renderTarget.SrcFactorAlpha); - CheckEnum($"{name}.DstFactorAlpha", renderTarget.DstFactorAlpha); - CheckEnum($"{name}.OperationAlpha", renderTarget.OperationAlpha); + CheckEnum($"{name}.SourceRgbBlendFactor", colorAttachment.SourceRgbBlendFactor); + CheckEnum($"{name}.DestinationRgbBlendFactor", colorAttachment.DestinationRgbBlendFactor); + CheckEnum($"{name}.RgbBlendOperation", colorAttachment.RgbBlendOperation); + CheckEnum($"{name}.SourceAlphaBlendFactor", colorAttachment.SourceAlphaBlendFactor); + CheckEnum($"{name}.DestinationAlphaBlendFactor", colorAttachment.DestinationAlphaBlendFactor); + CheckEnum($"{name}.AlphaBlendOperation", colorAttachment.AlphaBlendOperation); + CheckFlags($"{name}.ColorWrites", colorAttachment.ColorWrites); } - private void CheckAttachmentFormats(string name, AttachmentFormats layout) + private void CheckAttachmentFormats(string name, AttachmentFormats attachmentFormats) { - if (layout.Colors is null) + if (attachmentFormats.ColorFormats is null) { - ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorAttachments")); + ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorFormats")); return; } - for (int i = 0; i < layout.Colors.Length; i++) + for (int i = 0; i < attachmentFormats.ColorFormats.Length; i++) { - CheckEnum($"{name}.ColorAttachments[{i}]", layout.Colors[i]); + CheckEnum($"{name}.ColorFormats[{i}]", attachmentFormats.ColorFormats[i]); } - if (layout.DepthStencil is { } format) + if (attachmentFormats.DepthStencilFormat is { } format) { - CheckEnum($"{name}.DepthStencilAttachment", format); + CheckEnum($"{name}.DepthStencilFormat", format); } - CheckEnum($"{name}.SampleCount", layout.SampleCount); + CheckEnum($"{name}.SampleCount", attachmentFormats.SampleCount); - if (layout.Colors.Length is 0 && layout.DepthStencil is null) + if (attachmentFormats.ColorFormats.Length is 0 && attachmentFormats.DepthStencilFormat is null) { ReportWarning(string.Format(ValidationMessages.HasNoAttachments, name)); } @@ -522,91 +558,81 @@ private void CheckAttachmentFormats(string name, AttachmentFormats layout) private void CheckRayTracingGeometry(string name, RayTracingGeometry geometry) { - switch (geometry.Type) + if (!CheckEnum($"{name}.Type", geometry.Type)) { - case RayTracingGeometryType.Triangles: - CheckRayTracingTriangles(name, geometry.Triangles); - break; + return; + } - case RayTracingGeometryType.AABBs: - CheckRayTracingAABBs(name, geometry.AABBs); - break; + if (geometry.Type is RayTracingGeometryType.Triangle) + { + CheckRayTracingTriangleGeometry($"{name}.TriangleGeometry", geometry.TriangleGeometry); + } - default: - ReportError(string.Format(ValidationMessages.HasInvalidValue, $"{name}.Type", geometry.Type)); - break; + if (geometry.Type is RayTracingGeometryType.Aabb) + { + CheckRayTracingAabbGeometry($"{name}.AabbGeometry", geometry.AabbGeometry); } } - private void CheckRayTracingTriangles(string name, RayTracingTriangles triangles) + private void CheckRayTracingTriangleGeometry(string name, RayTracingTriangleGeometry triangleGeometry) { - if (!CheckResource($"{name}.Triangles.VertexBuffer", triangles.VertexBuffer)) - { - return; - } + bool hasVertexBuffer = CheckResource($"{name}.VertexBuffer", triangleGeometry.VertexBuffer); - CheckEnum($"{name}.Triangles.VertexFormat", triangles.VertexFormat); - CheckGreaterThanZero($"{name}.Triangles.VertexCount", triangles.VertexCount); - CheckGreaterThanZero($"{name}.Triangles.VertexStrideInBytes", triangles.VertexStrideInBytes); + CheckEnum($"{name}.VertexFormat", triangleGeometry.VertexFormat); + bool hasVertexCount = CheckGreaterThanZero($"{name}.VertexCount", triangleGeometry.VertexCount); + bool hasVertexStride = CheckGreaterThanZero($"{name}.VertexStrideInBytes", triangleGeometry.VertexStrideInBytes); - if (triangles.VertexOffsetInBytes + (triangles.VertexCount * triangles.VertexStrideInBytes) > triangles.VertexBuffer.Desc.SizeInBytes) + if (hasVertexBuffer && hasVertexCount && hasVertexStride && triangleGeometry.VertexOffsetInBytes + (triangleGeometry.VertexCount * triangleGeometry.VertexStrideInBytes) > triangleGeometry.VertexBuffer.Desc.SizeInBytes) { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Triangles.VertexCount", "the vertex buffer")); + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.VertexCount", "the vertex buffer")); } - if (triangles.IndexBuffer is null) + if (triangleGeometry.IndexBuffer is null) { return; } - if (!CheckResource($"{name}.Triangles.IndexBuffer", triangles.IndexBuffer)) - { - return; - } + bool hasIndexBuffer = CheckResource($"{name}.IndexBuffer", triangleGeometry.IndexBuffer); - CheckEnum($"{name}.Triangles.IndexFormat", triangles.IndexFormat); - CheckGreaterThanZero($"{name}.Triangles.IndexCount", triangles.IndexCount); + CheckEnum($"{name}.IndexFormat", triangleGeometry.IndexFormat); + bool hasIndexCount = CheckGreaterThanZero($"{name}.IndexCount", triangleGeometry.IndexCount); - uint indexSizeInBytes = triangles.IndexFormat switch + uint indexSizeInBytes = triangleGeometry.IndexFormat switch { IndexFormat.UInt16 => ValidationConstants.IndexSizeUInt16, IndexFormat.UInt32 => ValidationConstants.IndexSizeUInt32, _ => 0 }; - if (triangles.IndexOffsetInBytes + (triangles.IndexCount * indexSizeInBytes) > triangles.IndexBuffer.Desc.SizeInBytes) + if (hasIndexBuffer && hasIndexCount && indexSizeInBytes is not 0 && triangleGeometry.IndexOffsetInBytes + (triangleGeometry.IndexCount * indexSizeInBytes) > triangleGeometry.IndexBuffer.Desc.SizeInBytes) { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Triangles.IndexCount", "the index buffer")); + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.IndexCount", "the index buffer")); } } - private void CheckRayTracingAABBs(string name, RayTracingAABBs aabbs) + private void CheckRayTracingAabbGeometry(string name, RayTracingAabbGeometry aabbGeometry) { - if (!CheckResource($"{name}.AABBs.Buffer", aabbs.Buffer)) - { - return; - } + bool hasBuffer = CheckResource($"{name}.Buffer", aabbGeometry.Buffer); - CheckGreaterThanZero($"{name}.AABBs.Count", aabbs.Count); - CheckGreaterThanZero($"{name}.AABBs.StrideInBytes", aabbs.StrideInBytes); + bool hasCount = CheckGreaterThanZero($"{name}.Count", aabbGeometry.Count); + bool hasStride = CheckGreaterThanZero($"{name}.StrideInBytes", aabbGeometry.StrideInBytes); - if (aabbs.OffsetInBytes + (aabbs.Count * aabbs.StrideInBytes) > aabbs.Buffer.Desc.SizeInBytes) + if (hasBuffer && hasCount && hasStride && aabbGeometry.OffsetInBytes + (aabbGeometry.Count * aabbGeometry.StrideInBytes) > aabbGeometry.Buffer.Desc.SizeInBytes) { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.AABBs.Count", "the AABBs buffer")); + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Count", "the Aabb geometry buffer")); } } private void CheckRayTracingInstance(string name, RayTracingInstance instance) { - if (!CheckResource($"{name}.AccelerationStructure", instance.AccelerationStructure)) - { - return; - } + CheckResource($"{name}.AccelerationStructure", instance.AccelerationStructure); - if (instance.Id > ValidationConstants.MaxRayTracingInstanceId) + if (instance.InstanceId > ValidationConstants.MaxRayTracingInstanceId) { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.Id", ValidationConstants.MaxRayTracingInstanceId)); + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.InstanceId", ValidationConstants.MaxRayTracingInstanceId)); } + + CheckFlags($"{name}.Flags", instance.Flags); } private void ReportError(string message) diff --git a/sources/Zenith.NET/ValidationMessageArgs.cs b/sources/Zenith.NET/ValidationMessageEventArgs.cs similarity index 62% rename from sources/Zenith.NET/ValidationMessageArgs.cs rename to sources/Zenith.NET/ValidationMessageEventArgs.cs index c66e872b..060eb03b 100644 --- a/sources/Zenith.NET/ValidationMessageArgs.cs +++ b/sources/Zenith.NET/ValidationMessageEventArgs.cs @@ -1,6 +1,6 @@ -namespace Zenith.NET; +namespace Zenith.NET; -public class ValidationMessageArgs(MessageSource source, MessageSeverity severity, string message) : EventArgs +public class ValidationMessageEventArgs(MessageSource source, MessageSeverity severity, string message) : EventArgs { public MessageSource Source { get; } = source; diff --git a/sources/Zenith.NET/ZenithHelper.cs b/sources/Zenith.NET/ZenithHelper.cs index c6f4d29d..54b87e47 100644 --- a/sources/Zenith.NET/ZenithHelper.cs +++ b/sources/Zenith.NET/ZenithHelper.cs @@ -193,18 +193,18 @@ public static uint SizeInBytes(PixelFormat pixelFormat, uint width, uint height) return blocksWide * blocksHigh * SizeInBytes(pixelFormat); } - public static uint RowPitchInBytes(PixelFormat pixelFormat, uint width, uint height) + public static uint RowStrideInBytes(PixelFormat pixelFormat, uint width, uint height) { (_, _, uint blocksWide, _) = BlockLayout(pixelFormat, width, height); return SizeInBytes(pixelFormat) * blocksWide; } - public static uint SlicePitchInBytes(PixelFormat pixelFormat, uint width, uint height) + public static uint SliceStrideInBytes(PixelFormat pixelFormat, uint width, uint height) { (_, _, _, uint blocksHigh) = BlockLayout(pixelFormat, width, height); - return RowPitchInBytes(pixelFormat, width, height) * blocksHigh; + return RowStrideInBytes(pixelFormat, width, height) * blocksHigh; } public static uint SizeInBytes(ElementFormat elementFormat) From 3069b4f48825b68f2d7e5fd36bb3a08ec9949d73 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 2 May 2026 14:38:45 +0800 Subject: [PATCH 054/466] Refactor: improve variable naming for clarity Renamed local variables across CommandBuffer, ValidationLayer, and ZenithMarshal for better readability and consistency. No functional changes; all updates are for code clarity and maintainability. --- sources/Zenith.NET/CommandBuffer.cs | 20 +++---- sources/Zenith.NET/ValidationLayer.cs | 76 +++++++++++++-------------- sources/Zenith.NET/ZenithMarshal.cs | 10 ++-- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index bebaea3f..45fb8339 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -33,32 +33,32 @@ public void Transition(Texture texture, TextureSubresource subresource, TextureS public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) { - Buffer temporary = Context.Uploader.Buffer(this, data.SizeInBytes); - temporary.Upload(0, data); + Buffer stagingBuffer = Context.Uploader.Buffer(this, data.SizeInBytes); + stagingBuffer.Upload(0, data); - CopyBuffer(temporary, 0, buffer, offsetInBytes, data.SizeInBytes); + CopyBuffer(stagingBuffer, 0, buffer, offsetInBytes, data.SizeInBytes); } public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { - Buffer temporary = Context.Downloader.Buffer(this, data); + Buffer stagingBuffer = Context.Downloader.Buffer(this, data); - CopyBuffer(buffer, offsetInBytes, temporary, 0, data.SizeInBytes); + CopyBuffer(buffer, offsetInBytes, stagingBuffer, 0, data.SizeInBytes); } public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer temporary = Context.Uploader.Buffer(this, data.Layout.SizeInBytes); - temporary.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); + Buffer stagingBuffer = Context.Uploader.Buffer(this, data.Layout.SizeInBytes); + stagingBuffer.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); - CopyBufferToTexture(temporary, 0, data.Layout, texture, subresource, offset, extent); + CopyBufferToTexture(stagingBuffer, 0, data.Layout, texture, subresource, offset, extent); } public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer temporary = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); + Buffer stagingBuffer = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); - CopyTextureToBuffer(texture, subresource, offset, extent, temporary, 0, data.Layout); + CopyTextureToBuffer(texture, subresource, offset, extent, stagingBuffer, 0, data.Layout); } public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 76aef340..5ececb74 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -49,11 +49,11 @@ internal void ValidateDesc(BufferDesc desc) | BufferUsages.Indirect | BufferUsages.AccelerationStructure; - BufferUsages forbidden = desc.Usages & GpuOnlyUsages; + BufferUsages forbiddenUsages = desc.Usages & GpuOnlyUsages; - if (forbidden is not BufferUsages.None) + if (forbiddenUsages is not BufferUsages.None) { - ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbidden, desc.Access)); + ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbiddenUsages, desc.Access)); } } } @@ -182,9 +182,9 @@ internal void ValidateDesc(ResourceTableDesc desc) return; } - for (int i = 0; i < desc.ResourceLayouts.Length; i++) + for (int index = 0; index < desc.ResourceLayouts.Length; index++) { - CheckResourceLayout($"ResourceTableDesc.ResourceLayouts[{i}]", desc.ResourceLayouts[i]); + CheckResourceLayout($"ResourceTableDesc.ResourceLayouts[{index}]", desc.ResourceLayouts[index]); } } @@ -210,9 +210,9 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) } else { - for (int i = 0; i < desc.InputLayouts.Length; i++) + for (int index = 0; index < desc.InputLayouts.Length; index++) { - CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{i}]", desc.InputLayouts[i]); + CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{index}]", desc.InputLayouts[index]); } } @@ -259,9 +259,9 @@ internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) { if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) { - for (int i = 0; i < desc.Geometries.Length; i++) + for (int index = 0; index < desc.Geometries.Length; index++) { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{i}]", desc.Geometries[i]); + CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{index}]", desc.Geometries[index]); } } @@ -272,9 +272,9 @@ internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) { if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) { - for (int i = 0; i < desc.Instances.Length; i++) + for (int index = 0; index < desc.Instances.Length; index++) { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{i}]", desc.Instances[i]); + CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{index}]", desc.Instances[index]); } } @@ -395,7 +395,7 @@ private void CheckSurface(string name, Surface surface) return; } - if (!ExpectedSurfaceHandleCount.TryGetValue(surface.Type, out int expected)) + if (!ExpectedSurfaceHandleCount.TryGetValue(surface.Type, out int expectedHandleCount)) { ReportError(string.Format(ValidationMessages.HasUnsupportedSurfaceType, name, surface.Type)); @@ -409,18 +409,18 @@ private void CheckSurface(string name, Surface surface) return; } - if (surface.NativeHandles.Length != expected) + if (surface.NativeHandles.Length != expectedHandleCount) { - ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.NativeHandles", expected, surface.Type)); + ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.NativeHandles", expectedHandleCount, surface.Type)); return; } - for (int i = 0; i < surface.NativeHandles.Length; i++) + for (int index = 0; index < surface.NativeHandles.Length; index++) { - if (surface.NativeHandles[i] is 0) + if (surface.NativeHandles[index] is 0) { - if (expected is 1) + if (expectedHandleCount is 1) { ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.NativeHandles[0]", surface.Type)); } @@ -446,9 +446,9 @@ private void CheckResourceLayouts(string name, ResourceLayout[]? resourceLayouts return; } - for (int i = 0; i < resourceLayouts.Length; i++) + for (int index = 0; index < resourceLayouts.Length; index++) { - CheckResourceLayout($"{name}[{i}]", resourceLayouts[i]); + CheckResourceLayout($"{name}[{index}]", resourceLayouts[index]); } } @@ -462,9 +462,9 @@ private void CheckInputLayout(string name, InputLayout inputLayout) { if (CheckArrayNotEmpty($"{name}.InputElements", inputLayout.InputElements)) { - for (int i = 0; i < inputLayout.InputElements.Length; i++) + for (int index = 0; index < inputLayout.InputElements.Length; index++) { - CheckInputElement($"{name}.InputElements[{i}]", inputLayout.InputElements[i]); + CheckInputElement($"{name}.InputElements[{index}]", inputLayout.InputElements[index]); } } @@ -510,23 +510,23 @@ private void CheckBlendState(string name, BlendState blendState) CheckColorAttachmentBlendState($"{name}.ColorAttachment7", blendState.ColorAttachment7); } - private void CheckStencilFaceState(string name, StencilFaceState face) + private void CheckStencilFaceState(string name, StencilFaceState faceState) { - CheckEnum($"{name}.FailOperation", face.FailOperation); - CheckEnum($"{name}.DepthFailOperation", face.DepthFailOperation); - CheckEnum($"{name}.PassOperation", face.PassOperation); - CheckEnum($"{name}.CompareFunction", face.CompareFunction); + CheckEnum($"{name}.FailOperation", faceState.FailOperation); + CheckEnum($"{name}.DepthFailOperation", faceState.DepthFailOperation); + CheckEnum($"{name}.PassOperation", faceState.PassOperation); + CheckEnum($"{name}.CompareFunction", faceState.CompareFunction); } - private void CheckColorAttachmentBlendState(string name, ColorAttachmentBlendState colorAttachment) + private void CheckColorAttachmentBlendState(string name, ColorAttachmentBlendState blendState) { - CheckEnum($"{name}.SourceRgbBlendFactor", colorAttachment.SourceRgbBlendFactor); - CheckEnum($"{name}.DestinationRgbBlendFactor", colorAttachment.DestinationRgbBlendFactor); - CheckEnum($"{name}.RgbBlendOperation", colorAttachment.RgbBlendOperation); - CheckEnum($"{name}.SourceAlphaBlendFactor", colorAttachment.SourceAlphaBlendFactor); - CheckEnum($"{name}.DestinationAlphaBlendFactor", colorAttachment.DestinationAlphaBlendFactor); - CheckEnum($"{name}.AlphaBlendOperation", colorAttachment.AlphaBlendOperation); - CheckFlags($"{name}.ColorWrites", colorAttachment.ColorWrites); + CheckEnum($"{name}.SourceRgbBlendFactor", blendState.SourceRgbBlendFactor); + CheckEnum($"{name}.DestinationRgbBlendFactor", blendState.DestinationRgbBlendFactor); + CheckEnum($"{name}.RgbBlendOperation", blendState.RgbBlendOperation); + CheckEnum($"{name}.SourceAlphaBlendFactor", blendState.SourceAlphaBlendFactor); + CheckEnum($"{name}.DestinationAlphaBlendFactor", blendState.DestinationAlphaBlendFactor); + CheckEnum($"{name}.AlphaBlendOperation", blendState.AlphaBlendOperation); + CheckFlags($"{name}.ColorWrites", blendState.ColorWrites); } private void CheckAttachmentFormats(string name, AttachmentFormats attachmentFormats) @@ -538,14 +538,14 @@ private void CheckAttachmentFormats(string name, AttachmentFormats attachmentFor return; } - for (int i = 0; i < attachmentFormats.ColorFormats.Length; i++) + for (int index = 0; index < attachmentFormats.ColorFormats.Length; index++) { - CheckEnum($"{name}.ColorFormats[{i}]", attachmentFormats.ColorFormats[i]); + CheckEnum($"{name}.ColorFormats[{index}]", attachmentFormats.ColorFormats[index]); } - if (attachmentFormats.DepthStencilFormat is { } format) + if (attachmentFormats.DepthStencilFormat is { } depthStencilFormat) { - CheckEnum($"{name}.DepthStencilFormat", format); + CheckEnum($"{name}.DepthStencilFormat", depthStencilFormat); } CheckEnum($"{name}.SampleCount", attachmentFormats.SampleCount); diff --git a/sources/Zenith.NET/ZenithMarshal.cs b/sources/Zenith.NET/ZenithMarshal.cs index 9c79fe3d..cb460fcf 100644 --- a/sources/Zenith.NET/ZenithMarshal.cs +++ b/sources/Zenith.NET/ZenithMarshal.cs @@ -47,14 +47,14 @@ public static nint AllocateAndFill(Scope scope, ReadOnlySpan data) where T public static nint StringToPointer(Scope scope, string value, StringEncoding encoding) { - byte[] values = encoding switch + byte[] bytes = encoding switch { StringEncoding.UTF8 => Encoding.UTF8.GetBytes(value + '\0'), StringEncoding.UTF16 => Encoding.Unicode.GetBytes(value + '\0'), _ => [] }; - return scope.Native(values); + return scope.Native(bytes); } public static nint StringArrayToPointer(Scope scope, string[] values, StringEncoding encoding) @@ -83,13 +83,13 @@ public static string[] StringArrayFromPointer(nint pointer, uint length, StringE { nint* pointers = (nint*)pointer; - string[] values = new string[length]; + string[] strings = new string[length]; for (uint i = 0; i < length; i++) { - values[i] = StringFromPointer(pointers[i], encoding); + strings[i] = StringFromPointer(pointers[i], encoding); } - return values; + return strings; } } \ No newline at end of file From 759dc7faa756f6b1adfe0264d369c76737c09606 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 11 May 2026 13:56:35 +0800 Subject: [PATCH 055/466] Set blend state flags explicitly in BlendStates presets Explicitly set IsAlphaToCoverageEnabled and IsIndependentBlendEnabled to false for all predefined BlendState instances (Additive, AlphaBlend, NonPremultiplied, Opaque, ColorDisabled) to ensure consistent initialization. --- sources/Zenith.NET/BlendStates.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs index da8d1666..35fbdf0e 100644 --- a/sources/Zenith.NET/BlendStates.cs +++ b/sources/Zenith.NET/BlendStates.cs @@ -20,6 +20,8 @@ public static class BlendStates public static readonly BlendState Additive = new() { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = true, @@ -32,6 +34,8 @@ public static class BlendStates public static readonly BlendState AlphaBlend = new() { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = true, @@ -44,6 +48,8 @@ public static class BlendStates public static readonly BlendState NonPremultiplied = new() { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = true, @@ -56,6 +62,8 @@ public static class BlendStates public static readonly BlendState Opaque = new() { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = false @@ -64,6 +72,8 @@ public static class BlendStates public static readonly BlendState ColorDisabled = new() { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = false, From ecce762a34433203f9f651540e4cdd1c2e804efd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 19 May 2026 12:14:26 +0800 Subject: [PATCH 056/466] Add SlangResourceHeap project and refactor enums/validation Added a new `SlangResourceHeap` project to test shader compilation using `Slangc.NET`, including a console app, shaders, and a README. Updated `Zenith.NET.slnx` and bumped dependency versions in `Directory.Packages.props`. Refactored blend and depth-stencil state properties for consistency. Replaced enums like `BlendOperation` and `CompareFunction` with new enums (`BlendOp`, `CompareOp`, etc.) for better naming. Introduced validation checks in `CommandBuffer.cs` for resource operations and added extensive validation methods in `ValidationLayer.cs`. Updated `BufferUsages` and `TextureUsages` to include new flags. Added shader test cases and a driver in `Program.cs` for compiling shaders to multiple targets. Documented usage in the new README. --- Zenith.NET.slnx | 3 +- sources/Directory.Packages.props | 12 +- .../Experiments/SlangResourceHeap/Program.cs | 232 +++++ .../Experiments/SlangResourceHeap/README.md | 64 ++ .../Shaders/01_cbo_texture_sampler.slang | 33 + .../Shaders/02_cbo_buffer_uav.slang | 23 + .../03_nonuniform_material_texture.slang | 44 + .../SlangResourceHeap.csproj | 12 + sources/Zenith.NET/BlendStates.cs | 36 +- sources/Zenith.NET/CommandBuffer.cs | 228 ++++- sources/Zenith.NET/DepthStencilStates.cs | 20 +- sources/Zenith.NET/Downloader.cs | 3 +- .../Enums/{BlendOperation.cs => BlendOp.cs} | 6 +- sources/Zenith.NET/Enums/BufferUsages.cs | 18 +- .../{CompareFunction.cs => CompareOp.cs} | 6 +- sources/Zenith.NET/Enums/LoadOp.cs | 10 + sources/Zenith.NET/Enums/LoadOperation.cs | 10 - .../{StencilOperation.cs => StencilOp.cs} | 6 +- sources/Zenith.NET/Enums/StoreOp.cs | 8 + sources/Zenith.NET/Enums/StoreOperation.cs | 8 - sources/Zenith.NET/Enums/TextureUsages.cs | 12 +- sources/Zenith.NET/Structs/ColorAttachment.cs | 4 +- .../Structs/ColorAttachmentBlendState.cs | 12 +- .../Structs/DepthStencilAttachment.cs | 10 +- .../Zenith.NET/Structs/DepthStencilState.cs | 2 +- .../Structs/MeshShadingPipelineDesc.cs | 4 +- .../Structs/RayTracingTriangleGeometry.cs | 8 +- sources/Zenith.NET/Structs/SamplerDesc.cs | 2 +- .../Zenith.NET/Structs/StencilFaceState.cs | 8 +- sources/Zenith.NET/Uploader.cs | 3 +- sources/Zenith.NET/ValidationLayer.cs | 893 +++++++++++++++++- 31 files changed, 1606 insertions(+), 134 deletions(-) create mode 100644 sources/Experiments/SlangResourceHeap/Program.cs create mode 100644 sources/Experiments/SlangResourceHeap/README.md create mode 100644 sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang create mode 100644 sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang create mode 100644 sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang create mode 100644 sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj rename sources/Zenith.NET/Enums/{BlendOperation.cs => BlendOp.cs} (55%) rename sources/Zenith.NET/Enums/{CompareFunction.cs => CompareOp.cs} (67%) create mode 100644 sources/Zenith.NET/Enums/LoadOp.cs delete mode 100644 sources/Zenith.NET/Enums/LoadOperation.cs rename sources/Zenith.NET/Enums/{StencilOperation.cs => StencilOp.cs} (72%) create mode 100644 sources/Zenith.NET/Enums/StoreOp.cs delete mode 100644 sources/Zenith.NET/Enums/StoreOperation.cs diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 5aff3b15..7fbe62c4 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -1,9 +1,10 @@ - + + diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index b45f13f8..bd73f9f6 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,11 +5,11 @@ - + - - + + @@ -18,10 +18,10 @@ - + - - + + \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs new file mode 100644 index 00000000..7da2f81d --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Program.cs @@ -0,0 +1,232 @@ +using Slangc.NET; + +internal sealed record ShaderEntry(string Name, string Stage); + +internal sealed record ShaderTest(string Name, string File, ShaderEntry[] Entries); + +internal sealed record TargetConfig(string Name, string OutputExtension, string[] Arguments); + +internal sealed record CompileOptions(string Target, bool UseSpvDescriptorHeapExt, bool Clean, bool ShowHelp) +{ + private static readonly HashSet ValidTargets = new(StringComparer.OrdinalIgnoreCase) + { + "all", + "dxil", + "spirv", + "metal-source", + "metal" + }; + + public static CompileOptions Parse(string[] arguments) + { + string target = "all"; + bool targetSpecified = false; + bool useSpvDescriptorHeapExt = false; + bool clean = false; + bool showHelp = false; + + for (int index = 0; index < arguments.Length; index++) + { + string argument = arguments[index]; + + switch (argument) + { + case "-h": + case "--help": + showHelp = true; + break; + + case "--target": + if (index + 1 >= arguments.Length) + { + throw new ArgumentException("Missing value for --target."); + } + + target = arguments[++index]; + targetSpecified = true; + break; + + case "--spv-descriptor-heap-ext": + useSpvDescriptorHeapExt = true; + break; + + case "--clean": + clean = true; + break; + + default: + if (argument.StartsWith('-')) + { + throw new ArgumentException($"Unknown option '{argument}'."); + } + + if (targetSpecified) + { + throw new ArgumentException($"Unexpected argument '{argument}'."); + } + + target = argument; + targetSpecified = true; + break; + } + } + + if (!ValidTargets.Contains(target)) + { + throw new ArgumentException($"Unknown target '{target}'. Expected one of: {string.Join(", ", ValidTargets)}."); + } + + return new(target, useSpvDescriptorHeapExt, clean, showHelp); + } +} + +internal static class Program +{ + private static readonly ShaderTest[] TestCases = + [ + new( + "01_cbo_texture_sampler", + "01_cbo_texture_sampler.slang", + [ + new("vertexMain", "vertex"), + new("fragmentMain", "fragment") + ]), + new( + "02_cbo_buffer_uav", + "02_cbo_buffer_uav.slang", + [ + new("computeMain", "compute") + ]), + new( + "03_nonuniform_material_texture", + "03_nonuniform_material_texture.slang", + [ + new("vertexMain", "vertex"), + new("fragmentMain", "fragment") + ]) + ]; + + private static int Main(string[] arguments) + { + try + { + CompileOptions options = CompileOptions.Parse(arguments); + if (options.ShowHelp) + { + PrintUsage(); + return 0; + } + + string projectRoot = FindProjectRoot(); + string shaderDirectory = Path.Combine(projectRoot, "Shaders"); + string outputRoot = Path.Combine(projectRoot, "out"); + + if (options.Clean && Directory.Exists(outputRoot)) + { + Directory.Delete(outputRoot, recursive: true); + } + + foreach (string target in ExpandTargets(options.Target)) + { + CompileTarget(shaderDirectory, outputRoot, CreateTargetConfig(target, options.UseSpvDescriptorHeapExt)); + } + + return 0; + } + catch (Exception exception) + { + Console.Error.WriteLine(exception.Message); + return 1; + } + } + + private static void CompileTarget(string shaderDirectory, string outputRoot, TargetConfig targetConfig) + { + string outputDirectory = Path.Combine(outputRoot, targetConfig.Name); + Directory.CreateDirectory(outputDirectory); + + foreach (ShaderTest testCase in TestCases) + { + string source = Path.Combine(shaderDirectory, testCase.File); + + foreach (ShaderEntry entry in testCase.Entries) + { + string output = Path.Combine(outputDirectory, $"{testCase.Name}.{entry.Name}{targetConfig.OutputExtension}"); + string[] compilerArguments = + [ + source, + "-entry", entry.Name, + "-stage", entry.Stage, + "-matrix-layout-row-major", + .. targetConfig.Arguments + ]; + + Console.WriteLine($"[{targetConfig.Name}] {testCase.Name}::{entry.Name}"); + + byte[] shaderBytes = SlangCompiler.Compile(compilerArguments); + File.WriteAllBytes(output, shaderBytes); + } + } + } + + private static string[] ExpandTargets(string target) + { + return target.Equals("all", StringComparison.OrdinalIgnoreCase) + ? ["dxil", "spirv", "metal-source"] + : [target]; + } + + private static TargetConfig CreateTargetConfig(string target, bool useSpvDescriptorHeapExt) + { + return target.ToLowerInvariant() switch + { + "dxil" => new("dxil", ".dxil", ["-target", "dxil", "-profile", "sm_6_6"]), + "spirv" => new("spirv", ".spv", CreateSpirvArguments(useSpvDescriptorHeapExt)), + "metal-source" => new("metal-source", ".metal", ["-target", "metal"]), + "metal" => new("metal", ".metallib", ["-target", "metallib", "-capability", "metallib_latest"]), + _ => throw new ArgumentException($"Unknown target '{target}'.") + }; + } + + private static string[] CreateSpirvArguments(bool useSpvDescriptorHeapExt) + { + List arguments = + [ + "-target", "spirv", + "-capability", "spirv_latest", + "-fvk-use-entrypoint-name", + "-bindless-space-index", "100" + ]; + + if (useSpvDescriptorHeapExt) + { + arguments.AddRange(["-capability", "spvDescriptorHeapEXT"]); + } + + return [.. arguments]; + } + + private static string FindProjectRoot() + { + foreach (string seed in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory }) + { + DirectoryInfo? directory = new(Path.GetFullPath(seed)); + while (directory is not null) + { + if (File.Exists(Path.Combine(directory.FullName, "SlangResourceHeap.csproj"))) + { + return directory.FullName; + } + + directory = directory.Parent; + } + } + + throw new DirectoryNotFoundException("Could not locate SlangResourceHeap.csproj from the current directory or app base directory."); + } + + private static void PrintUsage() + { + Console.WriteLine("Usage: dotnet run --project SlangResourceHeap.csproj -- [all|dxil|spirv|metal-source|metal] [--spv-descriptor-heap-ext] [--clean]"); + } +} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md new file mode 100644 index 00000000..f62f2d72 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -0,0 +1,64 @@ +# Slang ResourceHeap Minimal Tests + +This folder is independent from the Zenith.NET runtime projects. It contains a +small C# console app that references `Slangc.NET` directly, plus three Slang +shader files. The goal is to test whether the proposed public model can be +written as: + +```text +ConstantBuffer + ResourceHeap + SamplerHeap +``` + +The shaders rely on Slang `DescriptorHandle`. Compilation is driven through +the NuGet `Slangc.NET` package instead of any `slangc.exe` that may appear on +`PATH`. + +## Binding Contract + +- One `ConstantBuffer` containing ordinary parameters and typed handles. + Vulkan pins it to set 0 binding 0; DXIL lets Slang auto-assign the first CBO. +- Resource heap: descriptors referenced by `DescriptorHandle>`, + `DescriptorHandle>`, and + `DescriptorHandle>`. +- Sampler heap: descriptors referenced by `DescriptorHandle`. +- Vulkan uses `-bindless-space-index 100` so Slang's global bindless arrays do + not collide with app set 0 in these tests. +- DXIL uses `sm_6_6`, which is required for direct heap indexing. + +## Test Cases + +1. `Shaders/01_cbo_texture_sampler.slang` + - Fragment shader samples a texture handle and sampler handle stored in CBO. +2. `Shaders/02_cbo_buffer_uav.slang` + - Compute shader reads a structured buffer handle and writes an RW structured + buffer handle stored in CBO. +3. `Shaders/03_nonuniform_material_texture.slang` + - Fragment shader reads a material record from a structured buffer handle. + Each material contains a texture handle, and the shader uses `nonuniform` + before dereferencing it. + +## Compile + +Use the C# project directly: + +```powershell +dotnet run --project SlangResourceHeap.csproj -- all --clean +dotnet run --project SlangResourceHeap.csproj -- dxil +dotnet run --project SlangResourceHeap.csproj -- spirv +dotnet run --project SlangResourceHeap.csproj -- spirv --spv-descriptor-heap-ext +dotnet run --project SlangResourceHeap.csproj -- metal-source +dotnet run --project SlangResourceHeap.csproj -- metal +``` + +The `all` target compiles DXIL, SPIR-V, and Metal source. `metal-source` emits +MSL and does not require Apple's Metal compiler. `metal` emits `metallib` and is +intended for a machine with the Metal downstream compiler available. The project +writes outputs under `out/`. It does not run any host-side binding code. + +If `metal-source` succeeds but `metal` fails on a non-Apple machine, the missing +piece is the platform Metal compiler rather than the CBO/ResourceHeap shader +syntax. + +This experiment uses the repository's central `Slangc.NET` package version, so +updating `sources/Directory.Packages.props` is enough to move the compiler used +by the test app. \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang b/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang new file mode 100644 index 00000000..15fa3e13 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang @@ -0,0 +1,33 @@ +struct PassConstants +{ + DescriptorHandle> AlbedoTexture; + DescriptorHandle LinearSampler; + float4 Tint; +}; + +struct FullscreenVertex +{ + float4 Position : SV_POSITION; + float2 Uv : TEXCOORD0; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer gPass; + +[shader("vertex")] +FullscreenVertex vertexMain(uint vertexId : SV_VertexID) +{ + float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); + + FullscreenVertex output; + output.Position = float4(position, 0.0, 1.0); + output.Uv = position * 0.5 + 0.5; + + return output; +} + +[shader("fragment")] +float4 fragmentMain(FullscreenVertex input) : SV_TARGET +{ + return (*gPass.AlbedoTexture).Sample(*gPass.LinearSampler, input.Uv) * gPass.Tint; +} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang b/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang new file mode 100644 index 00000000..7a427d10 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang @@ -0,0 +1,23 @@ +struct BufferConstants +{ + DescriptorHandle> Source; + DescriptorHandle> Destination; + uint Count; + float4 Scale; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer gBuffers; + +[shader("compute")] +[numthreads(64, 1, 1)] +void computeMain(uint3 dispatchThreadId : SV_DispatchThreadID) +{ + uint index = dispatchThreadId.x; + if (index >= gBuffers.Count) + { + return; + } + + (*gBuffers.Destination)[index] = (*gBuffers.Source)[index] * gBuffers.Scale; +} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang b/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang new file mode 100644 index 00000000..ee6a03b0 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang @@ -0,0 +1,44 @@ +struct MaterialRecord +{ + DescriptorHandle> BaseColorTexture; + float4 Tint; +}; + +struct MaterialConstants +{ + DescriptorHandle> Materials; + DescriptorHandle LinearSampler; + uint MaterialCount; + float UvScale; +}; + +struct FullscreenVertex +{ + float4 Position : SV_POSITION; + float2 Uv : TEXCOORD0; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer gMaterials; + +[shader("vertex")] +FullscreenVertex vertexMain(uint vertexId : SV_VertexID) +{ + float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); + + FullscreenVertex output; + output.Position = float4(position, 0.0, 1.0); + output.Uv = position * 0.5 + 0.5; + + return output; +} + +[shader("fragment")] +float4 fragmentMain(FullscreenVertex input) : SV_TARGET +{ + uint count = max(gMaterials.MaterialCount, 1u); + uint materialIndex = min(uint(input.Uv.x * float(count)), count - 1u); + MaterialRecord material = (*gMaterials.Materials)[materialIndex]; + + return nonuniform(material.BaseColorTexture)->Sample(*gMaterials.LinearSampler, input.Uv * gMaterials.UvScale) * material.Tint; +} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj b/sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj new file mode 100644 index 00000000..42b3df31 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj @@ -0,0 +1,12 @@ + + + + Exe + $(StandardTargetFramework) + + + + + + + \ No newline at end of file diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs index 35fbdf0e..7b5d8d71 100644 --- a/sources/Zenith.NET/BlendStates.cs +++ b/sources/Zenith.NET/BlendStates.cs @@ -8,12 +8,12 @@ public static class BlendStates IsIndependentBlendEnabled = false, ColorAttachment0 = new() { - SourceRgbBlendFactor = BlendFactor.One, - DestinationRgbBlendFactor = BlendFactor.Zero, - RgbBlendOperation = BlendOperation.Add, - SourceAlphaBlendFactor = BlendFactor.One, - DestinationAlphaBlendFactor = BlendFactor.Zero, - AlphaBlendOperation = BlendOperation.Add, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.Zero, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.Zero, + AlphaOp = BlendOp.Add, ColorWrites = ColorWrites.All } }; @@ -25,10 +25,10 @@ public static class BlendStates ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = true, - SourceRgbBlendFactor = BlendFactor.SourceAlpha, - DestinationRgbBlendFactor = BlendFactor.One, - SourceAlphaBlendFactor = BlendFactor.SourceAlpha, - DestinationAlphaBlendFactor = BlendFactor.One + SrcRgbFactor = BlendFactor.SourceAlpha, + DstRgbFactor = BlendFactor.One, + SrcAlphaFactor = BlendFactor.SourceAlpha, + DstAlphaFactor = BlendFactor.One } }; @@ -39,10 +39,10 @@ public static class BlendStates ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = true, - SourceRgbBlendFactor = BlendFactor.One, - DestinationRgbBlendFactor = BlendFactor.OneMinusSourceAlpha, - SourceAlphaBlendFactor = BlendFactor.One, - DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.OneMinusSourceAlpha } }; @@ -53,10 +53,10 @@ public static class BlendStates ColorAttachment0 = Default.ColorAttachment0 with { IsBlendingEnabled = true, - SourceRgbBlendFactor = BlendFactor.SourceAlpha, - DestinationRgbBlendFactor = BlendFactor.OneMinusSourceAlpha, - SourceAlphaBlendFactor = BlendFactor.SourceAlpha, - DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha + SrcRgbFactor = BlendFactor.SourceAlpha, + DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + SrcAlphaFactor = BlendFactor.SourceAlpha, + DstAlphaFactor = BlendFactor.OneMinusSourceAlpha } }; diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 45fb8339..2eb98919 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -18,21 +18,41 @@ public void MemoryBarrier() public void MemoryBarrier(Texture texture) { + if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.texture", texture) is false) + { + return; + } + MemoryBarrierImpl(texture); } public void MemoryBarrier(Buffer buffer) { + if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.buffer", buffer) is false) + { + return; + } + MemoryBarrierImpl(buffer); } public void Transition(Texture texture, TextureSubresource subresource, TextureState state) { + if (Context.ValidationLayer?.ValidateTransition(texture, subresource, state) is false) + { + return; + } + TransitionImpl(texture, subresource, state); } public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) { + if (Context.ValidationLayer?.ValidateUpload(buffer, offsetInBytes, data) is false) + { + return; + } + Buffer stagingBuffer = Context.Uploader.Buffer(this, data.SizeInBytes); stagingBuffer.Upload(0, data); @@ -41,6 +61,11 @@ public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { + if (Context.ValidationLayer?.ValidateDownload(buffer, offsetInBytes, data) is false) + { + return; + } + Buffer stagingBuffer = Context.Downloader.Buffer(this, data); CopyBuffer(buffer, offsetInBytes, stagingBuffer, 0, data.SizeInBytes); @@ -48,6 +73,11 @@ public void Download(Buffer buffer, uint offsetInBytes, BufferData data) public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { + if (Context.ValidationLayer?.ValidateUpload(texture, subresource, offset, extent, data) is false) + { + return; + } + Buffer stagingBuffer = Context.Uploader.Buffer(this, data.Layout.SizeInBytes); stagingBuffer.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); @@ -56,6 +86,11 @@ public void Upload(Texture texture, TextureSubresource subresource, Offset3D off public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { + if (Context.ValidationLayer?.ValidateDownload(texture, subresource, offset, extent, data) is false) + { + return; + } + Buffer stagingBuffer = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); CopyTextureToBuffer(texture, subresource, offset, extent, stagingBuffer, 0, data.Layout); @@ -63,26 +98,51 @@ public void Download(Texture texture, TextureSubresource subresource, Offset3D o public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { + if (Context.ValidationLayer?.ValidateCopyBuffer(src, srcOffsetInBytes, dst, dstOffsetInBytes, sizeInBytes) is false) + { + return; + } + CopyBufferImpl(src, srcOffsetInBytes, dst, dstOffsetInBytes, sizeInBytes); } public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { + if (Context.ValidationLayer?.ValidateCopyBufferToTexture(src, srcOffsetInBytes, srcLayout, dst, dstSubresource, dstOffset, dstExtent) is false) + { + return; + } + CopyBufferToTextureImpl(src, srcOffsetInBytes, srcLayout, dst, dstSubresource, dstOffset, dstExtent); } public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { + if (Context.ValidationLayer?.ValidateCopyTexture(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent) is false) + { + return; + } + CopyTextureImpl(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent); } public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) { + if (Context.ValidationLayer?.ValidateCopyTextureToBuffer(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstLayout) is false) + { + return; + } + CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstLayout); } public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { + if (Context.ValidationLayer?.ValidateResolveTexture(src, srcSubresource, dst, dstSubresource) is false) + { + return; + } + ResolveTextureImpl(src, srcSubresource, dst, dstSubresource); } @@ -102,6 +162,11 @@ public TopLevelAccelerationStructure BuildAccelerationStructure(TopLevelAccelera public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { + if (Context.ValidationLayer?.ValidateResource("UpdateAccelerationStructure.accelerationStructure", accelerationStructure) is false) + { + return; + } + Context.ValidationLayer?.ValidateDesc(accelerationStructure.Desc, newDesc); UpdateAccelerationStructureImpl(accelerationStructure, newDesc); @@ -111,8 +176,14 @@ public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerati public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { - Span scissors = stackalloc Scissor[8]; - Span viewports = stackalloc Viewport[8]; + if (Context.ValidationLayer?.ValidateRenderPass(colorAttachments, depthStencilAttachment) is false) + { + return; + } + + int attachmentCount = colorAttachments.Length > 0 ? colorAttachments.Length : depthStencilAttachment is null ? 0 : 1; + Span scissors = stackalloc Scissor[attachmentCount]; + Span viewports = stackalloc Viewport[attachmentCount]; if (colorAttachments.Length > 0) { @@ -156,16 +227,31 @@ public void EndRenderPass() public void SetScissors(ReadOnlySpan scissors) { + if (Context.ValidationLayer?.ValidateScissors(scissors) is false) + { + return; + } + SetScissorsImpl(scissors); } public void SetViewports(ReadOnlySpan viewports) { + if (Context.ValidationLayer?.ValidateViewports(viewports) is false) + { + return; + } + SetViewportsImpl(viewports); } public void SetPipeline(GraphicsPipeline pipeline) { + if (Context.ValidationLayer?.ValidateResource("SetPipeline.pipeline", pipeline) is false) + { + return; + } + SetPipelineImpl(pipeline); currentPipeline = pipeline; @@ -173,6 +259,11 @@ public void SetPipeline(GraphicsPipeline pipeline) public void SetPipeline(ComputePipeline pipeline) { + if (Context.ValidationLayer?.ValidateResource("SetPipeline.pipeline", pipeline) is false) + { + return; + } + SetPipelineImpl(pipeline); currentPipeline = pipeline; @@ -180,6 +271,11 @@ public void SetPipeline(ComputePipeline pipeline) public void SetPipeline(MeshShadingPipeline pipeline) { + if (Context.ValidationLayer?.ValidateResource("SetPipeline.pipeline", pipeline) is false) + { + return; + } + SetPipelineImpl(pipeline); currentPipeline = pipeline; @@ -187,7 +283,12 @@ public void SetPipeline(MeshShadingPipeline pipeline) public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(SetVertexBuffer), out GraphicsPipeline pipeline)) + { + return; + } + + if (Context.ValidationLayer?.ValidateSetVertexBuffer(buffer, offsetInBytes, slot, pipeline) is false) { return; } @@ -197,7 +298,12 @@ public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(SetIndexBuffer), out GraphicsPipeline pipeline)) + { + return; + } + + if (Context.ValidationLayer?.ValidateSetIndexBuffer(buffer, offsetInBytes, indexFormat) is false) { return; } @@ -207,17 +313,22 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexF public void PushResourceTable(ResourceTable resourceTable) { - if (currentPipeline is null) + if (!TryGetCurrentPipeline(nameof(PushResourceTable), out Pipeline pipeline)) { return; } - PushResourceTableImpl(currentPipeline, resourceTable); + if (Context.ValidationLayer?.ValidateResource("PushResourceTable.resourceTable", resourceTable) is false) + { + return; + } + + PushResourceTableImpl(pipeline, resourceTable); } public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(Draw), out GraphicsPipeline pipeline)) { return; } @@ -227,7 +338,12 @@ public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint fi public void DrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(DrawIndirect), out GraphicsPipeline pipeline)) + { + return; + } + + if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DrawIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawArgsSizeInBytes, drawCount) is false) { return; } @@ -237,7 +353,7 @@ public void DrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCou public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(DrawIndexed), out GraphicsPipeline pipeline)) { return; } @@ -247,7 +363,12 @@ public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, in public void DrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(DrawIndexedIndirect), out GraphicsPipeline pipeline)) + { + return; + } + + if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DrawIndexedIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawIndexedArgsSizeInBytes, drawCount) is false) { return; } @@ -257,7 +378,7 @@ public void DrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ) { - if (currentPipeline is not ComputePipeline pipeline) + if (!TryGetCurrentPipeline(nameof(Dispatch), out ComputePipeline pipeline)) { return; } @@ -267,7 +388,12 @@ public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ) public void DispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) { - if (currentPipeline is not ComputePipeline pipeline) + if (!TryGetCurrentPipeline(nameof(DispatchIndirect), out ComputePipeline pipeline)) + { + return; + } + + if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DispatchIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchArgsSizeInBytes, 1) is false) { return; } @@ -277,7 +403,7 @@ public void DispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) public void DispatchMesh(uint groupCountX, uint groupCountY, uint groupCountZ) { - if (currentPipeline is not MeshShadingPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(DispatchMesh), out MeshShadingPipeline pipeline)) { return; } @@ -287,7 +413,12 @@ public void DispatchMesh(uint groupCountX, uint groupCountY, uint groupCountZ) public void DispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) { - if (currentPipeline is not MeshShadingPipeline pipeline) + if (!TryGetCurrentPipeline(nameof(DispatchMeshIndirect), out MeshShadingPipeline pipeline)) + { + return; + } + + if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DispatchMeshIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchMeshArgsSizeInBytes, dispatchCount) is false) { return; } @@ -297,6 +428,11 @@ public void DispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint public void BeginQuery(QueryHeap queryHeap, uint index) { + if (Context.ValidationLayer?.ValidateBeginQuery(queryHeap, index) is false) + { + return; + } + if (queryHeap.Desc.Type is QueryType.Timestamp) { return; @@ -307,6 +443,11 @@ public void BeginQuery(QueryHeap queryHeap, uint index) public void EndQuery(QueryHeap queryHeap, uint index) { + if (Context.ValidationLayer?.ValidateEndQuery(queryHeap, index) is false) + { + return; + } + if (queryHeap.Desc.Type is QueryType.Timestamp) { return; @@ -317,6 +458,11 @@ public void EndQuery(QueryHeap queryHeap, uint index) public void WriteTimestamp(QueryHeap queryHeap, uint index) { + if (Context.ValidationLayer?.ValidateWriteTimestamp(queryHeap, index) is false) + { + return; + } + if (queryHeap.Desc.Type is not QueryType.Timestamp) { return; @@ -327,6 +473,11 @@ public void WriteTimestamp(QueryHeap queryHeap, uint index) public void BeginDebugEvent(string label) { + if (Context.ValidationLayer?.ValidateDebugLabel(nameof(BeginDebugEvent), label) is false) + { + return; + } + if (string.IsNullOrWhiteSpace(label)) { return; @@ -342,6 +493,11 @@ public void EndDebugEvent() public void InsertDebugMarker(string label) { + if (Context.ValidationLayer?.ValidateDebugLabel(nameof(InsertDebugMarker), label) is false) + { + return; + } + if (string.IsNullOrWhiteSpace(label)) { return; @@ -378,6 +534,50 @@ protected override void Destroy() currentPipeline = null; } + private bool TryGetCurrentPipeline(string commandName, out Pipeline pipeline) + { + if (currentPipeline is not null) + { + if (Context.ValidationLayer?.ValidateResource($"{commandName}.pipeline", currentPipeline) is false) + { + pipeline = null!; + + return false; + } + + pipeline = currentPipeline; + + return true; + } + + Context.ValidationLayer?.ValidateCurrentPipeline(commandName, currentPipeline); + pipeline = null!; + + return false; + } + + private bool TryGetCurrentPipeline(string commandName, out TPipeline pipeline) where TPipeline : Pipeline + { + if (currentPipeline is TPipeline typedPipeline) + { + if (Context.ValidationLayer?.ValidateResource($"{commandName}.pipeline", typedPipeline) is false) + { + pipeline = null!; + + return false; + } + + pipeline = typedPipeline; + + return true; + } + + Context.ValidationLayer?.ValidateCurrentPipeline(commandName, currentPipeline); + pipeline = null!; + + return false; + } + protected abstract void MemoryBarrierImpl(); protected abstract void MemoryBarrierImpl(Texture texture); diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs index 5f631e0d..63926cdd 100644 --- a/sources/Zenith.NET/DepthStencilStates.cs +++ b/sources/Zenith.NET/DepthStencilStates.cs @@ -6,29 +6,29 @@ public static class DepthStencilStates { IsDepthEnabled = true, IsDepthWriteEnabled = true, - DepthCompareFunction = CompareFunction.LessEqual, + DepthCompareOp = CompareOp.LessEqual, IsStencilEnabled = false, StencilReadMask = 0xFF, StencilWriteMask = 0xFF, FrontFace = new() { - FailOperation = StencilOperation.Keep, - DepthFailOperation = StencilOperation.Keep, - PassOperation = StencilOperation.Keep, - CompareFunction = CompareFunction.Always + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always }, BackFace = new() { - FailOperation = StencilOperation.Keep, - DepthFailOperation = StencilOperation.Keep, - PassOperation = StencilOperation.Keep, - CompareFunction = CompareFunction.Always + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always } }; public static readonly DepthStencilState DefaultInverted = Default with { - DepthCompareFunction = CompareFunction.GreaterEqual + DepthCompareOp = CompareOp.GreaterEqual }; public static readonly DepthStencilState DepthRead = Default with diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index 092a8355..9a4b1469 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -21,7 +21,8 @@ public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) { SizeInBytes = data.SizeInBytes, StrideInBytes = 1, - Access = BufferAccess.CpuReadOnly + Access = BufferAccess.CpuReadOnly, + Usages = BufferUsages.CopyDst })); } diff --git a/sources/Zenith.NET/Enums/BlendOperation.cs b/sources/Zenith.NET/Enums/BlendOp.cs similarity index 55% rename from sources/Zenith.NET/Enums/BlendOperation.cs rename to sources/Zenith.NET/Enums/BlendOp.cs index 382afe12..dabe79a8 100644 --- a/sources/Zenith.NET/Enums/BlendOperation.cs +++ b/sources/Zenith.NET/Enums/BlendOp.cs @@ -1,6 +1,6 @@ -namespace Zenith.NET; +namespace Zenith.NET; -public enum BlendOperation +public enum BlendOp { Add, @@ -11,4 +11,4 @@ public enum BlendOperation Min, Max -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs index d38b2a49..4623127f 100644 --- a/sources/Zenith.NET/Enums/BufferUsages.cs +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -5,17 +5,21 @@ public enum BufferUsages { None = 0, - Vertex = 1 << 0, + CopySrc = 1 << 0, - Index = 1 << 1, + CopyDst = 1 << 1, - Uniform = 1 << 2, + Vertex = 1 << 2, - StorageReadOnly = 1 << 3, + Index = 1 << 3, - StorageReadWrite = 1 << 4, + Uniform = 1 << 4, - Indirect = 1 << 5, + StorageReadOnly = 1 << 5, - AccelerationStructure = 1 << 6 + StorageReadWrite = 1 << 6, + + Indirect = 1 << 7, + + AccelerationStructure = 1 << 8 } diff --git a/sources/Zenith.NET/Enums/CompareFunction.cs b/sources/Zenith.NET/Enums/CompareOp.cs similarity index 67% rename from sources/Zenith.NET/Enums/CompareFunction.cs rename to sources/Zenith.NET/Enums/CompareOp.cs index 0a61b0e1..8c28d024 100644 --- a/sources/Zenith.NET/Enums/CompareFunction.cs +++ b/sources/Zenith.NET/Enums/CompareOp.cs @@ -1,6 +1,6 @@ -namespace Zenith.NET; +namespace Zenith.NET; -public enum CompareFunction +public enum CompareOp { Never, @@ -17,4 +17,4 @@ public enum CompareFunction GreaterEqual, Always -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/LoadOp.cs b/sources/Zenith.NET/Enums/LoadOp.cs new file mode 100644 index 00000000..9d3ba99e --- /dev/null +++ b/sources/Zenith.NET/Enums/LoadOp.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public enum LoadOp +{ + Load, + + Clear, + + DontCare +} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/LoadOperation.cs b/sources/Zenith.NET/Enums/LoadOperation.cs deleted file mode 100644 index 3ef43181..00000000 --- a/sources/Zenith.NET/Enums/LoadOperation.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Zenith.NET; - -public enum LoadOperation -{ - Load, - - Clear, - - DontCare -} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/StencilOperation.cs b/sources/Zenith.NET/Enums/StencilOp.cs similarity index 72% rename from sources/Zenith.NET/Enums/StencilOperation.cs rename to sources/Zenith.NET/Enums/StencilOp.cs index 61cfa89d..605979e3 100644 --- a/sources/Zenith.NET/Enums/StencilOperation.cs +++ b/sources/Zenith.NET/Enums/StencilOp.cs @@ -1,6 +1,6 @@ -namespace Zenith.NET; +namespace Zenith.NET; -public enum StencilOperation +public enum StencilOp { Keep, @@ -17,4 +17,4 @@ public enum StencilOperation IncrementAndWrap, DecrementAndWrap -} +} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/StoreOp.cs b/sources/Zenith.NET/Enums/StoreOp.cs new file mode 100644 index 00000000..fc74145f --- /dev/null +++ b/sources/Zenith.NET/Enums/StoreOp.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public enum StoreOp +{ + Store, + + DontCare +} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/StoreOperation.cs b/sources/Zenith.NET/Enums/StoreOperation.cs deleted file mode 100644 index 256ff4f5..00000000 --- a/sources/Zenith.NET/Enums/StoreOperation.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public enum StoreOperation -{ - Store, - - DontCare -} \ No newline at end of file diff --git a/sources/Zenith.NET/Enums/TextureUsages.cs b/sources/Zenith.NET/Enums/TextureUsages.cs index f50e0a28..e91450a3 100644 --- a/sources/Zenith.NET/Enums/TextureUsages.cs +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -5,11 +5,15 @@ public enum TextureUsages { None = 0, - ColorAttachment = 1 << 0, + CopySrc = 1 << 0, - DepthStencil = 1 << 1, + CopyDst = 1 << 1, - Sampled = 1 << 2, + ColorAttachment = 1 << 2, - Storage = 1 << 3 + DepthStencil = 1 << 3, + + Sampled = 1 << 4, + + Storage = 1 << 5 } diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 314f0170..8a690199 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -12,9 +12,9 @@ public record struct ColorAttachment public TextureSubresource ResolveSubresource; - public LoadOperation LoadOperation; + public LoadOp LoadOp; - public StoreOperation StoreOperation; + public StoreOp StoreOp; public Vector4 ClearColor; } diff --git a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs index 7ac1bdff..aff3995c 100644 --- a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -4,17 +4,17 @@ public record struct ColorAttachmentBlendState { public bool IsBlendingEnabled; - public BlendFactor SourceRgbBlendFactor; + public BlendFactor SrcRgbFactor; - public BlendFactor DestinationRgbBlendFactor; + public BlendFactor DstRgbFactor; - public BlendOperation RgbBlendOperation; + public BlendOp RgbOp; - public BlendFactor SourceAlphaBlendFactor; + public BlendFactor SrcAlphaFactor; - public BlendFactor DestinationAlphaBlendFactor; + public BlendFactor DstAlphaFactor; - public BlendOperation AlphaBlendOperation; + public BlendOp AlphaOp; public ColorWrites ColorWrites; } \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs index e050ca08..88f3a8bd 100644 --- a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -6,15 +6,15 @@ public record struct DepthStencilAttachment public TextureSubresource Subresource; - public LoadOperation DepthLoadOperation; + public LoadOp DepthLoadOp; - public StoreOperation DepthStoreOperation; + public StoreOp DepthStoreOp; - public LoadOperation StencilLoadOperation; + public float ClearDepth; - public StoreOperation StencilStoreOperation; + public LoadOp StencilLoadOp; - public float ClearDepth; + public StoreOp StencilStoreOp; public byte ClearStencil; } diff --git a/sources/Zenith.NET/Structs/DepthStencilState.cs b/sources/Zenith.NET/Structs/DepthStencilState.cs index 00d5a307..f6276736 100644 --- a/sources/Zenith.NET/Structs/DepthStencilState.cs +++ b/sources/Zenith.NET/Structs/DepthStencilState.cs @@ -6,7 +6,7 @@ public record struct DepthStencilState public bool IsDepthWriteEnabled; - public CompareFunction DepthCompareFunction; + public CompareOp DepthCompareOp; public bool IsStencilEnabled; diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index fec7acc9..d965f58c 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -4,8 +4,6 @@ public record struct MeshShadingPipelineDesc { public RenderState RenderState; - public Shader? TaskShader; - public Shader MeshShader; public Shader FragmentShader; @@ -15,4 +13,6 @@ public record struct MeshShadingPipelineDesc public PrimitiveTopology PrimitiveTopology; public AttachmentFormats AttachmentFormats; + + public Shader? TaskShader; } diff --git a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index 949b4d6d..faf68c88 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -6,6 +6,10 @@ public record struct RayTracingTriangleGeometry { public Buffer VertexBuffer; + public Buffer? IndexBuffer; + + public Matrix4x4 Transform; + public PixelFormat VertexFormat; public uint VertexCount; @@ -14,13 +18,9 @@ public record struct RayTracingTriangleGeometry public uint VertexOffsetInBytes; - public Buffer? IndexBuffer; - public IndexFormat IndexFormat; public uint IndexCount; public uint IndexOffsetInBytes; - - public Matrix4x4 Transform; } \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 1a207de8..7c507786 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -14,7 +14,7 @@ public record struct SamplerDesc public AddressMode AddressW; - public CompareFunction CompareFunction; + public CompareOp CompareOp; public uint MaxAnisotropy; diff --git a/sources/Zenith.NET/Structs/StencilFaceState.cs b/sources/Zenith.NET/Structs/StencilFaceState.cs index 4fd16e01..156c00ab 100644 --- a/sources/Zenith.NET/Structs/StencilFaceState.cs +++ b/sources/Zenith.NET/Structs/StencilFaceState.cs @@ -2,11 +2,11 @@ public record struct StencilFaceState { - public StencilOperation FailOperation; + public StencilOp FailOp; - public StencilOperation DepthFailOperation; + public StencilOp DepthFailOp; - public StencilOperation PassOperation; + public StencilOp PassOp; - public CompareFunction CompareFunction; + public CompareOp CompareOp; } diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index 8293b3df..d5785291 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -21,7 +21,8 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 1, - Access = BufferAccess.CpuWriteOnly + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.CopySrc })); } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 5ececb74..2dcb7770 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -160,7 +160,7 @@ internal void ValidateDesc(SamplerDesc desc) CheckEnum("SamplerDesc.AddressU", desc.AddressU); CheckEnum("SamplerDesc.AddressV", desc.AddressV); CheckEnum("SamplerDesc.AddressW", desc.AddressW); - CheckEnum("SamplerDesc.CompareFunction", desc.CompareFunction); + CheckEnum("SamplerDesc.CompareOp", desc.CompareOp); if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) { @@ -231,11 +231,6 @@ internal void ValidateDesc(MeshShadingPipelineDesc desc) { CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); - if (desc.TaskShader is not null) - { - CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); - } - CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); @@ -247,6 +242,11 @@ internal void ValidateDesc(MeshShadingPipelineDesc desc) } CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); + + if (desc.TaskShader is not null) + { + CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); + } } internal void ValidateDesc(QueryHeapDesc desc) @@ -301,6 +301,413 @@ internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAc } } + internal bool ValidateResource(string name, T? resource) where T : GraphicsResource + { + return CheckResource(name, resource); + } + + internal bool ValidateTransition(Texture texture, TextureSubresource subresource, TextureState state) + { + bool isValid = CheckResource("Transition.texture", texture); + + if (isValid) + { + isValid &= CheckTextureSubresource("Transition.subresource", texture, subresource); + } + + isValid &= CheckEnum("Transition.state", state); + + return isValid; + } + + internal bool ValidateUpload(Buffer buffer, uint offsetInBytes, BufferData data) + { + bool isValid = CheckResource("Upload.buffer", buffer); + isValid &= CheckBufferData("Upload.data", data); + + if (isValid) + { + isValid &= CheckBufferUsage("Upload.buffer", buffer, BufferUsages.CopyDst); + isValid &= CheckBufferRange("Upload.buffer", buffer, offsetInBytes, data.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateDownload(Buffer buffer, uint offsetInBytes, BufferData data) + { + bool isValid = CheckResource("Download.buffer", buffer); + isValid &= CheckBufferData("Download.data", data); + + if (isValid) + { + isValid &= CheckBufferUsage("Download.buffer", buffer, BufferUsages.CopySrc); + isValid &= CheckBufferRange("Download.buffer", buffer, offsetInBytes, data.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateUpload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + bool isValid = CheckResource("Upload.texture", texture); + isValid &= CheckTextureData("Upload.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); + + if (isValid) + { + isValid &= CheckTextureUsage("Upload.texture", texture, TextureUsages.CopyDst); + isValid &= CheckTextureRange("Upload.texture", texture, subresource, offset, extent); + } + + return isValid; + } + + internal bool ValidateDownload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + bool isValid = CheckResource("Download.texture", texture); + isValid &= CheckTextureData("Download.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); + + if (isValid) + { + isValid &= CheckTextureUsage("Download.texture", texture, TextureUsages.CopySrc); + isValid &= CheckTextureRange("Download.texture", texture, subresource, offset, extent); + } + + return isValid; + } + + internal bool ValidateCopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) + { + bool hasSrc = CheckResource("CopyBuffer.src", src); + bool hasDst = CheckResource("CopyBuffer.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckBufferUsage("CopyBuffer.src", src, BufferUsages.CopySrc); + isValid &= CheckBufferRange("CopyBuffer.src", src, srcOffsetInBytes, sizeInBytes); + } + + if (hasDst) + { + isValid &= CheckBufferUsage("CopyBuffer.dst", dst, BufferUsages.CopyDst); + isValid &= CheckBufferRange("CopyBuffer.dst", dst, dstOffsetInBytes, sizeInBytes); + } + + return isValid; + } + + internal bool ValidateCopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + bool hasSrc = CheckResource("CopyBufferToTexture.src", src); + bool hasDst = CheckResource("CopyBufferToTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckBufferUsage("CopyBufferToTexture.src", src, BufferUsages.CopySrc); + isValid &= CheckTextureDataLayout("CopyBufferToTexture.srcLayout", srcLayout, hasDst ? dst.Desc.Format : PixelFormat.Unknown, dstExtent); + isValid &= CheckBufferRange("CopyBufferToTexture.src", src, srcOffsetInBytes, srcLayout.SizeInBytes); + } + + if (hasDst) + { + isValid &= CheckTextureUsage("CopyBufferToTexture.dst", dst, TextureUsages.CopyDst); + isValid &= CheckTextureRange("CopyBufferToTexture.dst", dst, dstSubresource, dstOffset, dstExtent); + } + + return isValid; + } + + internal bool ValidateCopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) + { + bool hasSrc = CheckResource("CopyTexture.src", src); + bool hasDst = CheckResource("CopyTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureUsage("CopyTexture.src", src, TextureUsages.CopySrc); + isValid &= CheckTextureRange("CopyTexture.src", src, srcSubresource, srcOffset, extent); + } + + if (hasDst) + { + isValid &= CheckTextureUsage("CopyTexture.dst", dst, TextureUsages.CopyDst); + isValid &= CheckTextureRange("CopyTexture.dst", dst, dstSubresource, dstOffset, extent); + } + + if (hasSrc && hasDst) + { + isValid &= CheckSameValue("CopyTexture.Format", src.Desc.Format, dst.Desc.Format); + isValid &= CheckSameValue("CopyTexture.SampleCount", src.Desc.SampleCount, dst.Desc.SampleCount); + } + + return isValid; + } + + internal bool ValidateCopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) + { + bool hasSrc = CheckResource("CopyTextureToBuffer.src", src); + bool hasDst = CheckResource("CopyTextureToBuffer.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureUsage("CopyTextureToBuffer.src", src, TextureUsages.CopySrc); + isValid &= CheckTextureRange("CopyTextureToBuffer.src", src, srcSubresource, srcOffset, srcExtent); + isValid &= CheckTextureDataLayout("CopyTextureToBuffer.dstLayout", dstLayout, src.Desc.Format, srcExtent); + } + + if (hasDst) + { + isValid &= CheckBufferUsage("CopyTextureToBuffer.dst", dst, BufferUsages.CopyDst); + isValid &= CheckBufferRange("CopyTextureToBuffer.dst", dst, dstOffsetInBytes, dstLayout.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + bool hasSrc = CheckResource("ResolveTexture.src", src); + bool hasDst = CheckResource("ResolveTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureSubresource("ResolveTexture.srcSubresource", src, srcSubresource); + + if (src.Desc.SampleCount is SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeMultisampled, "ResolveTexture.src")); + isValid = false; + } + } + + if (hasDst) + { + isValid &= CheckTextureSubresource("ResolveTexture.dstSubresource", dst, dstSubresource); + + if (dst.Desc.SampleCount is not SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeSingleSampled, "ResolveTexture.dst")); + isValid = false; + } + } + + if (hasSrc && hasDst) + { + isValid &= CheckSameValue("ResolveTexture.Format", src.Desc.Format, dst.Desc.Format); + isValid &= CheckSameMipExtent("ResolveTexture", src, srcSubresource, dst, dstSubresource); + } + + return isValid; + } + + internal bool ValidateRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + bool isValid = true; + bool hasExtent = false; + uint renderWidth = 0; + uint renderHeight = 0; + + if (colorAttachments.Length > ValidationConstants.MaxColorAttachments) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "BeginRenderPass.colorAttachments.Length", ValidationConstants.MaxColorAttachments)); + isValid = false; + } + + if (colorAttachments.Length is 0 && depthStencilAttachment is null) + { + ReportError(string.Format(ValidationMessages.HasNoAttachments, "BeginRenderPass")); + isValid = false; + } + + for (int index = 0; index < colorAttachments.Length; index++) + { + isValid &= CheckColorAttachment($"BeginRenderPass.colorAttachments[{index}]", colorAttachments[index], ref hasExtent, ref renderWidth, ref renderHeight); + } + + if (depthStencilAttachment is { } attachment) + { + isValid &= CheckDepthStencilAttachment("BeginRenderPass.depthStencilAttachment", attachment, ref hasExtent, ref renderWidth, ref renderHeight); + } + + return isValid; + } + + internal bool ValidateScissors(ReadOnlySpan scissors) + { + bool isValid = true; + + if (scissors.Length is 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetScissors.scissors")); + return false; + } + + for (int index = 0; index < scissors.Length; index++) + { + isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Width", scissors[index].Width); + isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Height", scissors[index].Height); + } + + return isValid; + } + + internal bool ValidateViewports(ReadOnlySpan viewports) + { + bool isValid = true; + + if (viewports.Length is 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetViewports.viewports")); + return false; + } + + for (int index = 0; index < viewports.Length; index++) + { + Viewport viewport = viewports[index]; + + isValid &= CheckFinite($"SetViewports.viewports[{index}].Width", viewport.Width); + isValid &= CheckFinite($"SetViewports.viewports[{index}].Height", viewport.Height); + isValid &= CheckFinite($"SetViewports.viewports[{index}].MinDepth", viewport.MinDepth); + isValid &= CheckFinite($"SetViewports.viewports[{index}].MaxDepth", viewport.MaxDepth); + + if (viewport.Width <= 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Width")); + isValid = false; + } + + if (viewport.Height <= 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Height")); + isValid = false; + } + + if (viewport.MinDepth > viewport.MaxDepth) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"SetViewports.viewports[{index}].MinDepth", $"SetViewports.viewports[{index}].MaxDepth")); + isValid = false; + } + } + + return isValid; + } + + internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); + + return false; + } + + internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) where TPipeline : Pipeline + { + if (currentPipeline is null) + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); + } + else + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipelineType, commandName, typeof(TPipeline).Name, currentPipeline.GetType().Name)); + } + + return false; + } + + internal bool ValidateSetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot, GraphicsPipeline pipeline) + { + bool isValid = CheckResource("SetVertexBuffer.buffer", buffer); + + if (isValid) + { + isValid &= CheckBufferUsage("SetVertexBuffer.buffer", buffer, BufferUsages.Vertex); + isValid &= CheckBufferOffset("SetVertexBuffer.buffer", buffer, offsetInBytes); + } + + if (pipeline.Desc.InputLayouts is not null && slot >= pipeline.Desc.InputLayouts.Length) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "SetVertexBuffer.slot", "GraphicsPipelineDesc.InputLayouts.Length")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) + { + bool isValid = CheckResource("SetIndexBuffer.buffer", buffer); + isValid &= CheckEnum("SetIndexBuffer.indexFormat", indexFormat); + + if (isValid) + { + isValid &= CheckBufferUsage("SetIndexBuffer.buffer", buffer, BufferUsages.Index); + isValid &= CheckBufferOffset("SetIndexBuffer.buffer", buffer, offsetInBytes); + } + + return isValid; + } + + internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) + { + bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); + + if (isValid) + { + isValid &= CheckBufferUsage($"{commandName}.indirectBuffer", indirectBuffer, BufferUsages.Indirect); + isValid &= CheckBufferRange($"{commandName}.indirectBuffer", indirectBuffer, offsetInBytes, (ulong)argsSizeInBytes * drawOrDispatchCount, allowZeroSize: true); + } + + return isValid; + } + + internal bool ValidateBeginQuery(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("BeginQuery", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "BeginQuery")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateEndQuery(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("EndQuery", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "EndQuery")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateWriteTimestamp(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("WriteTimestamp", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is not QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustBeTimestampQuery, "WriteTimestamp")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateDebugLabel(string commandName, string? label) + { + return CheckStringNotWhitespace($"{commandName}.label", label); + } + private bool CheckResource(string name, T? resource) where T : GraphicsResource { if (resource is null) @@ -388,6 +795,413 @@ private bool CheckGreaterThanZero(string name, T value) where T : struct, INu return true; } + private bool CheckBufferData(string name, BufferData data) + { + bool isValid = true; + + if (data.Pointer == 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeZero, $"{name}.Pointer")); + isValid = false; + } + + isValid &= CheckGreaterThanZero($"{name}.SizeInBytes", data.SizeInBytes); + + return isValid; + } + + private bool CheckTextureData(string name, TextureData data, PixelFormat format, Extent3D extent) + { + bool isValid = true; + + if (data.Pointer == 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeZero, $"{name}.Pointer")); + isValid = false; + } + + isValid &= CheckTextureDataLayout($"{name}.Layout", data.Layout, format, extent); + + return isValid; + } + + private bool CheckTextureDataLayout(string name, TextureDataLayout layout, PixelFormat format, Extent3D extent) + { + bool isValid = true; + + isValid &= CheckGreaterThanZero($"{name}.SizeInBytes", layout.SizeInBytes); + isValid &= CheckGreaterThanZero($"{name}.RowStrideInBytes", layout.RowStrideInBytes); + isValid &= CheckGreaterThanZero($"{name}.SliceStrideInBytes", layout.SliceStrideInBytes); + + if (!CheckTextureExtent($"{name}.Extent", extent)) + { + return false; + } + + uint minRowStrideInBytes = ZenithHelper.RowStrideInBytes(format, extent.Width, extent.Height); + + if (minRowStrideInBytes is 0) + { + return isValid; + } + + if (layout.RowStrideInBytes < minRowStrideInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanOrEqualTo, $"{name}.RowStrideInBytes", minRowStrideInBytes)); + isValid = false; + } + + (_, _, _, uint blocksHigh) = ZenithHelper.BlockLayout(format, extent.Width, extent.Height); + ulong minSliceStrideInBytes = (ulong)layout.RowStrideInBytes * blocksHigh; + + if (layout.SliceStrideInBytes < minSliceStrideInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanOrEqualTo, $"{name}.SliceStrideInBytes", minSliceStrideInBytes)); + isValid = false; + } + + ulong minSizeInBytes = extent.Depth is 0 ? 0 : ((ulong)layout.SliceStrideInBytes * (extent.Depth - 1)) + minSliceStrideInBytes; + + if (layout.SizeInBytes < minSizeInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanOrEqualTo, $"{name}.SizeInBytes", minSizeInBytes)); + isValid = false; + } + + return isValid; + } + + private bool CheckBufferUsage(string name, Buffer buffer, BufferUsages requiredUsage) + { + if (!buffer.Desc.Usages.HasFlag(requiredUsage)) + { + ReportError(string.Format(ValidationMessages.MustHaveUsage, name, requiredUsage)); + + return false; + } + + return true; + } + + private bool CheckTextureUsage(string name, Texture texture, TextureUsages requiredUsage) + { + if (!texture.Desc.Usages.HasFlag(requiredUsage)) + { + ReportError(string.Format(ValidationMessages.MustHaveUsage, name, requiredUsage)); + + return false; + } + + return true; + } + + private bool CheckBufferOffset(string name, Buffer buffer, uint offsetInBytes) + { + if (offsetInBytes > buffer.Desc.SizeInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.OffsetInBytes", name)); + + return false; + } + + return true; + } + + private bool CheckBufferRange(string name, Buffer buffer, uint offsetInBytes, ulong sizeInBytes, bool allowZeroSize = false) + { + bool isValid = CheckBufferOffset(name, buffer, offsetInBytes); + + if (!allowZeroSize && sizeInBytes is 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.SizeInBytes")); + isValid = false; + } + + if ((ulong)offsetInBytes + sizeInBytes > buffer.Desc.SizeInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, name, "the buffer")); + isValid = false; + } + + return isValid; + } + + private bool CheckTextureSubresource(string name, Texture texture, TextureSubresource subresource) + { + bool isValid = true; + + if (subresource.MipLevel >= texture.Desc.MipLevels) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{name}.MipLevel", "TextureDesc.MipLevels")); + isValid = false; + } + + if (subresource.ArrayLayer >= texture.Desc.ArrayLayers) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{name}.ArrayLayer", "TextureDesc.ArrayLayers")); + isValid = false; + } + + return isValid; + } + + private bool CheckTextureExtent(string name, Extent3D extent) + { + if (extent.Width is 0 || extent.Height is 0 || extent.Depth is 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name} dimensions (Width, Height, Depth)")); + + return false; + } + + return true; + } + + private bool CheckTextureRange(string name, Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent) + { + bool isValid = CheckTextureSubresource($"{name}.Subresource", texture, subresource); + isValid &= CheckTextureExtent($"{name}.Extent", extent); + + if (!isValid) + { + return false; + } + + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, subresource.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); + + if ((ulong)offset.X + extent.Width > mipWidth) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.X range", "the texture subresource width")); + isValid = false; + } + + if ((ulong)offset.Y + extent.Height > mipHeight) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.Y range", "the texture subresource height")); + isValid = false; + } + + if ((ulong)offset.Z + extent.Depth > mipDepth) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.Z range", "the texture subresource depth")); + isValid = false; + } + + return isValid; + } + + private bool CheckSameValue(string name, T first, T second) + { + if (!EqualityComparer.Default.Equals(first, second)) + { + ReportError(string.Format(ValidationMessages.MustHaveSameValue, name, first, second)); + + return false; + } + + return true; + } + + private bool CheckSameMipExtent(string name, Texture first, TextureSubresource firstSubresource, Texture second, TextureSubresource secondSubresource) + { + if (!CheckTextureSubresource($"{name}.firstSubresource", first, firstSubresource) || !CheckTextureSubresource($"{name}.secondSubresource", second, secondSubresource)) + { + return false; + } + + ZenithHelper.MipDimensions(first.Desc.Width, first.Desc.Height, first.Desc.Depth, firstSubresource.MipLevel, out uint firstWidth, out uint firstHeight, out uint firstDepth); + ZenithHelper.MipDimensions(second.Desc.Width, second.Desc.Height, second.Desc.Depth, secondSubresource.MipLevel, out uint secondWidth, out uint secondHeight, out uint secondDepth); + + bool isValid = true; + isValid &= CheckSameValue($"{name}.Width", firstWidth, secondWidth); + isValid &= CheckSameValue($"{name}.Height", firstHeight, secondHeight); + isValid &= CheckSameValue($"{name}.Depth", firstDepth, secondDepth); + + return isValid; + } + + private bool CheckColorAttachment(string name, ColorAttachment attachment, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) + { + bool hasTexture = CheckResource($"{name}.Texture", attachment.Texture); + bool isValid = hasTexture; + + isValid &= CheckEnum($"{name}.LoadOp", attachment.LoadOp); + isValid &= CheckEnum($"{name}.StoreOp", attachment.StoreOp); + + if (hasTexture) + { + isValid &= CheckTextureUsage($"{name}.Texture", attachment.Texture, TextureUsages.ColorAttachment); + isValid &= CheckColorFormat($"{name}.Texture.Desc.Format", attachment.Texture.Desc.Format); + + if (CheckTextureSubresource($"{name}.Subresource", attachment.Texture, attachment.Subresource)) + { + isValid &= CheckRenderPassExtent(name, attachment.Texture, attachment.Subresource, ref hasExtent, ref renderWidth, ref renderHeight); + } + else + { + isValid = false; + } + } + + if (attachment.ResolveTexture is { } resolveTexture) + { + bool hasResolveTexture = CheckResource($"{name}.ResolveTexture", resolveTexture); + isValid &= hasResolveTexture; + + if (hasResolveTexture) + { + isValid &= CheckTextureUsage($"{name}.ResolveTexture", resolveTexture, TextureUsages.ColorAttachment); + isValid &= CheckColorFormat($"{name}.ResolveTexture.Desc.Format", resolveTexture.Desc.Format); + + if (hasTexture) + { + isValid &= CheckSameValue($"{name}.ResolveTexture.Desc.Format", attachment.Texture.Desc.Format, resolveTexture.Desc.Format); + + if (attachment.Texture.Desc.SampleCount is SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeMultisampled, $"{name}.Texture")); + isValid = false; + } + } + + if (resolveTexture.Desc.SampleCount is not SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeSingleSampled, $"{name}.ResolveTexture")); + isValid = false; + } + + if (CheckTextureSubresource($"{name}.ResolveSubresource", resolveTexture, attachment.ResolveSubresource)) + { + isValid &= CheckRenderPassExtent($"{name}.ResolveTexture", resolveTexture, attachment.ResolveSubresource, ref hasExtent, ref renderWidth, ref renderHeight); + } + else + { + isValid = false; + } + } + } + + return isValid; + } + + private bool CheckDepthStencilAttachment(string name, DepthStencilAttachment attachment, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) + { + bool hasTexture = CheckResource($"{name}.Texture", attachment.Texture); + bool isValid = hasTexture; + + isValid &= CheckEnum($"{name}.DepthLoadOp", attachment.DepthLoadOp); + isValid &= CheckEnum($"{name}.DepthStoreOp", attachment.DepthStoreOp); + isValid &= CheckEnum($"{name}.StencilLoadOp", attachment.StencilLoadOp); + isValid &= CheckEnum($"{name}.StencilStoreOp", attachment.StencilStoreOp); + + if (attachment.ClearDepth is < 0.0f or > 1.0f) + { + ReportError(string.Format(ValidationMessages.MustBeBetween, $"{name}.ClearDepth", 0.0f, 1.0f)); + isValid = false; + } + + if (hasTexture) + { + isValid &= CheckTextureUsage($"{name}.Texture", attachment.Texture, TextureUsages.DepthStencil); + isValid &= CheckDepthStencilFormat($"{name}.Texture.Desc.Format", attachment.Texture.Desc.Format); + + if (CheckTextureSubresource($"{name}.Subresource", attachment.Texture, attachment.Subresource)) + { + isValid &= CheckRenderPassExtent(name, attachment.Texture, attachment.Subresource, ref hasExtent, ref renderWidth, ref renderHeight); + } + else + { + isValid = false; + } + } + + return isValid; + } + + private bool CheckColorFormat(string name, PixelFormat format) + { + if (ZenithHelper.HasDepth(format) || ZenithHelper.HasStencil(format)) + { + ReportError(string.Format(ValidationMessages.MustNotBeDepthStencilFormat, name)); + + return false; + } + + return true; + } + + private bool CheckDepthStencilFormat(string name, PixelFormat format) + { + if (!ZenithHelper.HasDepth(format) && !ZenithHelper.HasStencil(format)) + { + ReportError(string.Format(ValidationMessages.MustBeDepthStencilFormat, name)); + + return false; + } + + return true; + } + + private bool CheckRenderPassExtent(string name, Texture texture, TextureSubresource subresource, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) + { + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, subresource.MipLevel, out uint width, out uint height, out _); + + if (!hasExtent) + { + renderWidth = width; + renderHeight = height; + hasExtent = true; + + return true; + } + + bool isValid = true; + + if (width != renderWidth) + { + ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Width", width, renderWidth)); + isValid = false; + } + + if (height != renderHeight) + { + ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Height", height, renderHeight)); + isValid = false; + } + + return isValid; + } + + private bool CheckFinite(string name, float value) + { + if (!float.IsFinite(value)) + { + ReportError(string.Format(ValidationMessages.MustBeFinite, name)); + + return false; + } + + return true; + } + + private bool CheckQuery(string commandName, QueryHeap queryHeap, uint index) + { + bool isValid = CheckResource($"{commandName}.queryHeap", queryHeap); + + if (isValid) + { + isValid &= CheckEnum($"{commandName}.queryHeap.Desc.Type", queryHeap.Desc.Type); + + if (index >= queryHeap.Desc.Count) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{commandName}.index", "QueryHeapDesc.Count")); + isValid = false; + } + } + + return isValid; + } + private void CheckSurface(string name, Surface surface) { if (!CheckEnum($"{name}.Type", surface.Type)) @@ -493,7 +1307,7 @@ private void CheckRasterizerState(string name, RasterizerState rasterizerState) private void CheckDepthStencilState(string name, DepthStencilState depthStencilState) { - CheckEnum($"{name}.DepthCompareFunction", depthStencilState.DepthCompareFunction); + CheckEnum($"{name}.DepthCompareOp", depthStencilState.DepthCompareOp); CheckStencilFaceState($"{name}.FrontFace", depthStencilState.FrontFace); CheckStencilFaceState($"{name}.BackFace", depthStencilState.BackFace); } @@ -512,20 +1326,20 @@ private void CheckBlendState(string name, BlendState blendState) private void CheckStencilFaceState(string name, StencilFaceState faceState) { - CheckEnum($"{name}.FailOperation", faceState.FailOperation); - CheckEnum($"{name}.DepthFailOperation", faceState.DepthFailOperation); - CheckEnum($"{name}.PassOperation", faceState.PassOperation); - CheckEnum($"{name}.CompareFunction", faceState.CompareFunction); + CheckEnum($"{name}.FailOp", faceState.FailOp); + CheckEnum($"{name}.DepthFailOp", faceState.DepthFailOp); + CheckEnum($"{name}.PassOp", faceState.PassOp); + CheckEnum($"{name}.CompareOp", faceState.CompareOp); } private void CheckColorAttachmentBlendState(string name, ColorAttachmentBlendState blendState) { - CheckEnum($"{name}.SourceRgbBlendFactor", blendState.SourceRgbBlendFactor); - CheckEnum($"{name}.DestinationRgbBlendFactor", blendState.DestinationRgbBlendFactor); - CheckEnum($"{name}.RgbBlendOperation", blendState.RgbBlendOperation); - CheckEnum($"{name}.SourceAlphaBlendFactor", blendState.SourceAlphaBlendFactor); - CheckEnum($"{name}.DestinationAlphaBlendFactor", blendState.DestinationAlphaBlendFactor); - CheckEnum($"{name}.AlphaBlendOperation", blendState.AlphaBlendOperation); + CheckEnum($"{name}.SrcRgbFactor", blendState.SrcRgbFactor); + CheckEnum($"{name}.DstRgbFactor", blendState.DstRgbFactor); + CheckEnum($"{name}.RgbOp", blendState.RgbOp); + CheckEnum($"{name}.SrcAlphaFactor", blendState.SrcAlphaFactor); + CheckEnum($"{name}.DstAlphaFactor", blendState.DstAlphaFactor); + CheckEnum($"{name}.AlphaOp", blendState.AlphaOp); CheckFlags($"{name}.ColorWrites", blendState.ColorWrites); } @@ -577,6 +1391,7 @@ private void CheckRayTracingGeometry(string name, RayTracingGeometry geometry) private void CheckRayTracingTriangleGeometry(string name, RayTracingTriangleGeometry triangleGeometry) { bool hasVertexBuffer = CheckResource($"{name}.VertexBuffer", triangleGeometry.VertexBuffer); + bool hasIndexBuffer = triangleGeometry.IndexBuffer is not null && CheckResource($"{name}.IndexBuffer", triangleGeometry.IndexBuffer); CheckEnum($"{name}.VertexFormat", triangleGeometry.VertexFormat); bool hasVertexCount = CheckGreaterThanZero($"{name}.VertexCount", triangleGeometry.VertexCount); @@ -592,8 +1407,6 @@ private void CheckRayTracingTriangleGeometry(string name, RayTracingTriangleGeom return; } - bool hasIndexBuffer = CheckResource($"{name}.IndexBuffer", triangleGeometry.IndexBuffer); - CheckEnum($"{name}.IndexFormat", triangleGeometry.IndexFormat); bool hasIndexCount = CheckGreaterThanZero($"{name}.IndexCount", triangleGeometry.IndexCount); @@ -646,12 +1459,22 @@ private void ReportWarning(string message) } } -file static class ValidationConstants +internal static class ValidationConstants { public const int CubeMapFaceCount = 6; public const int MaxAnisotropy = 16; + public const int MaxColorAttachments = 8; + + public const uint IndirectDrawArgsSizeInBytes = 16; + + public const uint IndirectDrawIndexedArgsSizeInBytes = 20; + + public const uint IndirectDispatchArgsSizeInBytes = 12; + + public const uint IndirectDispatchMeshArgsSizeInBytes = 12; + public const int IndexSizeUInt16 = 2; public const int IndexSizeUInt32 = 4; @@ -663,6 +1486,8 @@ file static class ValidationMessages { public const string MustNotBeNull = "{0} must not be null."; + public const string MustNotBeZero = "{0} must not be zero."; + public const string MustHaveExactlyNHandles = "{0} must have exactly {1} handles for {2}."; public const string MustBeValidHandle = "{0} must be a valid handle for {1}."; @@ -693,8 +1518,12 @@ file static class ValidationMessages public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; + public const string MustBeGreaterThanOrEqualTo = "{0} must be greater than or equal to {1}."; + public const string MustBeEqualTo = "{0} must be equal to {1}."; + public const string MustBeBetween = "{0} must be between {1} and {2}."; + public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; @@ -703,6 +1532,30 @@ file static class ValidationMessages public const string MustHaveFlag = "{0} must have the flag '{1}' set."; + public const string MustHaveUsage = "{0} must have usage flag '{1}'."; + + public const string MustHaveSameValue = "{0} must match. First value: '{1}', second value: '{2}'."; + + public const string MustBeWithinResourceBounds = "{0} must be within the bounds of {1}."; + + public const string MustNotBeDepthStencilFormat = "{0} must not use a depth/stencil format."; + + public const string MustBeDepthStencilFormat = "{0} must use a depth/stencil format."; + + public const string MustBeSingleSampled = "{0} must use SampleCount.Count1."; + + public const string MustBeMultisampled = "{0} must use a sample count greater than SampleCount.Count1."; + + public const string MustBeFinite = "{0} must be finite."; + + public const string MustHaveCurrentPipeline = "{0} requires a pipeline to be set."; + + public const string MustHaveCurrentPipelineType = "{0} requires current pipeline type {1}, but current pipeline is {2}."; + + public const string MustNotBeTimestampQuery = "{0} cannot be used with QueryType.Timestamp."; + + public const string MustBeTimestampQuery = "{0} requires QueryType.Timestamp."; + public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; From 45fd28c4a8edb05b35419ef83e147ff8d1e99456 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 19 May 2026 12:31:35 +0800 Subject: [PATCH 057/466] Improve DXIL handling and platform-specific logic Added platform-specific logic to handle DXIL compilation, including a `try-catch` block for missing DXIL compiler exceptions. Introduced `ShaderEntry` and `ShaderTest` record types for better data organization. Updated target expansion to use a new `CreateDefaultTargets` method, ensuring platform-appropriate defaults. Enhanced `PrintUsage` and `README.md` to clarify platform-specific behavior for the `all` target and DXIL requirements. --- .../Experiments/SlangResourceHeap/Program.cs | 38 +++++++++++++++++-- .../Experiments/SlangResourceHeap/README.md | 11 ++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs index 7da2f81d..c68f4bba 100644 --- a/sources/Experiments/SlangResourceHeap/Program.cs +++ b/sources/Experiments/SlangResourceHeap/Program.cs @@ -1,4 +1,5 @@ using Slangc.NET; +using System.Runtime.InteropServices; internal sealed record ShaderEntry(string Name, string Stage); @@ -163,8 +164,18 @@ .. targetConfig.Arguments Console.WriteLine($"[{targetConfig.Name}] {testCase.Name}::{entry.Name}"); - byte[] shaderBytes = SlangCompiler.Compile(compilerArguments); - File.WriteAllBytes(output, shaderBytes); + try + { + byte[] shaderBytes = SlangCompiler.Compile(compilerArguments); + File.WriteAllBytes(output, shaderBytes); + } + catch (Exception exception) when (IsMissingDxilCompiler(targetConfig, exception)) + { + throw new InvalidOperationException( + "DXIL compilation requires DXC/dxcompiler to be available to Slang. " + + "On macOS, run the SPIR-V or Metal-source targets for this experiment, or install DXC and make libdxcompiler loadable before running the dxil target.", + exception); + } } } } @@ -172,10 +183,17 @@ .. targetConfig.Arguments private static string[] ExpandTargets(string target) { return target.Equals("all", StringComparison.OrdinalIgnoreCase) - ? ["dxil", "spirv", "metal-source"] + ? CreateDefaultTargets() : [target]; } + private static string[] CreateDefaultTargets() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? ["dxil", "spirv", "metal-source"] + : ["spirv", "metal-source"]; + } + private static TargetConfig CreateTargetConfig(string target, bool useSpvDescriptorHeapExt) { return target.ToLowerInvariant() switch @@ -206,6 +224,19 @@ private static string[] CreateSpirvArguments(bool useSpvDescriptorHeapExt) return [.. arguments]; } + private static bool IsMissingDxilCompiler(TargetConfig targetConfig, Exception exception) + { + if (!targetConfig.Name.Equals("dxil", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + string message = exception.ToString(); + return message.Contains("dxc", StringComparison.OrdinalIgnoreCase) || + message.Contains("dxcompiler", StringComparison.OrdinalIgnoreCase) || + message.Contains("pass-through compiler", StringComparison.OrdinalIgnoreCase); + } + private static string FindProjectRoot() { foreach (string seed in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory }) @@ -228,5 +259,6 @@ private static string FindProjectRoot() private static void PrintUsage() { Console.WriteLine("Usage: dotnet run --project SlangResourceHeap.csproj -- [all|dxil|spirv|metal-source|metal] [--spv-descriptor-heap-ext] [--clean]"); + Console.WriteLine("Note: all includes dxil on Windows, and skips dxil on non-Windows hosts unless dxil is requested explicitly."); } } \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md index f62f2d72..a6b8ebcb 100644 --- a/sources/Experiments/SlangResourceHeap/README.md +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -51,9 +51,14 @@ dotnet run --project SlangResourceHeap.csproj -- metal ``` The `all` target compiles DXIL, SPIR-V, and Metal source. `metal-source` emits -MSL and does not require Apple's Metal compiler. `metal` emits `metallib` and is -intended for a machine with the Metal downstream compiler available. The project -writes outputs under `out/`. It does not run any host-side binding code. +MSL and does not require Apple's Metal compiler. On macOS and Linux, `all` skips +DXIL because Slang's DXIL target needs a loadable DXC / `dxcompiler` downstream +compiler. Run `dxil` explicitly only on a machine where DXC is installed and +loadable by Slang. + +The `metal` target emits `metallib` and is intended for a machine with the Metal +downstream compiler available. The project writes outputs under `out/`. It does +not run any host-side binding code. If `metal-source` succeeds but `metal` fails on a non-Apple machine, the missing piece is the platform Metal compiler rather than the CBO/ResourceHeap shader From 01b0d04b29755118aa96422ca37ea2aa45ec2578 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 19 May 2026 12:41:43 +0800 Subject: [PATCH 058/466] Default behavior for no args; macOS target updates Updated `CompileOptions` to set defaults when no arguments are supplied. Added a user notification for no-argument runs. Refactored `CreateDefaultTargets` to include macOS-specific `metal` target. Enhanced `PrintUsage` to clarify platform-specific targets. Updated README to document default behavior and platform-specific target details. --- .../Experiments/SlangResourceHeap/Program.cs | 26 ++++++++++++++----- .../Experiments/SlangResourceHeap/README.md | 26 ++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs index c68f4bba..dcc66ada 100644 --- a/sources/Experiments/SlangResourceHeap/Program.cs +++ b/sources/Experiments/SlangResourceHeap/Program.cs @@ -22,8 +22,8 @@ public static CompileOptions Parse(string[] arguments) { string target = "all"; bool targetSpecified = false; - bool useSpvDescriptorHeapExt = false; - bool clean = false; + bool useSpvDescriptorHeapExt = arguments.Length == 0; + bool clean = arguments.Length == 0; bool showHelp = false; for (int index = 0; index < arguments.Length; index++) @@ -118,6 +118,11 @@ private static int Main(string[] arguments) return 0; } + if (arguments.Length == 0) + { + Console.WriteLine("No arguments supplied; running all --clean --spv-descriptor-heap-ext."); + } + string projectRoot = FindProjectRoot(); string shaderDirectory = Path.Combine(projectRoot, "Shaders"); string outputRoot = Path.Combine(projectRoot, "out"); @@ -189,9 +194,17 @@ private static string[] ExpandTargets(string target) private static string[] CreateDefaultTargets() { - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? ["dxil", "spirv", "metal-source"] - : ["spirv", "metal-source"]; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return ["dxil", "spirv", "metal-source"]; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return ["spirv", "metal-source", "metal"]; + } + + return ["spirv", "metal-source"]; } private static TargetConfig CreateTargetConfig(string target, bool useSpvDescriptorHeapExt) @@ -259,6 +272,7 @@ private static string FindProjectRoot() private static void PrintUsage() { Console.WriteLine("Usage: dotnet run --project SlangResourceHeap.csproj -- [all|dxil|spirv|metal-source|metal] [--spv-descriptor-heap-ext] [--clean]"); - Console.WriteLine("Note: all includes dxil on Windows, and skips dxil on non-Windows hosts unless dxil is requested explicitly."); + Console.WriteLine("No arguments runs: all --clean --spv-descriptor-heap-ext."); + Console.WriteLine("Note: all includes dxil on Windows, metallib on macOS, and skips platform downstream targets elsewhere unless requested explicitly."); } } \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md index a6b8ebcb..c4e92047 100644 --- a/sources/Experiments/SlangResourceHeap/README.md +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -39,7 +39,14 @@ the NuGet `Slangc.NET` package instead of any `slangc.exe` that may appear on ## Compile -Use the C# project directly: +Start the C# project directly to run the default platform test matrix. No +arguments is equivalent to `all --clean --spv-descriptor-heap-ext`: + +```powershell +dotnet run --project SlangResourceHeap.csproj +``` + +Use explicit arguments when you want a narrower target: ```powershell dotnet run --project SlangResourceHeap.csproj -- all --clean @@ -50,11 +57,18 @@ dotnet run --project SlangResourceHeap.csproj -- metal-source dotnet run --project SlangResourceHeap.csproj -- metal ``` -The `all` target compiles DXIL, SPIR-V, and Metal source. `metal-source` emits -MSL and does not require Apple's Metal compiler. On macOS and Linux, `all` skips -DXIL because Slang's DXIL target needs a loadable DXC / `dxcompiler` downstream -compiler. Run `dxil` explicitly only on a machine where DXC is installed and -loadable by Slang. +The `all` target is host-aware: + +- Windows: DXIL, SPIR-V, and Metal source. +- macOS: SPIR-V, Metal source, and `metallib`. +- Linux: SPIR-V and Metal source. + +`metal-source` emits MSL and does not require Apple's Metal compiler. `metal` +emits `metallib` with `-target metallib -capability metallib_latest`, so it is +the target to use when validating the Apple Metal library path. On macOS and +Linux, `all` skips DXIL because Slang's DXIL target needs a loadable DXC / +`dxcompiler` downstream compiler. Run `dxil` explicitly only on a machine where +DXC is installed and loadable by Slang. The `metal` target emits `metallib` and is intended for a machine with the Metal downstream compiler available. The project writes outputs under `out/`. It does From 6e0751de54ebba7ee5fded9cb692da9787483514 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 19 May 2026 13:16:41 +0800 Subject: [PATCH 059/466] Add support for texture arrays and Metal 4.0 updates Added a new shader test case `04_texture_handle_array` to handle texture arrays with vertex and fragment shaders. Updated the `CompileTarget` method to log target configuration arguments for debugging. Modified the `metal` target to include the `-std=metal4.0` flag for Apple's Metal compiler. Updated the README.md to document the new shader test case and clarify the `metal` target's behavior with Metal 4.0 compatibility. Introduced `04_texture_handle_array.slang` to implement texture array handling with `TextureArrayConstants`, `vertexMain`, and `fragmentMain` functions. --- .../Experiments/SlangResourceHeap/Program.cs | 11 +++++- .../Experiments/SlangResourceHeap/README.md | 36 +++++++++++++++--- .../Shaders/04_texture_handle_array.slang | 38 +++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs index dcc66ada..64d47699 100644 --- a/sources/Experiments/SlangResourceHeap/Program.cs +++ b/sources/Experiments/SlangResourceHeap/Program.cs @@ -101,6 +101,13 @@ internal static class Program new( "03_nonuniform_material_texture", "03_nonuniform_material_texture.slang", + [ + new("vertexMain", "vertex"), + new("fragmentMain", "fragment") + ]), + new( + "04_texture_handle_array", + "04_texture_handle_array.slang", [ new("vertexMain", "vertex"), new("fragmentMain", "fragment") @@ -151,6 +158,8 @@ private static void CompileTarget(string shaderDirectory, string outputRoot, Tar string outputDirectory = Path.Combine(outputRoot, targetConfig.Name); Directory.CreateDirectory(outputDirectory); + Console.WriteLine($"[{targetConfig.Name}] args: {string.Join(' ', targetConfig.Arguments)}"); + foreach (ShaderTest testCase in TestCases) { string source = Path.Combine(shaderDirectory, testCase.File); @@ -214,7 +223,7 @@ private static TargetConfig CreateTargetConfig(string target, bool useSpvDescrip "dxil" => new("dxil", ".dxil", ["-target", "dxil", "-profile", "sm_6_6"]), "spirv" => new("spirv", ".spv", CreateSpirvArguments(useSpvDescriptorHeapExt)), "metal-source" => new("metal-source", ".metal", ["-target", "metal"]), - "metal" => new("metal", ".metallib", ["-target", "metallib", "-capability", "metallib_latest"]), + "metal" => new("metal", ".metallib", ["-target", "metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]), _ => throw new ArgumentException($"Unknown target '{target}'.") }; } diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md index c4e92047..358514c8 100644 --- a/sources/Experiments/SlangResourceHeap/README.md +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -36,6 +36,31 @@ the NuGet `Slangc.NET` package instead of any `slangc.exe` that may appear on - Fragment shader reads a material record from a structured buffer handle. Each material contains a texture handle, and the shader uses `nonuniform` before dereferencing it. +4. `Shaders/04_texture_handle_array.slang` + - Fragment shader reads a texture handle from a structured buffer of texture + handles, then samples it with `nonuniform(handle)`. + +## Many Textures + +For many textures, shader code should not use an untyped `heap[index]` expression +directly. The portable Slang shape is to store typed handles in ordinary data, +such as a constant buffer, material buffer, or a structured buffer of handles: + +```hlsl +struct DrawConstants +{ + DescriptorHandle>>> Textures; + DescriptorHandle LinearSampler; + uint TextureIndex; +}; + +DescriptorHandle> textureHandle = (*draw.Textures)[draw.TextureIndex]; +float4 color = nonuniform(textureHandle)->Sample(*draw.LinearSampler, uv); +``` + +The host-side `ResourceHeap` owns the actual descriptors. The shader receives +typed descriptor handles or buffers of typed descriptor handles, and Slang lowers +the handle dereference to the target's heap or bindless descriptor mechanism. ## Compile @@ -64,11 +89,12 @@ The `all` target is host-aware: - Linux: SPIR-V and Metal source. `metal-source` emits MSL and does not require Apple's Metal compiler. `metal` -emits `metallib` with `-target metallib -capability metallib_latest`, so it is -the target to use when validating the Apple Metal library path. On macOS and -Linux, `all` skips DXIL because Slang's DXIL target needs a loadable DXC / -`dxcompiler` downstream compiler. Run `dxil` explicitly only on a machine where -DXC is installed and loadable by Slang. +emits `metallib` with `-target metallib -capability metallib_latest` and passes +`-std=metal4.0` to Apple's downstream `metal` compiler, so it is the target to +use when validating the Apple Metal 4 library path. On macOS and Linux, `all` +skips DXIL because Slang's DXIL target needs a loadable DXC / `dxcompiler` +downstream compiler. Run `dxil` explicitly only on a machine where DXC is +installed and loadable by Slang. The `metal` target emits `metallib` and is intended for a machine with the Metal downstream compiler available. The project writes outputs under `out/`. It does diff --git a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang new file mode 100644 index 00000000..01e60605 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang @@ -0,0 +1,38 @@ +struct TextureArrayConstants +{ + DescriptorHandle>>> Textures; + DescriptorHandle LinearSampler; + uint TextureCount; + float UvScale; +}; + +struct FullscreenVertex +{ + float4 Position : SV_POSITION; + float2 Uv : TEXCOORD0; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer gTextures; + +[shader("vertex")] +FullscreenVertex vertexMain(uint vertexId : SV_VertexID) +{ + float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); + + FullscreenVertex output; + output.Position = float4(position, 0.0, 1.0); + output.Uv = position * 0.5 + 0.5; + + return output; +} + +[shader("fragment")] +float4 fragmentMain(FullscreenVertex input) : SV_TARGET +{ + uint count = max(gTextures.TextureCount, 1u); + uint textureIndex = min(uint(input.Uv.x * float(count)), count - 1u); + DescriptorHandle> textureHandle = (*gTextures.Textures)[textureIndex]; + + return nonuniform(textureHandle)->Sample(*gTextures.LinearSampler, input.Uv * gTextures.UvScale); +} \ No newline at end of file From f85bcf0e5dac4b14d3aa5e330d15a8be27a31e9c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 19 May 2026 13:20:25 +0800 Subject: [PATCH 060/466] Refactor texture handling in shaders Updated README to reference NuGet `Slangc.NET` package. Refactored shaders to use `TextureRecord` structs for texture handles, ensuring compatibility with Metal. Updated `03_nonuniform_material_texture.slang` and `04_texture_handle_array.slang` to use structured buffers of records. Improved code clarity and portability. --- .../Experiments/SlangResourceHeap/README.md | 24 ++++++++++++++----- .../Shaders/04_texture_handle_array.slang | 11 ++++++--- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md index 358514c8..410c5ad5 100644 --- a/sources/Experiments/SlangResourceHeap/README.md +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -37,27 +37,39 @@ the NuGet `Slangc.NET` package instead of any `slangc.exe` that may appear on Each material contains a texture handle, and the shader uses `nonuniform` before dereferencing it. 4. `Shaders/04_texture_handle_array.slang` - - Fragment shader reads a texture handle from a structured buffer of texture - handles, then samples it with `nonuniform(handle)`. + - Fragment shader reads a record containing a texture handle from a structured + buffer, then samples it with `nonuniform(handle)`. ## Many Textures For many textures, shader code should not use an untyped `heap[index]` expression directly. The portable Slang shape is to store typed handles in ordinary data, -such as a constant buffer, material buffer, or a structured buffer of handles: +such as a constant buffer, material buffer, or a structured buffer of records +that contain handles: ```hlsl +struct TextureRecord +{ + DescriptorHandle> Texture; +}; + struct DrawConstants { - DescriptorHandle>>> Textures; + DescriptorHandle> Textures; DescriptorHandle LinearSampler; uint TextureIndex; }; -DescriptorHandle> textureHandle = (*draw.Textures)[draw.TextureIndex]; -float4 color = nonuniform(textureHandle)->Sample(*draw.LinearSampler, uv); +TextureRecord textureRecord = (*draw.Textures)[draw.TextureIndex]; +float4 color = nonuniform(textureRecord.Texture)->Sample(*draw.LinearSampler, uv); ``` +Wrapping each texture handle in a record is intentional. DXIL and SPIR-V can +compile a raw `StructuredBuffer>>`, but Apple's +Metal compiler rejects the generated `texture2d device*` buffer-pointee type. +The record form matches material-buffer usage and is accepted by the Metal 4 +`metallib` path. + The host-side `ResourceHeap` owns the actual descriptors. The shader receives typed descriptor handles or buffers of typed descriptor handles, and Slang lowers the handle dereference to the target's heap or bindless descriptor mechanism. diff --git a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang index 01e60605..3f8ccd30 100644 --- a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang +++ b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang @@ -1,6 +1,11 @@ +struct TextureRecord +{ + DescriptorHandle> Texture; +}; + struct TextureArrayConstants { - DescriptorHandle>>> Textures; + DescriptorHandle> Textures; DescriptorHandle LinearSampler; uint TextureCount; float UvScale; @@ -32,7 +37,7 @@ float4 fragmentMain(FullscreenVertex input) : SV_TARGET { uint count = max(gTextures.TextureCount, 1u); uint textureIndex = min(uint(input.Uv.x * float(count)), count - 1u); - DescriptorHandle> textureHandle = (*gTextures.Textures)[textureIndex]; + TextureRecord textureRecord = (*gTextures.Textures)[textureIndex]; - return nonuniform(textureHandle)->Sample(*gTextures.LinearSampler, input.Uv * gTextures.UvScale); + return nonuniform(textureRecord.Texture)->Sample(*gTextures.LinearSampler, input.Uv * gTextures.UvScale); } \ No newline at end of file From 528fede4f15cea414192d942440595ef978dd7fe Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 19 May 2026 22:28:12 +0800 Subject: [PATCH 061/466] Refactor shaders and improve Metal backend support Updated `Program.cs` to add `CreateTargetDefines` for modular target-specific defines. Refactored `CreateTargetConfig` to use these defines. Renamed project in `README.md` to "Zenith DescriptorHandle Minimal Tests" and expanded documentation on `DescriptorHandle` usage, backend policies, and Metal-specific runtime handling. Refactored shaders to use explicit `register` bindings, replaced pointer dereferencing with array indexing, and aligned `DescriptorHandle` usage. Removed `TextureRecord` in `04_texture_handle_array.slang` for direct handle usage. Enhanced test cases and clarified Metal API implications for resource binding. Improved backend-aware handling for `SetConstants` to ensure compatibility across DX12/Vulkan and Metal. --- .../Experiments/SlangResourceHeap/Program.cs | 20 +- .../Experiments/SlangResourceHeap/README.md | 296 ++++++++++++++---- .../Shaders/01_cbo_texture_sampler.slang | 4 +- .../Shaders/02_cbo_buffer_uav.slang | 4 +- .../03_nonuniform_material_texture.slang | 7 +- .../Shaders/04_texture_handle_array.slang | 14 +- 6 files changed, 271 insertions(+), 74 deletions(-) diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs index 64d47699..8276484d 100644 --- a/sources/Experiments/SlangResourceHeap/Program.cs +++ b/sources/Experiments/SlangResourceHeap/Program.cs @@ -170,6 +170,7 @@ private static void CompileTarget(string shaderDirectory, string outputRoot, Tar string[] compilerArguments = [ source, + "-I", shaderDirectory, "-entry", entry.Name, "-stage", entry.Stage, "-matrix-layout-row-major", @@ -220,10 +221,21 @@ private static TargetConfig CreateTargetConfig(string target, bool useSpvDescrip { return target.ToLowerInvariant() switch { - "dxil" => new("dxil", ".dxil", ["-target", "dxil", "-profile", "sm_6_6"]), - "spirv" => new("spirv", ".spv", CreateSpirvArguments(useSpvDescriptorHeapExt)), - "metal-source" => new("metal-source", ".metal", ["-target", "metal"]), - "metal" => new("metal", ".metallib", ["-target", "metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]), + "dxil" => new("dxil", ".dxil", [.. CreateTargetDefines("dxil"), "-target", "dxil", "-profile", "sm_6_6"]), + "spirv" => new("spirv", ".spv", [.. CreateTargetDefines("spirv"), .. CreateSpirvArguments(useSpvDescriptorHeapExt)]), + "metal-source" => new("metal-source", ".metal", [.. CreateTargetDefines("metal"), "-target", "metal"]), + "metal" => new("metal", ".metallib", [.. CreateTargetDefines("metal"), "-target", "metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]), + _ => throw new ArgumentException($"Unknown target '{target}'.") + }; + } + + private static string[] CreateTargetDefines(string target) + { + return target.ToLowerInvariant() switch + { + "dxil" => ["-DZENITH_TARGET_DX=1", "-DZENITH_TARGET_SPIRV=0", "-DZENITH_TARGET_METAL=0"], + "spirv" => ["-DZENITH_TARGET_DX=0", "-DZENITH_TARGET_SPIRV=1", "-DZENITH_TARGET_METAL=0"], + "metal" => ["-DZENITH_TARGET_DX=0", "-DZENITH_TARGET_SPIRV=0", "-DZENITH_TARGET_METAL=1"], _ => throw new ArgumentException($"Unknown target '{target}'.") }; } diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md index 410c5ad5..d8ab00f3 100644 --- a/sources/Experiments/SlangResourceHeap/README.md +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -1,78 +1,267 @@ -# Slang ResourceHeap Minimal Tests +# Zenith DescriptorHandle Minimal Tests This folder is independent from the Zenith.NET runtime projects. It contains a -small C# console app that references `Slangc.NET` directly, plus three Slang -shader files. The goal is to test whether the proposed public model can be -written as: +small C# console app that references `Slangc.NET` directly, plus Slang shader +files. The current experiment follows the Slang-official `DescriptorHandle` +model instead of a Zenith-owned shader compatibility layer. + +The proposed public shape is: ```text -ConstantBuffer + ResourceHeap + SamplerHeap +ConstantBuffer + DescriptorHandle + backend-specific binding API ``` -The shaders rely on Slang `DescriptorHandle`. Compilation is driven through -the NuGet `Slangc.NET` package instead of any `slangc.exe` that may appear on -`PATH`. +Compilation is driven through the NuGet `Slangc.NET` package instead of any +`slangc.exe` that may appear on `PATH`. -## Binding Contract +## Route A Contract -- One `ConstantBuffer` containing ordinary parameters and typed handles. - Vulkan pins it to set 0 binding 0; DXIL lets Slang auto-assign the first CBO. -- Resource heap: descriptors referenced by `DescriptorHandle>`, - `DescriptorHandle>`, and - `DescriptorHandle>`. -- Sampler heap: descriptors referenced by `DescriptorHandle`. -- Vulkan uses `-bindless-space-index 100` so Slang's global bindless arrays do - not collide with app set 0 in these tests. -- DXIL uses `sm_6_6`, which is required for direct heap indexing. +- Shader code uses Slang's native `DescriptorHandle` type directly. +- DXIL and SPIR-V may treat descriptor handles as integer descriptor slots. In + practice, Slang's default descriptor-handle dereference logic lowers to + direct heap indexing or bindless descriptor arrays. +- Metal must not be forced into the same integer ABI. Slang intentionally lowers + `DescriptorHandle` to the native layout of `T` on Metal: -## Test Cases + ```text + DescriptorHandle> -> texture2d + DescriptorHandle -> sampler + DescriptorHandle> -> device T* + StructuredBuffer>> -> texture2d<...> device* + ``` -1. `Shaders/01_cbo_texture_sampler.slang` - - Fragment shader samples a texture handle and sampler handle stored in CBO. -2. `Shaders/02_cbo_buffer_uav.slang` - - Compute shader reads a structured buffer handle and writes an RW structured - buffer handle stored in CBO. -3. `Shaders/03_nonuniform_material_texture.slang` - - Fragment shader reads a material record from a structured buffer handle. - Each material contains a texture handle, and the shader uses `nonuniform` - before dereferencing it. -4. `Shaders/04_texture_handle_array.slang` - - Fragment shader reads a record containing a texture handle from a structured - buffer, then samples it with `nonuniform(handle)`. +- Therefore the Metal backend should handle compatibility in the runtime API, + not in shader source. Metal should follow reflection and bind native resources + or fill Metal argument buffers, similar to Slang-RHI. -## Many Textures +The important design split is: -For many textures, shader code should not use an untyped `heap[index]` expression -directly. The portable Slang shape is to store typed handles in ordinary data, -such as a constant buffer, material buffer, or a structured buffer of records -that contain handles: +```text +DX12/Vulkan: + resource.GetDescriptorHandle() -> integer slot -> setDescriptorHandle() -```hlsl -struct TextureRecord +Metal: + resource binding / ParameterBlock / argument buffer -> native resource id or gpu address +``` + +## Official Slang-RHI Behavior + +Slang-RHI exposes a host-side descriptor handle type: + +```cpp +struct DescriptorHandle { - DescriptorHandle> Texture; + DescriptorHandleType type; + uint64_t value; }; +``` + +On D3D12 and Vulkan, `getDescriptorHandle` allocates a bindless descriptor slot +and `setDescriptorHandle` writes the 8-byte `value` into shader object uniform +data. + +On Metal, resource `getDescriptorHandle` is not implemented. The official API +support table marks Metal as unsupported for: + +- `IBuffer::getDescriptorHandle` +- `ITextureView::getDescriptorHandle` +- `ISampler::getDescriptorHandle` +- `IAccelerationStructure::getDescriptorHandle` + +The Metal backend instead binds native resources. Its argument-buffer path writes +Metal `gpuResourceID()` values for textures, samplers, and acceleration +structures, and writes `gpuAddress()` values for buffers. If residency sets are +not available, it tracks resources for per-encoder `useResource` fallback. + +Zenith should mirror that policy for Route A: keep shader code official, and let +the Metal API layer translate application resource bindings to Metal-native +layout. -struct DrawConstants +## Shader Usage + +Use `DescriptorHandle` in parameter structs, then let Slang implicitly +dereference handles when a resource value is needed. The official docs state +that `getDescriptorFromHandle` is not supposed to be called from user code +directly; it is a customization hook that an engine can provide to override the +default dereference behavior. + +```hlsl +struct DrawParams { - DescriptorHandle> Textures; + DescriptorHandle> BaseColorTexture; DescriptorHandle LinearSampler; - uint TextureIndex; + float4 Tint; }; -TextureRecord textureRecord = (*draw.Textures)[draw.TextureIndex]; -float4 color = nonuniform(textureRecord.Texture)->Sample(*draw.LinearSampler, uv); +ConstantBuffer gDraw : register(b0); + +float4 fragmentMain(float2 uv : TEXCOORD0) : SV_Target +{ + return gDraw.BaseColorTexture->Sample(gDraw.LinearSampler, uv) * gDraw.Tint; +} +``` + +When a descriptor handle value is loaded through a per-thread-varying path, mark +it non-uniform immediately before dereferencing: + +```hlsl +DescriptorHandle> handle = materials[index].BaseColorTexture; +float4 color = nonuniform(handle)->Sample(sampler, uv); ``` -Wrapping each texture handle in a record is intentional. DXIL and SPIR-V can -compile a raw `StructuredBuffer>>`, but Apple's -Metal compiler rejects the generated `texture2d device*` buffer-pointee type. -The record form matches material-buffer usage and is accepted by the Metal 4 -`metallib` path. +For buffer resources, the handle can be used as if it were the underlying +buffer: + +```hlsl +gBuffers.Destination[index] = gBuffers.Source[index] * scale; +``` + +## Resource Binding API Direction + +The C# side should expose a single backend-neutral `ResourceHandle` value as the +host counterpart of shader-side `DescriptorHandle`. It is not a custom Slang +shader type; shaders should keep using the official `DescriptorHandle`. + +```csharp +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential)] +public struct ResourceHandle +{ + public uint X; + public uint Y; +} + +public abstract class Buffer +{ + public ResourceHandle UniformHandle { get; } + public ResourceHandle StorageReadOnlyHandle { get; } + public ResourceHandle StorageReadWriteHandle { get; } +} + + public abstract class BufferView + { + public ResourceHandle UniformHandle { get; } + public ResourceHandle StorageReadOnlyHandle { get; } + public ResourceHandle StorageReadWriteHandle { get; } + } + +public abstract class Texture +{ + public ResourceHandle SampledHandle { get; } + public ResourceHandle StorageHandle { get; } +} + + public abstract class TextureView + { + public ResourceHandle SampledHandle { get; } + public ResourceHandle StorageHandle { get; } + } + +public abstract class Sampler +{ + public ResourceHandle Handle { get; } +} +``` + +Draw constants can then stay resource-oriented and backend-neutral: + +```csharp +public struct DrawParams +{ + public Matrix4x4 World; + public Matrix4x4 ViewProjection; + public ResourceHandle MaterialBuffer; + public ResourceHandle BaseColor; + public ResourceHandle LinearSampler; + public uint MaterialIndex; +} + +cmd.SetPipeline(pipeline); +cmd.SetConstants(new DrawParams +{ + World = world, + ViewProjection = camera.ViewProjection, + MaterialBuffer = materialBuffer.StorageReadOnlyHandle, + BaseColor = texture.SampledHandle, + LinearSampler = sampler.Handle, + MaterialIndex = materialIndex, +}); +cmd.DrawIndexed(indexCount); +``` + +Backend policy: + +- DX12/Vulkan: `ResourceHandle` stores descriptor-heap indexes, so + `SetConstants` can copy the struct bytes directly into uniform data. +- Metal: `SetConstants` must be reflection-aware. It copies ordinary POD + fields, resolves `ResourceHandle` fields to native Metal resources, and writes + those resources into a command/frame argument table. The user-facing C# struct + stays the same, but Metal does not upload the `ResourceHandle` bytes as the + final shader ABI. +- For CPU-authored buffers that contain `ResourceHandle` values, provide a + backend-aware upload/packing path instead of raw byte upload on Metal. Large + material systems should usually store resource indices and bind resource arrays + or argument tables explicitly. + +This keeps the public API simple while preserving the official Slang rule: +shader source uses `DescriptorHandle`, and backend-specific ABI work happens +in the runtime binding layer. + +## Metal API Implications + +The experiment still compiles Metal source for `DescriptorHandle` so we can +inspect Slang's generated MSL. That does not mean Zenith should upload the same +CPU byte layout to Metal. + +For Metal, Zenith should build a pipeline binding manifest from Slang +reflection: + +- Ordinary POD fields are copied into Metal constant data. +- Texture fields are bound as `MTLTexture` or written as argument-buffer resource + IDs. +- Sampler fields are bound as `MTLSamplerState` or written as argument-buffer + resource IDs. +- Buffer fields are bound as `MTLBuffer`/offset or written as GPU addresses. +- Pointer/resource fields referenced from ordinary data must be included in + residency tracking. + +That means a high-level call can stay simple: + +```csharp +cmd.SetPipeline(pipeline); +cmd.SetConstants(drawParams); +cmd.DrawIndexed(...); +``` + +but `SetConstants` is backend-aware: + +- DX12/Vulkan encode descriptor slot integers into the uniform data. +- Metal resolves resource references and populates native bindings or argument + buffers according to reflection. + +## Test Cases + +1. `Shaders/01_cbo_texture_sampler.slang` + - Fragment shader samples a `DescriptorHandle>` and + `DescriptorHandle` stored in a CBO. +2. `Shaders/02_cbo_buffer_uav.slang` + - Compute shader reads a `DescriptorHandle>` and + writes a `DescriptorHandle>` stored in a CBO. +3. `Shaders/03_nonuniform_material_texture.slang` + - Fragment shader reads a material record from a structured-buffer handle. + Each material contains a `DescriptorHandle>`. +4. `Shaders/04_texture_handle_array.slang` + - Fragment shader reads a descriptor handle directly from a structured buffer + of texture handles: + `DescriptorHandle>>>`. -The host-side `ResourceHeap` owns the actual descriptors. The shader receives -typed descriptor handles or buffers of typed descriptor handles, and Slang lowers -the handle dereference to the target's heap or bindless descriptor mechanism. +The fourth case is intentionally a stress case. It is natural on DX12/Vulkan, +but Metal lowers the nested texture handles to native texture-resource layout. +Zenith should treat this as a feature to inspect, not necessarily the preferred +Metal data model. For large material systems, a more Metal-friendly production +model is often `uint` material texture indices plus API-bound texture arrays or +argument buffers. ## Compile @@ -113,8 +302,7 @@ downstream compiler available. The project writes outputs under `out/`. It does not run any host-side binding code. If `metal-source` succeeds but `metal` fails on a non-Apple machine, the missing -piece is the platform Metal compiler rather than the CBO/ResourceHeap shader -syntax. +piece is the platform Metal compiler rather than the shader syntax. This experiment uses the repository's central `Slangc.NET` package version, so updating `sources/Directory.Packages.props` is enough to move the compiler used diff --git a/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang b/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang index 15fa3e13..89af3d2d 100644 --- a/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang +++ b/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang @@ -12,7 +12,7 @@ struct FullscreenVertex }; [[vk::binding(0, 0)]] -ConstantBuffer gPass; +ConstantBuffer gPass : register(b0); [shader("vertex")] FullscreenVertex vertexMain(uint vertexId : SV_VertexID) @@ -29,5 +29,5 @@ FullscreenVertex vertexMain(uint vertexId : SV_VertexID) [shader("fragment")] float4 fragmentMain(FullscreenVertex input) : SV_TARGET { - return (*gPass.AlbedoTexture).Sample(*gPass.LinearSampler, input.Uv) * gPass.Tint; + return gPass.AlbedoTexture->Sample(gPass.LinearSampler, input.Uv) * gPass.Tint; } \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang b/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang index 7a427d10..b6eb8fbb 100644 --- a/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang +++ b/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang @@ -7,7 +7,7 @@ struct BufferConstants }; [[vk::binding(0, 0)]] -ConstantBuffer gBuffers; +ConstantBuffer gBuffers : register(b0); [shader("compute")] [numthreads(64, 1, 1)] @@ -19,5 +19,5 @@ void computeMain(uint3 dispatchThreadId : SV_DispatchThreadID) return; } - (*gBuffers.Destination)[index] = (*gBuffers.Source)[index] * gBuffers.Scale; + gBuffers.Destination[index] = gBuffers.Source[index] * gBuffers.Scale; } \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang b/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang index ee6a03b0..d78edcf6 100644 --- a/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang +++ b/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang @@ -19,7 +19,7 @@ struct FullscreenVertex }; [[vk::binding(0, 0)]] -ConstantBuffer gMaterials; +ConstantBuffer gMaterials : register(b0); [shader("vertex")] FullscreenVertex vertexMain(uint vertexId : SV_VertexID) @@ -38,7 +38,8 @@ float4 fragmentMain(FullscreenVertex input) : SV_TARGET { uint count = max(gMaterials.MaterialCount, 1u); uint materialIndex = min(uint(input.Uv.x * float(count)), count - 1u); - MaterialRecord material = (*gMaterials.Materials)[materialIndex]; + MaterialRecord material = gMaterials.Materials[materialIndex]; + SamplerState linearSampler = gMaterials.LinearSampler; - return nonuniform(material.BaseColorTexture)->Sample(*gMaterials.LinearSampler, input.Uv * gMaterials.UvScale) * material.Tint; + return nonuniform(material.BaseColorTexture)->Sample(linearSampler, input.Uv * gMaterials.UvScale) * material.Tint; } \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang index 3f8ccd30..0540eaee 100644 --- a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang +++ b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang @@ -1,11 +1,6 @@ -struct TextureRecord -{ - DescriptorHandle> Texture; -}; - struct TextureArrayConstants { - DescriptorHandle> Textures; + DescriptorHandle>>> Textures; DescriptorHandle LinearSampler; uint TextureCount; float UvScale; @@ -18,7 +13,7 @@ struct FullscreenVertex }; [[vk::binding(0, 0)]] -ConstantBuffer gTextures; +ConstantBuffer gTextures : register(b0); [shader("vertex")] FullscreenVertex vertexMain(uint vertexId : SV_VertexID) @@ -37,7 +32,8 @@ float4 fragmentMain(FullscreenVertex input) : SV_TARGET { uint count = max(gTextures.TextureCount, 1u); uint textureIndex = min(uint(input.Uv.x * float(count)), count - 1u); - TextureRecord textureRecord = (*gTextures.Textures)[textureIndex]; + DescriptorHandle> textureHandle = gTextures.Textures[textureIndex]; + SamplerState linearSampler = gTextures.LinearSampler; - return nonuniform(textureRecord.Texture)->Sample(*gTextures.LinearSampler, input.Uv * gTextures.UvScale); + return nonuniform(textureHandle)->Sample(linearSampler, input.Uv * gTextures.UvScale); } \ No newline at end of file From 5018541fc71eaf68a7eeeba5b15893b7e7c9c4b6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 14:05:11 +0800 Subject: [PATCH 062/466] Add reflection dump target and AOT binding model sketch - Add "reflection" target to CLI for dumping Slang reflection JSON and member info for new test shaders. - Add 05_fixed_texture_array and 06_flattened_textures shader cases to test fixed arrays and flattened resource fields. - Update test matrix and CLI usage to support the new target. - Expand README with detailed AOT binding generation docs, including per-backend layout, Metal limitations, and code sketches. - Add ConstantsBindingSketch.cs with C# types, attributes, and interfaces for AOT-safe constants binding and resource handle arrays. --- .../ConstantsBindingSketch.cs | 208 ++++++++++ .../Experiments/SlangResourceHeap/Program.cs | 134 ++++++- .../Experiments/SlangResourceHeap/README.md | 378 +++++++++++++++++- .../Shaders/05_fixed_texture_array.slang | 39 ++ .../Shaders/06_flattened_textures.slang | 54 +++ 5 files changed, 803 insertions(+), 10 deletions(-) create mode 100644 sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs create mode 100644 sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang create mode 100644 sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang diff --git a/sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs b/sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs new file mode 100644 index 00000000..ab7f4e4f --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs @@ -0,0 +1,208 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SlangResourceHeap.BindingModel; + +[AttributeUsage(AttributeTargets.Struct, Inherited = false)] +public sealed class ConstantsAttribute : Attribute +{ +} + +[AttributeUsage(AttributeTargets.Struct, Inherited = false)] +public sealed class ResourceHandleArrayAttribute(int length) : Attribute +{ + public int Length { get; } = length; +} + +[StructLayout(LayoutKind.Sequential)] +public struct ResourceHandle +{ + public uint X; + + public uint Y; +} + +[ResourceHandleArray(4)] +public partial struct TextureHandle4 +{ +} + +[InlineArray(4)] +public partial struct TextureHandle4 +{ + private ResourceHandle element0; +} + +public enum ResourceKind +{ + Buffer, + + Texture, + + Sampler, + + AccelerationStructure +} + +public enum ResourceAccess +{ + Read, + + ReadWrite +} + +public sealed class ConstantsLayout(int sizeInBytes, ResourceFieldLayout[] resourceFields) +{ + public int SizeInBytes { get; } = sizeInBytes; + + public ResourceFieldLayout[] ResourceFields { get; } = resourceFields; +} + +public sealed class ResourceFieldLayout( + string name, + ResourceKind kind, + ResourceAccess access, + int offsetInBytes, + int metalIndex, + int count = 1, + int strideInBytes = 0, + ConstantsLayout? elementLayout = null) +{ + public string Name { get; } = name; + + public ResourceKind Kind { get; } = kind; + + public ResourceAccess Access { get; } = access; + + public int OffsetInBytes { get; } = offsetInBytes; + + public int MetalIndex { get; } = metalIndex; + + public int Count { get; } = count; + + public int StrideInBytes { get; } = strideInBytes; + + public ConstantsLayout? ElementLayout { get; } = elementLayout; +} + +public interface IConstantsLayout + where TSelf : unmanaged, IConstantsLayout +{ + static abstract ConstantsLayout GetLayout(); +} + +public sealed class CommandBufferSketch +{ + public void SetConstants(in TConstants constants) + where TConstants : unmanaged, IConstantsLayout + { + ConstantsLayout layout = TConstants.GetLayout(); + + _ = constants; + _ = layout; + } +} + +[Constants] +public partial struct DrawParams +{ + public Matrix4x4 World; + + public Matrix4x4 ViewProjection; + + public ResourceHandle Buffers; + + public ResourceHandle BaseColor; + + public ResourceHandle LinearSampler; + + public uint MaterialIndex; +} + +public partial struct DrawParams : IConstantsLayout +{ + public static ConstantsLayout GetLayout() => DrawParamsLayout.Value; +} + +file static class DrawParamsLayout +{ + public static readonly ConstantsLayout Value = new( + sizeInBytes: 160, + resourceFields: + [ + new("Buffers", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 128, metalIndex: 0), + new("BaseColor", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 136, metalIndex: 0), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 144, metalIndex: 0), + ]); +} + +[Constants] +public partial struct TextureIndexParams +{ + public ResourceHandle TextureIndices; + + public ResourceHandle LinearSampler; + + public uint TextureIndex; +} + +public partial struct TextureIndexParams : IConstantsLayout +{ + public static ConstantsLayout GetLayout() => TextureIndexParamsLayout.Value; +} + +file static class TextureIndexParamsLayout +{ + public static readonly ConstantsLayout Value = new( + sizeInBytes: 32, + resourceFields: + [ + new("TextureIndices", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 8, metalIndex: 0), + ]); +} + +[Constants] +public partial struct FixedTextureArrayParams +{ + public TextureHandle4 Textures; + + public ResourceHandle LinearSampler; + + public uint TextureIndex; + + public float UvScale; +} + +public partial struct FixedTextureArrayParams : IConstantsLayout +{ + public static ConstantsLayout GetLayout() => FixedTextureArrayParamsLayout.Spirv; +} + +file static class FixedTextureArrayParamsLayout +{ + public static readonly ConstantsLayout Dxil = new( + sizeInBytes: 72, + resourceFields: + [ + new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4, strideInBytes: 16), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 56, metalIndex: 0), + ]); + + public static readonly ConstantsLayout Spirv = new( + sizeInBytes: 80, + resourceFields: + [ + new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4, strideInBytes: 16), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 64, metalIndex: 0), + ]); + + public static readonly ConstantsLayout Metal = new( + sizeInBytes: 8, + resourceFields: + [ + new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 32, metalIndex: 0), + ]); +} diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs index 8276484d..3bc37017 100644 --- a/sources/Experiments/SlangResourceHeap/Program.cs +++ b/sources/Experiments/SlangResourceHeap/Program.cs @@ -1,5 +1,6 @@ using Slangc.NET; using System.Runtime.InteropServices; +using System.Text.Json; internal sealed record ShaderEntry(string Name, string Stage); @@ -15,7 +16,8 @@ internal sealed record CompileOptions(string Target, bool UseSpvDescriptorHeapEx "dxil", "spirv", "metal-source", - "metal" + "metal", + "reflection" }; public static CompileOptions Parse(string[] arguments) @@ -108,6 +110,20 @@ internal static class Program new( "04_texture_handle_array", "04_texture_handle_array.slang", + [ + new("vertexMain", "vertex"), + new("fragmentMain", "fragment") + ]), + new( + "05_fixed_texture_array", + "05_fixed_texture_array.slang", + [ + new("vertexMain", "vertex"), + new("fragmentMain", "fragment") + ]), + new( + "06_flattened_textures", + "06_flattened_textures.slang", [ new("vertexMain", "vertex"), new("fragmentMain", "fragment") @@ -139,6 +155,12 @@ private static int Main(string[] arguments) Directory.Delete(outputRoot, recursive: true); } + if (options.Target.Equals("reflection", StringComparison.OrdinalIgnoreCase)) + { + DumpReflection(shaderDirectory, outputRoot, options.UseSpvDescriptorHeapExt); + return 0; + } + foreach (string target in ExpandTargets(options.Target)) { CompileTarget(shaderDirectory, outputRoot, CreateTargetConfig(target, options.UseSpvDescriptorHeapExt)); @@ -153,6 +175,114 @@ private static int Main(string[] arguments) } } + private static void DumpReflection(string shaderDirectory, string outputRoot, bool useSpvDescriptorHeapExt) + { + string outputDirectory = Path.Combine(outputRoot, "reflection"); + Directory.CreateDirectory(outputDirectory); + + foreach (ShaderTest testCase in TestCases.Where(static item => item.Name is "05_fixed_texture_array" or "06_flattened_textures")) + { + ShaderEntry entry = testCase.Entries.Single(static item => item.Name == "fragmentMain"); + string source = Path.Combine(shaderDirectory, testCase.File); + + foreach (string target in new[] { "dxil", "spirv", "metal-source" }) + { + TargetConfig targetConfig = CreateTargetConfig(target, useSpvDescriptorHeapExt); + string[] compilerArguments = + [ + source, + "-I", shaderDirectory, + "-entry", entry.Name, + "-stage", entry.Stage, + "-matrix-layout-row-major", + .. targetConfig.Arguments + ]; + + Console.WriteLine($"[reflection:{targetConfig.Name}] {testCase.Name}::{entry.Name}"); + + byte[] shaderBytes = SlangCompiler.CompileWithReflection(compilerArguments, out SlangReflection reflection); + string outputName = $"{testCase.Name}.{targetConfig.Name}"; + File.WriteAllText(Path.Combine(outputDirectory, $"{outputName}.members.txt"), $"ShaderBytes: {shaderBytes.Length}" + Environment.NewLine + DescribeObject(reflection)); + + if (TryGetReflectionJson(reflection, out string reflectionJson)) + { + File.WriteAllText(Path.Combine(outputDirectory, $"{outputName}.reflection.json"), PrettyJson(reflectionJson)); + } + } + } + } + + private static string DescribeObject(object value) + { + Type type = value.GetType(); + List lines = [$"Type: {type.FullName}"]; + + foreach (System.Reflection.PropertyInfo property in type.GetProperties()) + { + object? propertyValue = property.GetValue(value); + lines.Add($"Property {property.Name}: {FormatMemberValue(propertyValue)}"); + } + + foreach (System.Reflection.FieldInfo field in type.GetFields()) + { + object? fieldValue = field.GetValue(value); + lines.Add($"Field {field.Name}: {FormatMemberValue(fieldValue)}"); + } + + return string.Join(Environment.NewLine, lines); + } + + private static string FormatMemberValue(object? value) => value switch + { + null => "", + byte[] bytes => $"byte[{bytes.Length}]", + string text => text.Length > 120 ? $"string[{text.Length}] {text[..120]}..." : $"string[{text.Length}] {text}", + _ => $"{value.GetType().FullName}: {value}" + }; + + private static bool TryGetReflectionJson(object value, out string reflectionJson) + { + Type type = value.GetType(); + + foreach (System.Reflection.PropertyInfo property in type.GetProperties()) + { + if (property.PropertyType == typeof(string) && IsLikelyReflectionMember(property.Name, property.GetValue(value) as string, out reflectionJson)) + { + return true; + } + } + + foreach (System.Reflection.FieldInfo field in type.GetFields()) + { + if (field.FieldType == typeof(string) && IsLikelyReflectionMember(field.Name, field.GetValue(value) as string, out reflectionJson)) + { + return true; + } + } + + reflectionJson = string.Empty; + return false; + } + + private static bool IsLikelyReflectionMember(string name, string? value, out string reflectionJson) + { + if (value is not null && + (name.Contains("Reflection", StringComparison.OrdinalIgnoreCase) || value.TrimStart().StartsWith('{') || value.TrimStart().StartsWith('['))) + { + reflectionJson = value; + return true; + } + + reflectionJson = string.Empty; + return false; + } + + private static string PrettyJson(string json) + { + using JsonDocument document = JsonDocument.Parse(json); + return JsonSerializer.Serialize(document, new JsonSerializerOptions { WriteIndented = true }); + } + private static void CompileTarget(string shaderDirectory, string outputRoot, TargetConfig targetConfig) { string outputDirectory = Path.Combine(outputRoot, targetConfig.Name); @@ -292,7 +422,7 @@ private static string FindProjectRoot() private static void PrintUsage() { - Console.WriteLine("Usage: dotnet run --project SlangResourceHeap.csproj -- [all|dxil|spirv|metal-source|metal] [--spv-descriptor-heap-ext] [--clean]"); + Console.WriteLine("Usage: dotnet run --project SlangResourceHeap.csproj -- [all|dxil|spirv|metal-source|metal|reflection] [--spv-descriptor-heap-ext] [--clean]"); Console.WriteLine("No arguments runs: all --clean --spv-descriptor-heap-ext."); Console.WriteLine("Note: all includes dxil on Windows, metallib on macOS, and skips platform downstream targets elsewhere unless requested explicitly."); } diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md index d8ab00f3..b658da05 100644 --- a/sources/Experiments/SlangResourceHeap/README.md +++ b/sources/Experiments/SlangResourceHeap/README.md @@ -199,15 +199,369 @@ Backend policy: those resources into a command/frame argument table. The user-facing C# struct stays the same, but Metal does not upload the `ResourceHandle` bytes as the final shader ABI. -- For CPU-authored buffers that contain `ResourceHandle` values, provide a - backend-aware upload/packing path instead of raw byte upload on Metal. Large - material systems should usually store resource indices and bind resource arrays - or argument tables explicitly. +- CPU-authored buffers must not contain shader-side `DescriptorHandle` values + when Metal is a target. Large material systems should store resource indices + and bind resource arrays or argument tables explicitly. This keeps the public API simple while preserving the official Slang rule: shader source uses `DescriptorHandle`, and backend-specific ABI work happens in the runtime binding layer. +## AOT Binding Generation + +Metal needs resource-layout information, but that does not require runtime +.NET reflection. The preferred AOT path is to move reflection to build time: + +1. The user marks constants structs with `[Constants]` and declares them as + `partial`. +2. A shader build step compiles Slang and extracts reflection for each entry + point, including parameter blocks, `DescriptorHandle` fields, ordinary POD + fields, Metal argument-buffer indexes, resource kinds, access modes, and byte + offsets. +3. A C# source generator matches that shader reflection with user constants + structs such as `DrawParams` and validates that every `ResourceHandle` field + corresponds to the expected shader-side `DescriptorHandle` field. +4. The generator emits the other partial declaration that implements a static + layout interface. The runtime calls the static interface member instead of + inspecting fields through reflection. +5. The runtime command path constrains `SetConstants` to the layout + interface: + + ```csharp + cmd.SetConstants(in drawParams); + ``` + +The user-authored side stays small: + +```csharp +[Constants] +public partial struct DrawParams +{ + public Matrix4x4 World; + public Matrix4x4 ViewProjection; + public ResourceHandle Buffers; + public ResourceHandle BaseColor; + public ResourceHandle LinearSampler; + public uint MaterialIndex; +} +``` + +The generated side supplies the AOT layout contract: + +```csharp +public interface IConstantsLayout + where TSelf : unmanaged, IConstantsLayout +{ + static abstract ConstantsLayout GetLayout(); +} + +public partial struct DrawParams : IConstantsLayout +{ + public static ConstantsLayout GetLayout() => DrawParamsLayout.Value; +} +``` + +`ConstantsLayout` should be a manifest that contains per-backend layouts derived +from `SlangReflection.Json`, not a single universal byte layout. Fixed resource +arrays prove that DXIL, SPIR-V, and Metal can all differ for the same source. + +Then the public command API can be both simple and AOT-safe: + +```csharp +public void SetConstants(in TConstants constants) + where TConstants : unmanaged, IConstantsLayout +{ + ConstantsLayout layout = TConstants.GetLayout(); + BackendConstantsLayout backendLayout = layout.For(Context.Backend); + // DX/VK pack bytes from generated offsets; Metal consumes resource slots. +} +``` + +Generated layout shape: + +```csharp +file static class DrawParamsLayout +{ + public static readonly ConstantsLayout Value = new( + sizeInBytes: 160, + resourceFields: + [ + new("Buffers", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 128, metalIndex: 0), + new("BaseColor", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 136, metalIndex: 0), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 144, metalIndex: 0), + ]); +} +``` + +The runtime still has one generic implementation, but it consumes generated +metadata rather than reflection metadata: + +- DX12/Vulkan copy the bytes directly because the `ResourceHandle` values are + already descriptor indexes. +- Metal copies POD bytes and patches resource fields into the generated Metal + argument layout by resolving `ResourceHandle` to `MTLBuffer` GPU address, + `MTLTexture`, or `MTLSamplerState`. + +For maximum AOT friendliness, keep the generated path explicit. Do not discover +binders by scanning assemblies. Either call the generated binding type directly, +or have the shader/pipeline source generator emit pipeline factory code that +registers the manifest through ordinary static references. + +Important limitation: the layout interface cannot make this shader type portable +to Metal: + +```slang +DescriptorHandle>>> Textures; +``` + +DX12 and Vulkan can treat the buffer payload as a sequence of descriptor-handle +tokens, but Slang lowers the Metal version to a buffer of native texture +resources: + +```metal +texture2d device* Textures; +``` + +That is not an ordinary `MTLBuffer` payload that Zenith can assemble from a C# +`ResourceHandle[]`. A generator can know the recursive type, but it still cannot +write texture objects into a structured-buffer byte stream for Metal. + +The AOT generator should therefore diagnose this pattern when Metal is enabled, +for example: + +```text +ZENITH0001: StructuredBuffer> is not portable to Metal. +Use uint resource indices plus an explicit resource table/argument table. +``` + +The portable model is: + +```slang +struct TextureArrayConstants +{ + DescriptorHandle> TextureIndices; + DescriptorHandle LinearSampler; + uint TextureCount; + float UvScale; +}; + +// Bound by the generated pipeline binding manifest. +Texture2D gTextureTable[]; + +ConstantBuffer gTextures; + +float4 sampleTexture(uint logicalIndex, float2 uv) +{ + uint textureIndex = gTextures.TextureIndices[logicalIndex]; + return gTextureTable[nonuniform(textureIndex)].Sample(gTextures.LinearSampler, uv); +} +``` + +The generated constants layout then only describes the index buffer and sampler: + +```csharp +file static class TextureIndexParamsLayout +{ + public static readonly ConstantsLayout Value = new( + sizeInBytes: 32, + resourceFields: + [ + new("TextureIndices", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 8, metalIndex: 0), + ]); +} +``` + +The texture table itself is a pipeline/resource-table binding generated from +shader reflection. DX12/Vulkan may still implement it as descriptor heap indexes; +Metal implements it as an argument table/resource array. Users who do not want +the generator can manually implement `IConstantsLayout`, but they still need +to avoid `StructuredBuffer>` for portable Metal shaders. + +Fixed-size arrays are different. This shader shape is portable as a small, +compile-time-sized binding group: + +```slang +static const uint TextureSlotCount = 4; + +struct FixedTextureArrayConstants +{ + DescriptorHandle> Textures[TextureSlotCount]; + DescriptorHandle LinearSampler; + uint TextureIndex; + float UvScale; +}; +``` + +Slang lowers it to `uint2 Textures[4]` for HLSL/DXIL-style targets and to a +Metal native resource array: + +```metal +array, int(4)> Textures; +``` + +`SlangCompiler.CompileWithReflection(args, out SlangReflection reflection)` +returns the compiled bytes and exposes `reflection.Json`. For this fixed array, +the JSON shows that every backend needs its own generated layout: + +```text +DXIL: + Textures uniform offset 0, size 56, elementStride 16 + LinearSampler uniform offset 56, size 8 + TextureIndex uniform offset 64, size 4 + UvScale uniform offset 68, size 4 + CBO size 72 + +SPIR-V + spvDescriptorHeapEXT: + Textures uniform offset 0, size 64, elementStride 16 + LinearSampler uniform offset 64, size 8 + TextureIndex uniform offset 72, size 4 + UvScale uniform offset 76, size 4 + CBO size 80 + +Metal: + Textures shaderResource index 0, count 4 + LinearSampler samplerState index 0 + TextureIndex uniform offset 0, size 4 + UvScale uniform offset 4, size 4 + CBO size 8 +``` + +That means fixed arrays can be supported, but they are not ordinary tight +`ResourceHandle[]` data on every backend. Even DXIL and SPIR-V differ in the +uniform size for the same Slang source. The source generator must consume Slang +reflection per target and emit a per-backend layout, not just preserve C# struct +field order. + +The generated layout should therefore distinguish source offsets, shader uniform +offsets, array strides, and Metal resource slots. A simplified SPIR-V layout +looks like this: + +```csharp +file static class FixedTextureArrayParamsLayout +{ + public static readonly ConstantsLayout Spirv = new( + sizeInBytes: 80, + resourceFields: + [ + new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4, strideInBytes: 16), + new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 64, metalIndex: 0), + ]); +} +``` + +The fixed-length C# value type can also be generated. A source generator cannot +rewrite a field type, so this is not possible: + +```csharp +[ResourceHandleArray(4)] +public ResourceHandle Textures; // Cannot be rewritten into an inline array. +``` + +Instead, let the user declare a named partial value type and let the generator +complete it: + +```csharp +[ResourceHandleArray(4)] +public partial struct TextureHandle4 +{ +} +``` + +Generated source: + +```csharp +[InlineArray(4)] +public partial struct TextureHandle4 +{ + private ResourceHandle element0; +} +``` + +Then constants structs use the generated value type: + +```csharp +[Constants] +public partial struct FixedTextureArrayParams +{ + public TextureHandle4 Textures; + public ResourceHandle LinearSampler; + public uint TextureIndex; + public float UvScale; +} +``` + +`TextureHandle4` is unmanaged and indexable, but the user does not write the +`InlineArray` backing field. The generator can ship common predefined types such +as `ResourceHandle2`, `ResourceHandle4`, `ResourceHandle8`, and +`ResourceHandle16`, and also support project-local marker declarations for less +common lengths. Shader reflection still decides whether a field named `Textures` +matches `DescriptorHandle>[4]`, so the array type itself does +not need to encode texture/buffer/sampler kind. + +Metal operation model: + +- The Metal constants buffer only contains ordinary POD fields here: + `TextureIndex` and `UvScale`. +- `Textures[0..3]` are flattened into four reflected texture resource slots. +- `SetConstants` reads the four C# `ResourceHandle` values, resolves them to + `MTLTexture` objects or resource IDs, and writes them into the command/frame + argument table at `metalIndex + elementIndex`. +- The sampler is resolved separately into the sampler table. + +This is a good model for small fixed groups such as four material textures. It +is not a good model for large or frequently changing arrays, because Metal work +is proportional to the fixed count. For large sets, use `uint` indices plus a +resource table/argument table object that can be cached and rebound. + +Flattening the fixed group into named fields avoids the constant-buffer array +stride difference between DXIL and SPIR-V: + +```slang +struct FlattenedTextureConstants +{ + DescriptorHandle> Texture0; + DescriptorHandle> Texture1; + DescriptorHandle> Texture2; + DescriptorHandle> Texture3; + DescriptorHandle LinearSampler; + uint TextureIndex; + float UvScale; +}; +``` + +Reflection for both DXIL and SPIR-V reports the same uniform layout: + +```text +Texture0 offset 0, size 8 +Texture1 offset 8, size 8 +Texture2 offset 16, size 8 +Texture3 offset 24, size 8 +LinearSampler offset 32, size 8 +TextureIndex offset 40, size 4 +UvScale offset 44, size 4 +CBO size 48 +``` + +Metal still removes the resource handles from the constant bytes, but the +reflection is straightforward: + +```text +Texture0 shaderResource index 0 +Texture1 shaderResource index 1 +Texture2 shaderResource index 2 +Texture3 shaderResource index 3 +LinearSampler samplerState index 0 +TextureIndex uniform offset 0, size 4 +UvScale uniform offset 4, size 4 +CBO size 8 +``` + +So for small fixed material groups, flattened fields are simpler than +`DescriptorHandle[N]`: DX/VK get a compact stable layout, and Metal gets the +same four resource slots. The tradeoff is shader ergonomics; users need a small +generated accessor or a switch helper when indexing dynamically. + ## Metal API Implications The experiment still compiles Metal source for `DescriptorHandle` so we can @@ -255,13 +609,21 @@ but `SetConstants` is backend-aware: - Fragment shader reads a descriptor handle directly from a structured buffer of texture handles: `DescriptorHandle>>>`. +5. `Shaders/05_fixed_texture_array.slang` + - Fragment shader samples from a fixed-size + `DescriptorHandle>[4]` array in a constants block. + This is portable as a reflected fixed resource group; Metal lowers it to a + native `array, 4>`. +6. `Shaders/06_flattened_textures.slang` + - Fragment shader samples from four named texture handle fields. DXIL and + SPIR-V both report a compact 48-byte uniform layout, while Metal reports + four texture resource slots plus an 8-byte POD constant block. The fourth case is intentionally a stress case. It is natural on DX12/Vulkan, but Metal lowers the nested texture handles to native texture-resource layout. -Zenith should treat this as a feature to inspect, not necessarily the preferred -Metal data model. For large material systems, a more Metal-friendly production -model is often `uint` material texture indices plus API-bound texture arrays or -argument buffers. +Zenith should treat it as a compile-time lowering inspection, not as a portable +Metal runtime binding model. For large material systems, use `uint` material +texture indices plus API-bound texture arrays or argument buffers. ## Compile diff --git a/sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang b/sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang new file mode 100644 index 00000000..70895286 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang @@ -0,0 +1,39 @@ +static const uint TextureSlotCount = 4; + +struct FixedTextureArrayConstants +{ + DescriptorHandle> Textures[TextureSlotCount]; + DescriptorHandle LinearSampler; + uint TextureIndex; + float UvScale; +}; + +struct FullscreenVertex +{ + float4 Position : SV_POSITION; + float2 Uv : TEXCOORD0; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer gTextures : register(b0); + +[shader("vertex")] +FullscreenVertex vertexMain(uint vertexId : SV_VertexID) +{ + float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); + + FullscreenVertex output; + output.Position = float4(position, 0.0, 1.0); + output.Uv = position * 0.5 + 0.5; + + return output; +} + +[shader("fragment")] +float4 fragmentMain(FullscreenVertex input) : SV_TARGET +{ + uint textureIndex = min(gTextures.TextureIndex, TextureSlotCount - 1u); + DescriptorHandle> textureHandle = gTextures.Textures[textureIndex]; + + return nonuniform(textureHandle)->Sample(gTextures.LinearSampler, input.Uv * gTextures.UvScale); +} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang b/sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang new file mode 100644 index 00000000..f4fc36f8 --- /dev/null +++ b/sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang @@ -0,0 +1,54 @@ +struct FlattenedTextureConstants +{ + DescriptorHandle> Texture0; + DescriptorHandle> Texture1; + DescriptorHandle> Texture2; + DescriptorHandle> Texture3; + DescriptorHandle LinearSampler; + uint TextureIndex; + float UvScale; +}; + +struct FullscreenVertex +{ + float4 Position : SV_POSITION; + float2 Uv : TEXCOORD0; +}; + +[[vk::binding(0, 0)]] +ConstantBuffer gTextures : register(b0); + +[shader("vertex")] +FullscreenVertex vertexMain(uint vertexId : SV_VertexID) +{ + float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); + + FullscreenVertex output; + output.Position = float4(position, 0.0, 1.0); + output.Uv = position * 0.5 + 0.5; + + return output; +} + +DescriptorHandle> selectTexture(uint textureIndex) +{ + switch (min(textureIndex, 3u)) + { + case 0u: + return gTextures.Texture0; + case 1u: + return gTextures.Texture1; + case 2u: + return gTextures.Texture2; + default: + return gTextures.Texture3; + } +} + +[shader("fragment")] +float4 fragmentMain(FullscreenVertex input) : SV_TARGET +{ + DescriptorHandle> textureHandle = selectTexture(gTextures.TextureIndex); + + return nonuniform(textureHandle)->Sample(gTextures.LinearSampler, input.Uv * gTextures.UvScale); +} \ No newline at end of file From 6dbf9a6ebc45b121d8c5ad9f967aba87fb2b86ab Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 14:32:06 +0800 Subject: [PATCH 063/466] Refactor resource system, remove Slang extension Removed Zenith.NET.Extensions.Slang and related code. Refactored resource management by introducing ResourceHandle, BufferView, and new attribute classes for resource binding. Added IConstantsLayout interface for backend-specific constant handling. Removed legacy resource types and layouts. Centralized Slangc.NET dependency in core project. --- Zenith.NET.slnx | 1 - .../Experiments/CornellBox/CornellBox.csproj | 1 - .../Zenith.NET.Extensions.Slang/Extensions.cs | 63 ------------------- .../Zenith.NET.Extensions.Slang.csproj | 17 ----- .../Attributes/ConstantsAttribute.cs | 4 ++ .../Zenith.NET/Attributes/SampledAttribute.cs | 4 ++ .../Zenith.NET/Attributes/SamplerAttribute.cs | 4 ++ .../Zenith.NET/Attributes/StorageAttribute.cs | 4 ++ .../Attributes/StorageReadOnlyAttribute.cs | 4 ++ .../Attributes/StorageReadWriteAttribute.cs | 4 ++ .../Zenith.NET/Attributes/UniformAttribute.cs | 4 ++ sources/Zenith.NET/Buffer.cs | 6 ++ sources/Zenith.NET/BufferView.cs | 14 +++++ sources/Zenith.NET/Enums/ResourceType.cs | 18 ------ .../Zenith.NET/Interfaces/IConstantsLayout.cs | 16 +++++ sources/Zenith.NET/ResourceTable.cs | 32 ---------- .../{BufferRange.cs => BufferViewDesc.cs} | 2 +- sources/Zenith.NET/Structs/ResourceHandle.cs | 11 ++++ sources/Zenith.NET/Structs/ResourceLayout.cs | 8 --- .../Zenith.NET/Structs/ResourceTableDesc.cs | 6 -- sources/Zenith.NET/Texture.cs | 4 ++ sources/Zenith.NET/TextureView.cs | 4 ++ sources/Zenith.NET/Zenith.NET.csproj | 4 ++ 23 files changed, 88 insertions(+), 147 deletions(-) delete mode 100644 sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs delete mode 100644 sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj create mode 100644 sources/Zenith.NET/Attributes/ConstantsAttribute.cs create mode 100644 sources/Zenith.NET/Attributes/SampledAttribute.cs create mode 100644 sources/Zenith.NET/Attributes/SamplerAttribute.cs create mode 100644 sources/Zenith.NET/Attributes/StorageAttribute.cs create mode 100644 sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs create mode 100644 sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs create mode 100644 sources/Zenith.NET/Attributes/UniformAttribute.cs create mode 100644 sources/Zenith.NET/BufferView.cs delete mode 100644 sources/Zenith.NET/Enums/ResourceType.cs create mode 100644 sources/Zenith.NET/Interfaces/IConstantsLayout.cs delete mode 100644 sources/Zenith.NET/ResourceTable.cs rename sources/Zenith.NET/Structs/{BufferRange.cs => BufferViewDesc.cs} (80%) create mode 100644 sources/Zenith.NET/Structs/ResourceHandle.cs delete mode 100644 sources/Zenith.NET/Structs/ResourceLayout.cs delete mode 100644 sources/Zenith.NET/Structs/ResourceTableDesc.cs diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 7fbe62c4..5578a078 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -10,7 +10,6 @@ - diff --git a/sources/Experiments/CornellBox/CornellBox.csproj b/sources/Experiments/CornellBox/CornellBox.csproj index 65e6ad06..89e4254c 100644 --- a/sources/Experiments/CornellBox/CornellBox.csproj +++ b/sources/Experiments/CornellBox/CornellBox.csproj @@ -7,7 +7,6 @@ - diff --git a/sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs deleted file mode 100644 index 87749a66..00000000 --- a/sources/Extensions/Zenith.NET.Extensions.Slang/Extensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Slangc.NET; - -namespace Zenith.NET.Extensions.Slang; - -public static class Extensions -{ - extension(GraphicsContext context) - { - public Shader LoadShaderFromFile(string file, string entryPoint, ShaderStageFlags stage, string[]? searchPaths = null) - { - List arguments = - [ - file, - "-entry", entryPoint, - "-stage", stage.ToString().ToLowerInvariant(), - "-matrix-layout-row-major" - ]; - - if (searchPaths is not null) - { - foreach (string path in searchPaths) - { - arguments.AddRange(["-I", path]); - } - } - - arguments.Add("-target"); - - switch (context.Backend) - { - case Backend.DirectX12: - arguments.AddRange(["dxil", "-profile", "sm_6_6"]); - break; - - case Backend.Metal: - arguments.AddRange(["metallib", "-capability", "metallib_latest"]); - break; - - case Backend.Vulkan: - arguments.AddRange(["spirv", "-capability", "spirv_latest", "-fvk-use-entrypoint-name"]); - break; - } - - return context.CreateShader(new() { ShaderBytes = SlangCompiler.Compile([.. arguments]), EntryPoint = entryPoint, Stage = stage }); - } - - public Shader LoadShaderFromSource(string source, string entryPoint, ShaderStageFlags stage, string[]? searchPaths = null) - { - string file = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.slang"); - - File.WriteAllText(file, source); - - try - { - return context.LoadShaderFromFile(file, entryPoint, stage, searchPaths); - } - finally - { - File.Delete(file); - } - } - } -} diff --git a/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj b/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj deleted file mode 100644 index f7e52318..00000000 --- a/sources/Extensions/Zenith.NET.Extensions.Slang/Zenith.NET.Extensions.Slang.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - $(StandardTargetFramework) - - - - - - - - - - - - - diff --git a/sources/Zenith.NET/Attributes/ConstantsAttribute.cs b/sources/Zenith.NET/Attributes/ConstantsAttribute.cs new file mode 100644 index 00000000..fbbb8e55 --- /dev/null +++ b/sources/Zenith.NET/Attributes/ConstantsAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Struct)] +public sealed class ConstantsAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/SampledAttribute.cs b/sources/Zenith.NET/Attributes/SampledAttribute.cs new file mode 100644 index 00000000..331f0524 --- /dev/null +++ b/sources/Zenith.NET/Attributes/SampledAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Field)] +public sealed class SampledAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/SamplerAttribute.cs b/sources/Zenith.NET/Attributes/SamplerAttribute.cs new file mode 100644 index 00000000..b385e482 --- /dev/null +++ b/sources/Zenith.NET/Attributes/SamplerAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Field)] +public sealed class SamplerAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/StorageAttribute.cs b/sources/Zenith.NET/Attributes/StorageAttribute.cs new file mode 100644 index 00000000..c8a9c95b --- /dev/null +++ b/sources/Zenith.NET/Attributes/StorageAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Field)] +public sealed class StorageAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs b/sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs new file mode 100644 index 00000000..352ee7e4 --- /dev/null +++ b/sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Field)] +public sealed class StorageReadOnlyAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs b/sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs new file mode 100644 index 00000000..d7d8d94c --- /dev/null +++ b/sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Field)] +public sealed class StorageReadWriteAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/UniformAttribute.cs b/sources/Zenith.NET/Attributes/UniformAttribute.cs new file mode 100644 index 00000000..d3f919bc --- /dev/null +++ b/sources/Zenith.NET/Attributes/UniformAttribute.cs @@ -0,0 +1,4 @@ +namespace Zenith.NET; + +[AttributeUsage(AttributeTargets.Field)] +public sealed class UniformAttribute : Attribute; diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index ab360017..f8184faf 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -6,6 +6,12 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public ref readonly BufferDesc Desc => ref desc; + public ResourceHandle UniformHandle { get; } + + public ResourceHandle StorageReadOnlyHandle { get; } + + public ResourceHandle StorageReadWriteHandle { get; } + public abstract MappedMemory Map(); public abstract void Unmap(); diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs new file mode 100644 index 00000000..e690bc43 --- /dev/null +++ b/sources/Zenith.NET/BufferView.cs @@ -0,0 +1,14 @@ +namespace Zenith.NET; + +internal abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : GraphicsResource(context) +{ + private BufferViewDesc desc = desc; + + public ref readonly BufferViewDesc Desc => ref desc; + + public ResourceHandle UniformHandle { get; } + + public ResourceHandle StorageReadOnlyHandle { get; } + + public ResourceHandle StorageReadWriteHandle { get; } +} diff --git a/sources/Zenith.NET/Enums/ResourceType.cs b/sources/Zenith.NET/Enums/ResourceType.cs deleted file mode 100644 index 7a20dce5..00000000 --- a/sources/Zenith.NET/Enums/ResourceType.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Zenith.NET; - -public enum ResourceType -{ - UniformBuffer, - - StorageReadOnlyBuffer, - - StorageReadWriteBuffer, - - SampledTexture, - - StorageTexture, - - Sampler, - - AccelerationStructure -} diff --git a/sources/Zenith.NET/Interfaces/IConstantsLayout.cs b/sources/Zenith.NET/Interfaces/IConstantsLayout.cs new file mode 100644 index 00000000..a449aed5 --- /dev/null +++ b/sources/Zenith.NET/Interfaces/IConstantsLayout.cs @@ -0,0 +1,16 @@ +namespace Zenith.NET; + +public interface IConstantsLayout where T : unmanaged, IConstantsLayout +{ + static abstract uint SizeInBytesOnDirectX12 { get; } + + static abstract uint SizeInBytesOnMetal { get; } + + static abstract uint SizeInBytesOnVulkan { get; } + + static abstract void DirectX12(T data, Buffer dst); + + static abstract void Metal(T data, Buffer dst); + + static abstract void Vulkan(T data, Buffer dst); +} diff --git a/sources/Zenith.NET/ResourceTable.cs b/sources/Zenith.NET/ResourceTable.cs deleted file mode 100644 index a817ff0c..00000000 --- a/sources/Zenith.NET/ResourceTable.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Zenith.NET; - -public abstract class ResourceTable(GraphicsContext context, ResourceTableDesc desc) : GraphicsResource(context) -{ - private ResourceTableDesc desc = desc; - - public ref readonly ResourceTableDesc Desc => ref desc; - - public abstract void Write(uint binding, Buffer buffer); - - public abstract void Write(uint binding, BufferRange bufferRange); - - public abstract void Write(uint binding, Texture texture); - - public abstract void Write(uint binding, TextureView textureView); - - public abstract void Write(uint binding, Sampler sampler); - - public abstract void Write(uint binding, TopLevelAccelerationStructure topLevelAccelerationStructure); - - public abstract void Write(uint binding, ReadOnlySpan buffers); - - public abstract void Write(uint binding, ReadOnlySpan bufferRanges); - - public abstract void Write(uint binding, ReadOnlySpan textures); - - public abstract void Write(uint binding, ReadOnlySpan textureViews); - - public abstract void Write(uint binding, ReadOnlySpan samplers); - - public abstract void Write(uint binding, ReadOnlySpan topLevelAccelerationStructures); -} diff --git a/sources/Zenith.NET/Structs/BufferRange.cs b/sources/Zenith.NET/Structs/BufferViewDesc.cs similarity index 80% rename from sources/Zenith.NET/Structs/BufferRange.cs rename to sources/Zenith.NET/Structs/BufferViewDesc.cs index 84cdc030..472c771a 100644 --- a/sources/Zenith.NET/Structs/BufferRange.cs +++ b/sources/Zenith.NET/Structs/BufferViewDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BufferRange +public record struct BufferViewDesc { public Buffer Buffer; diff --git a/sources/Zenith.NET/Structs/ResourceHandle.cs b/sources/Zenith.NET/Structs/ResourceHandle.cs new file mode 100644 index 00000000..1185fd88 --- /dev/null +++ b/sources/Zenith.NET/Structs/ResourceHandle.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Zenith.NET; + +[StructLayout(LayoutKind.Sequential)] +public readonly struct ResourceHandle(uint x, uint y) +{ + public readonly uint X = x; + + public readonly uint Y = y; +} diff --git a/sources/Zenith.NET/Structs/ResourceLayout.cs b/sources/Zenith.NET/Structs/ResourceLayout.cs deleted file mode 100644 index 026c748b..00000000 --- a/sources/Zenith.NET/Structs/ResourceLayout.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public record struct ResourceLayout -{ - public ResourceType Type; - - public uint Count; -} \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/ResourceTableDesc.cs b/sources/Zenith.NET/Structs/ResourceTableDesc.cs deleted file mode 100644 index 3328dae7..00000000 --- a/sources/Zenith.NET/Structs/ResourceTableDesc.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Zenith.NET; - -public record struct ResourceTableDesc -{ - public ResourceLayout[] ResourceLayouts; -} diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index 6ee142b6..6889dd06 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -6,6 +6,10 @@ public abstract class Texture(GraphicsContext context, TextureDesc desc) : Graph public ref readonly TextureDesc Desc => ref desc; + public ResourceHandle SampledHandle { get; } + + public ResourceHandle StorageHandle { get; } + public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); diff --git a/sources/Zenith.NET/TextureView.cs b/sources/Zenith.NET/TextureView.cs index b8137c72..6588f449 100644 --- a/sources/Zenith.NET/TextureView.cs +++ b/sources/Zenith.NET/TextureView.cs @@ -5,4 +5,8 @@ public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) private TextureViewDesc desc = desc; public ref readonly TextureViewDesc Desc => ref desc; + + public ResourceHandle SampledHandle { get; } + + public ResourceHandle StorageHandle { get; } } diff --git a/sources/Zenith.NET/Zenith.NET.csproj b/sources/Zenith.NET/Zenith.NET.csproj index bace72ae..a8044549 100644 --- a/sources/Zenith.NET/Zenith.NET.csproj +++ b/sources/Zenith.NET/Zenith.NET.csproj @@ -7,4 +7,8 @@ + + + + From 4a92ddf75ac7c42e1e663ccdda2ed0b582a0bf5f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 15:55:08 +0800 Subject: [PATCH 064/466] Refactor: remove resource tables, add constants system Refactored the graphics API to eliminate resource tables and resource layouts from pipeline descriptors and validation. Introduced a new constants management system with a `Constants` class for pooling and reusing constant buffers. Updated `CommandBuffer` to use `SetConstants` for setting shader constants. Adjusted pipeline descriptors and validation logic accordingly, and added support for buffer views. --- sources/Zenith.NET/BufferView.cs | 2 +- sources/Zenith.NET/CommandBuffer.cs | 12 +- sources/Zenith.NET/Constants.cs | 143 ++++++++++++++++++ sources/Zenith.NET/GraphicsContext.cs | 22 +-- .../Zenith.NET/Structs/ComputePipelineDesc.cs | 2 - .../Structs/GraphicsPipelineDesc.cs | 2 - .../Structs/MeshShadingPipelineDesc.cs | 6 +- sources/Zenith.NET/ValidationLayer.cs | 60 +++----- 8 files changed, 186 insertions(+), 63 deletions(-) create mode 100644 sources/Zenith.NET/Constants.cs diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs index e690bc43..4f7eaa3c 100644 --- a/sources/Zenith.NET/BufferView.cs +++ b/sources/Zenith.NET/BufferView.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -internal abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : GraphicsResource(context) +public abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : GraphicsResource(context) { private BufferViewDesc desc = desc; diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 2eb98919..d9f88db4 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -311,19 +311,19 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexF SetIndexBufferImpl(pipeline, buffer, offsetInBytes, indexFormat); } - public void PushResourceTable(ResourceTable resourceTable) + public void SetConstants(T data) where T : unmanaged, IConstantsLayout { - if (!TryGetCurrentPipeline(nameof(PushResourceTable), out Pipeline pipeline)) + if (!TryGetCurrentPipeline(nameof(SetConstants), out Pipeline pipeline)) { return; } - if (Context.ValidationLayer?.ValidateResource("PushResourceTable.resourceTable", resourceTable) is false) + if (Context.ValidationLayer?.ValidateSetConstants(data) is false) { return; } - PushResourceTableImpl(pipeline, resourceTable); + SetConstantsImpl(pipeline, Context.Constants.Buffer(this, data)); } public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) @@ -522,6 +522,7 @@ internal void Reset() Context.Uploader.Release(this); Context.Downloader.Release(this); + Context.Constants.Release(this); currentPipeline = null; } @@ -530,6 +531,7 @@ protected override void Destroy() { Context.Uploader.Release(this); Context.Downloader.Release(this); + Context.Constants.Release(this); currentPipeline = null; } @@ -620,7 +622,7 @@ private bool TryGetCurrentPipeline(string commandName, out TPipeline protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); - protected abstract void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable); + protected abstract void SetConstantsImpl(Pipeline pipeline, Buffer buffer); protected abstract void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); diff --git a/sources/Zenith.NET/Constants.cs b/sources/Zenith.NET/Constants.cs new file mode 100644 index 00000000..6f29b481 --- /dev/null +++ b/sources/Zenith.NET/Constants.cs @@ -0,0 +1,143 @@ +namespace Zenith.NET; + +internal class Constants(GraphicsContext context) : DisposableObject +{ + private readonly Lock @lock = new(); + private readonly List available = []; + private readonly Dictionary> borrowed = []; + + public Buffer Buffer(CommandBuffer commandBuffer, T data) where T : unmanaged, IConstantsLayout + { + uint sizeInBytes = SizeInBytes(); + Lease lease; + + using (Lock.Scope _ = @lock.EnterScope()) + { + if (!borrowed.TryGetValue(commandBuffer, out List? leases)) + { + borrowed[commandBuffer] = leases = []; + } + + Lease? availableLease = available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes); + if (availableLease is not null && available.Remove(availableLease)) + { + lease = availableLease; + } + else + { + lease = new(context.CreateBuffer(new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 1, + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Uniform + })); + } + + leases.Add(lease); + } + + Write(data, lease.Buffer); + + return lease.Buffer; + } + + public void Release(CommandBuffer commandBuffer) + { + using Lock.Scope _ = @lock.EnterScope(); + + CleanupExpiredLeases(); + + if (borrowed.Remove(commandBuffer, out List? leases)) + { + foreach (Lease lease in leases) + { + available.Add(lease.Renew()); + } + } + } + + protected override void Destroy() + { + foreach (CommandBuffer commandBuffer in borrowed.Keys.ToArray()) + { + Release(commandBuffer); + } + + foreach (Lease lease in available) + { + lease.Release(); + } + available.Clear(); + } + + private uint SizeInBytes() where T : unmanaged, IConstantsLayout + { + return context.Api switch + { + GraphicsApi.DirectX12 => T.SizeInBytesOnDirectX12, + GraphicsApi.Metal => T.SizeInBytesOnMetal, + GraphicsApi.Vulkan => T.SizeInBytesOnVulkan, + _ => 0 + }; + } + + private void Write(T data, Buffer buffer) where T : unmanaged, IConstantsLayout + { + switch (context.Api) + { + case GraphicsApi.DirectX12: + T.DirectX12(data, buffer); + break; + + case GraphicsApi.Metal: + T.Metal(data, buffer); + break; + + case GraphicsApi.Vulkan: + T.Vulkan(data, buffer); + break; + } + } + + private void CleanupExpiredLeases() + { + available.RemoveAll(static item => item.TryExpire()); + } + + private class Lease(Buffer buffer) + { + private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + + public Buffer Buffer { get; } = buffer; + + public bool HasCapacityFor(uint sizeInBytes) + { + return Buffer.Desc.SizeInBytes >= sizeInBytes; + } + + public bool TryExpire() + { + if (DateTime.UtcNow >= expirationTime) + { + Release(); + + return true; + } + + return false; + } + + public Lease Renew() + { + expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + + return this; + } + + public void Release() + { + Buffer.Dispose(); + } + } +} \ No newline at end of file diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 918d58b1..feab38f1 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -21,6 +21,7 @@ protected GraphicsContext(GraphicsApi api, bool useValidationLayer) Uploader = new(this); Downloader = new(this); + Constants = new(this); } public GraphicsApi Api { get; } @@ -39,6 +40,8 @@ protected GraphicsContext(GraphicsApi api, bool useValidationLayer) internal Downloader Downloader { get; } + internal Constants Constants { get; } + public event EventHandler? ValidationMessage; public SwapChain CreateSwapChain(SwapChainDesc desc) @@ -55,6 +58,13 @@ public Buffer CreateBuffer(BufferDesc desc) return CreateBufferImpl(desc); } + public BufferView CreateBufferView(BufferViewDesc desc) + { + ValidationLayer?.ValidateDesc(desc); + + return CreateBufferViewImpl(desc); + } + public Texture CreateTexture(TextureDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -76,13 +86,6 @@ public Sampler CreateSampler(SamplerDesc desc) return CreateSamplerImpl(desc); } - public ResourceTable CreateResourceTable(ResourceTableDesc desc) - { - ValidationLayer?.ValidateDesc(desc); - - return CreateResourceTableImpl(desc); - } - public Shader CreateShader(ShaderDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -129,6 +132,7 @@ protected override void Destroy() Uploader.Dispose(); Downloader.Dispose(); + Constants.Dispose(); } protected abstract void Initialize(bool useValidationLayer, @@ -142,14 +146,14 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract Buffer CreateBufferImpl(BufferDesc desc); + protected abstract BufferView CreateBufferViewImpl(BufferViewDesc desc); + protected abstract Texture CreateTextureImpl(TextureDesc desc); protected abstract TextureView CreateTextureViewImpl(TextureViewDesc desc); protected abstract Sampler CreateSamplerImpl(SamplerDesc desc); - protected abstract ResourceTable CreateResourceTableImpl(ResourceTableDesc desc); - protected abstract Shader CreateShaderImpl(ShaderDesc desc); protected abstract GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc); diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index 1070a781..e1dd6033 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -3,6 +3,4 @@ public record struct ComputePipelineDesc { public Shader ComputeShader; - - public ResourceLayout[] ResourceLayouts; } diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 34a5baf0..256846e3 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -8,8 +8,6 @@ public record struct GraphicsPipelineDesc public Shader FragmentShader; - public ResourceLayout[] ResourceLayouts; - public InputLayout[] InputLayouts; public PrimitiveTopology PrimitiveTopology; diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index d965f58c..e6964110 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -4,15 +4,13 @@ public record struct MeshShadingPipelineDesc { public RenderState RenderState; + public Shader? TaskShader; + public Shader MeshShader; public Shader FragmentShader; - public ResourceLayout[] ResourceLayouts; - public PrimitiveTopology PrimitiveTopology; public AttachmentFormats AttachmentFormats; - - public Shader? TaskShader; } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 2dcb7770..e0887b05 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -58,6 +58,21 @@ internal void ValidateDesc(BufferDesc desc) } } + internal void ValidateDesc(BufferViewDesc desc) + { + if (!CheckResource("BufferViewDesc.Buffer", desc.Buffer)) + { + return; + } + + CheckBufferRange("BufferViewDesc", desc.Buffer, desc.OffsetInBytes, desc.SizeInBytes); + + if (desc.StrideInBytes is 0) + { + ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "structured buffer views")); + } + } + internal void ValidateDesc(TextureDesc desc) { CheckEnum("TextureDesc.Type", desc.Type); @@ -175,19 +190,6 @@ internal void ValidateDesc(SamplerDesc desc) CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); } - internal void ValidateDesc(ResourceTableDesc desc) - { - if (!CheckArrayNotEmpty("ResourceTableDesc.ResourceLayouts", desc.ResourceLayouts)) - { - return; - } - - for (int index = 0; index < desc.ResourceLayouts.Length; index++) - { - CheckResourceLayout($"ResourceTableDesc.ResourceLayouts[{index}]", desc.ResourceLayouts[index]); - } - } - internal void ValidateDesc(ShaderDesc desc) { CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); @@ -202,8 +204,6 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); - CheckResourceLayouts("GraphicsPipelineDesc.ResourceLayouts", desc.ResourceLayouts); - if (desc.InputLayouts is null) { ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); @@ -223,8 +223,6 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) internal void ValidateDesc(ComputePipelineDesc desc) { CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); - - CheckResourceLayouts("ComputePipelineDesc.ResourceLayouts", desc.ResourceLayouts); } internal void ValidateDesc(MeshShadingPipelineDesc desc) @@ -234,8 +232,6 @@ internal void ValidateDesc(MeshShadingPipelineDesc desc) CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); - CheckResourceLayouts("MeshShadingPipelineDesc.ResourceLayouts", desc.ResourceLayouts); - if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) { ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); @@ -651,6 +647,11 @@ internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFor return isValid; } + internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout + { + return true; + } + internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) { bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); @@ -1251,27 +1252,6 @@ private void CheckSurface(string name, Surface surface) CheckGreaterThanZero($"{name}.Height", surface.Height); } - private void CheckResourceLayouts(string name, ResourceLayout[]? resourceLayouts) - { - if (resourceLayouts is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, name)); - - return; - } - - for (int index = 0; index < resourceLayouts.Length; index++) - { - CheckResourceLayout($"{name}[{index}]", resourceLayouts[index]); - } - } - - private void CheckResourceLayout(string name, ResourceLayout resourceLayout) - { - CheckEnum($"{name}.Type", resourceLayout.Type); - CheckGreaterThanZero($"{name}.Count", resourceLayout.Count); - } - private void CheckInputLayout(string name, InputLayout inputLayout) { if (CheckArrayNotEmpty($"{name}.InputElements", inputLayout.InputElements)) From 1511bdded7b1605001649e3da28adb8791ef3901 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 17:16:54 +0800 Subject: [PATCH 065/466] Refactor GraphicsApi property, add shader compile helpers Renamed Api to GraphicsApi for clarity and updated all references. Added CreateShaderFromFile and CreateShaderFromSource methods to compile shaders using SlangCompiler. Introduced SlangCompilerArguments helper to generate backend-specific arguments. Included Slangc.NET using directive. --- sources/Zenith.NET/Constants.cs | 4 +- sources/Zenith.NET/GraphicsContext.cs | 82 +++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET/Constants.cs b/sources/Zenith.NET/Constants.cs index 6f29b481..9d1b6bbd 100644 --- a/sources/Zenith.NET/Constants.cs +++ b/sources/Zenith.NET/Constants.cs @@ -73,7 +73,7 @@ protected override void Destroy() private uint SizeInBytes() where T : unmanaged, IConstantsLayout { - return context.Api switch + return context.GraphicsApi switch { GraphicsApi.DirectX12 => T.SizeInBytesOnDirectX12, GraphicsApi.Metal => T.SizeInBytesOnMetal, @@ -84,7 +84,7 @@ private uint SizeInBytes() where T : unmanaged, IConstantsLayout private void Write(T data, Buffer buffer) where T : unmanaged, IConstantsLayout { - switch (context.Api) + switch (context.GraphicsApi) { case GraphicsApi.DirectX12: T.DirectX12(data, buffer); diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index feab38f1..3b892251 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -1,10 +1,12 @@ -namespace Zenith.NET; +using Slangc.NET; + +namespace Zenith.NET; public abstract class GraphicsContext : DisposableObject, INativeObject { - protected GraphicsContext(GraphicsApi api, bool useValidationLayer) + protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) { - Api = api; + GraphicsApi = graphicsApi; Initialize(useValidationLayer, out Capabilities capabilities, @@ -24,7 +26,7 @@ protected GraphicsContext(GraphicsApi api, bool useValidationLayer) Constants = new(this); } - public GraphicsApi Api { get; } + public GraphicsApi GraphicsApi { get; } public Capabilities Capabilities { get; } @@ -121,6 +123,26 @@ public QueryHeap CreateQueryHeap(QueryHeapDesc desc) return CreateQueryHeapImpl(desc); } + public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages stage, string[]? searchPaths = null) + { + return CreateShader(new() + { + Bytecode = SlangCompiler.Compile(SlangCompilerArguments(file, entryPoint, stage, searchPaths)), + EntryPoint = entryPoint, + Stage = stage + }); + } + + public Shader CreateShaderFromSource(string source, string entryPoint, ShaderStages stage, string[]? searchPaths = null) + { + return CreateShader(new() + { + Bytecode = SlangCompiler.Compile(source, SlangCompilerArguments(null, entryPoint, stage, searchPaths)), + EntryPoint = entryPoint, + Stage = stage + }); + } + public abstract nint GetNativeObject(NativeObjectType type); protected override void Destroy() @@ -168,4 +190,56 @@ internal void OnValidationMessage(ValidationMessageEventArgs args) { ValidationMessage?.Invoke(this, args); } + + private string[] SlangCompilerArguments(string? file, string entryPoint, ShaderStages stage, string[]? searchPaths) + { + List arguments; + + if (file is null) + { + arguments = + [ + "-entry", entryPoint, + "-stage", stage.ToString().ToLowerInvariant(), + "-matrix-layout-row-major" + ]; + } + else + { + arguments = + [ + file, + "-entry", entryPoint, + "-stage", stage.ToString().ToLowerInvariant(), + "-matrix-layout-row-major" + ]; + } + + if (searchPaths is not null) + { + foreach (string path in searchPaths) + { + arguments.AddRange(["-I", path]); + } + } + + arguments.Add("-target"); + + switch (GraphicsApi) + { + case GraphicsApi.DirectX12: + arguments.AddRange(["dxil", "-profile", "sm_6_6"]); + break; + + case GraphicsApi.Metal: + arguments.AddRange(["metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]); + break; + + case GraphicsApi.Vulkan: + arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-fvk-use-entrypoint-name"]); + break; + } + + return [.. arguments]; + } } From a9a17792e402d18e27c79088e5d11081414f9ff4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 17:24:52 +0800 Subject: [PATCH 066/466] Add ResourceHandle property to Sampler class Introduced a public read-only Handle property of type ResourceHandle to the Sampler class, enabling external access to the underlying resource handle. --- sources/Zenith.NET/Sampler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/Zenith.NET/Sampler.cs b/sources/Zenith.NET/Sampler.cs index 7c699dcd..5ad1d477 100644 --- a/sources/Zenith.NET/Sampler.cs +++ b/sources/Zenith.NET/Sampler.cs @@ -5,4 +5,6 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph private SamplerDesc desc = desc; public ref readonly SamplerDesc Desc => ref desc; + + public ResourceHandle Handle { get; } } From c513c9258917b21b333bbacd8867ab47ccb27078 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 17:27:18 +0800 Subject: [PATCH 067/466] Clean up: remove experiment sources and shaders Removed all experiment source files, shaders, and the C# console app from the project. Updated the solution file to drop experiment references. The README is retained for documentation, but all implementation and test cases have been deleted. --- Zenith.NET.slnx | 1 - .../ConstantsBindingSketch.cs | 208 ------ .../Experiments/SlangResourceHeap/Program.cs | 429 ----------- .../Experiments/SlangResourceHeap/README.md | 671 ------------------ .../Shaders/01_cbo_texture_sampler.slang | 33 - .../Shaders/02_cbo_buffer_uav.slang | 23 - .../03_nonuniform_material_texture.slang | 45 -- .../Shaders/04_texture_handle_array.slang | 39 - .../Shaders/05_fixed_texture_array.slang | 39 - .../Shaders/06_flattened_textures.slang | 54 -- .../SlangResourceHeap.csproj | 12 - 11 files changed, 1554 deletions(-) delete mode 100644 sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs delete mode 100644 sources/Experiments/SlangResourceHeap/Program.cs delete mode 100644 sources/Experiments/SlangResourceHeap/README.md delete mode 100644 sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang delete mode 100644 sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang delete mode 100644 sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang delete mode 100644 sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang delete mode 100644 sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang delete mode 100644 sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang delete mode 100644 sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 5578a078..62db645d 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -4,7 +4,6 @@ - diff --git a/sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs b/sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs deleted file mode 100644 index ab7f4e4f..00000000 --- a/sources/Experiments/SlangResourceHeap/ConstantsBindingSketch.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SlangResourceHeap.BindingModel; - -[AttributeUsage(AttributeTargets.Struct, Inherited = false)] -public sealed class ConstantsAttribute : Attribute -{ -} - -[AttributeUsage(AttributeTargets.Struct, Inherited = false)] -public sealed class ResourceHandleArrayAttribute(int length) : Attribute -{ - public int Length { get; } = length; -} - -[StructLayout(LayoutKind.Sequential)] -public struct ResourceHandle -{ - public uint X; - - public uint Y; -} - -[ResourceHandleArray(4)] -public partial struct TextureHandle4 -{ -} - -[InlineArray(4)] -public partial struct TextureHandle4 -{ - private ResourceHandle element0; -} - -public enum ResourceKind -{ - Buffer, - - Texture, - - Sampler, - - AccelerationStructure -} - -public enum ResourceAccess -{ - Read, - - ReadWrite -} - -public sealed class ConstantsLayout(int sizeInBytes, ResourceFieldLayout[] resourceFields) -{ - public int SizeInBytes { get; } = sizeInBytes; - - public ResourceFieldLayout[] ResourceFields { get; } = resourceFields; -} - -public sealed class ResourceFieldLayout( - string name, - ResourceKind kind, - ResourceAccess access, - int offsetInBytes, - int metalIndex, - int count = 1, - int strideInBytes = 0, - ConstantsLayout? elementLayout = null) -{ - public string Name { get; } = name; - - public ResourceKind Kind { get; } = kind; - - public ResourceAccess Access { get; } = access; - - public int OffsetInBytes { get; } = offsetInBytes; - - public int MetalIndex { get; } = metalIndex; - - public int Count { get; } = count; - - public int StrideInBytes { get; } = strideInBytes; - - public ConstantsLayout? ElementLayout { get; } = elementLayout; -} - -public interface IConstantsLayout - where TSelf : unmanaged, IConstantsLayout -{ - static abstract ConstantsLayout GetLayout(); -} - -public sealed class CommandBufferSketch -{ - public void SetConstants(in TConstants constants) - where TConstants : unmanaged, IConstantsLayout - { - ConstantsLayout layout = TConstants.GetLayout(); - - _ = constants; - _ = layout; - } -} - -[Constants] -public partial struct DrawParams -{ - public Matrix4x4 World; - - public Matrix4x4 ViewProjection; - - public ResourceHandle Buffers; - - public ResourceHandle BaseColor; - - public ResourceHandle LinearSampler; - - public uint MaterialIndex; -} - -public partial struct DrawParams : IConstantsLayout -{ - public static ConstantsLayout GetLayout() => DrawParamsLayout.Value; -} - -file static class DrawParamsLayout -{ - public static readonly ConstantsLayout Value = new( - sizeInBytes: 160, - resourceFields: - [ - new("Buffers", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 128, metalIndex: 0), - new("BaseColor", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 136, metalIndex: 0), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 144, metalIndex: 0), - ]); -} - -[Constants] -public partial struct TextureIndexParams -{ - public ResourceHandle TextureIndices; - - public ResourceHandle LinearSampler; - - public uint TextureIndex; -} - -public partial struct TextureIndexParams : IConstantsLayout -{ - public static ConstantsLayout GetLayout() => TextureIndexParamsLayout.Value; -} - -file static class TextureIndexParamsLayout -{ - public static readonly ConstantsLayout Value = new( - sizeInBytes: 32, - resourceFields: - [ - new("TextureIndices", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 8, metalIndex: 0), - ]); -} - -[Constants] -public partial struct FixedTextureArrayParams -{ - public TextureHandle4 Textures; - - public ResourceHandle LinearSampler; - - public uint TextureIndex; - - public float UvScale; -} - -public partial struct FixedTextureArrayParams : IConstantsLayout -{ - public static ConstantsLayout GetLayout() => FixedTextureArrayParamsLayout.Spirv; -} - -file static class FixedTextureArrayParamsLayout -{ - public static readonly ConstantsLayout Dxil = new( - sizeInBytes: 72, - resourceFields: - [ - new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4, strideInBytes: 16), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 56, metalIndex: 0), - ]); - - public static readonly ConstantsLayout Spirv = new( - sizeInBytes: 80, - resourceFields: - [ - new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4, strideInBytes: 16), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 64, metalIndex: 0), - ]); - - public static readonly ConstantsLayout Metal = new( - sizeInBytes: 8, - resourceFields: - [ - new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 32, metalIndex: 0), - ]); -} diff --git a/sources/Experiments/SlangResourceHeap/Program.cs b/sources/Experiments/SlangResourceHeap/Program.cs deleted file mode 100644 index 3bc37017..00000000 --- a/sources/Experiments/SlangResourceHeap/Program.cs +++ /dev/null @@ -1,429 +0,0 @@ -using Slangc.NET; -using System.Runtime.InteropServices; -using System.Text.Json; - -internal sealed record ShaderEntry(string Name, string Stage); - -internal sealed record ShaderTest(string Name, string File, ShaderEntry[] Entries); - -internal sealed record TargetConfig(string Name, string OutputExtension, string[] Arguments); - -internal sealed record CompileOptions(string Target, bool UseSpvDescriptorHeapExt, bool Clean, bool ShowHelp) -{ - private static readonly HashSet ValidTargets = new(StringComparer.OrdinalIgnoreCase) - { - "all", - "dxil", - "spirv", - "metal-source", - "metal", - "reflection" - }; - - public static CompileOptions Parse(string[] arguments) - { - string target = "all"; - bool targetSpecified = false; - bool useSpvDescriptorHeapExt = arguments.Length == 0; - bool clean = arguments.Length == 0; - bool showHelp = false; - - for (int index = 0; index < arguments.Length; index++) - { - string argument = arguments[index]; - - switch (argument) - { - case "-h": - case "--help": - showHelp = true; - break; - - case "--target": - if (index + 1 >= arguments.Length) - { - throw new ArgumentException("Missing value for --target."); - } - - target = arguments[++index]; - targetSpecified = true; - break; - - case "--spv-descriptor-heap-ext": - useSpvDescriptorHeapExt = true; - break; - - case "--clean": - clean = true; - break; - - default: - if (argument.StartsWith('-')) - { - throw new ArgumentException($"Unknown option '{argument}'."); - } - - if (targetSpecified) - { - throw new ArgumentException($"Unexpected argument '{argument}'."); - } - - target = argument; - targetSpecified = true; - break; - } - } - - if (!ValidTargets.Contains(target)) - { - throw new ArgumentException($"Unknown target '{target}'. Expected one of: {string.Join(", ", ValidTargets)}."); - } - - return new(target, useSpvDescriptorHeapExt, clean, showHelp); - } -} - -internal static class Program -{ - private static readonly ShaderTest[] TestCases = - [ - new( - "01_cbo_texture_sampler", - "01_cbo_texture_sampler.slang", - [ - new("vertexMain", "vertex"), - new("fragmentMain", "fragment") - ]), - new( - "02_cbo_buffer_uav", - "02_cbo_buffer_uav.slang", - [ - new("computeMain", "compute") - ]), - new( - "03_nonuniform_material_texture", - "03_nonuniform_material_texture.slang", - [ - new("vertexMain", "vertex"), - new("fragmentMain", "fragment") - ]), - new( - "04_texture_handle_array", - "04_texture_handle_array.slang", - [ - new("vertexMain", "vertex"), - new("fragmentMain", "fragment") - ]), - new( - "05_fixed_texture_array", - "05_fixed_texture_array.slang", - [ - new("vertexMain", "vertex"), - new("fragmentMain", "fragment") - ]), - new( - "06_flattened_textures", - "06_flattened_textures.slang", - [ - new("vertexMain", "vertex"), - new("fragmentMain", "fragment") - ]) - ]; - - private static int Main(string[] arguments) - { - try - { - CompileOptions options = CompileOptions.Parse(arguments); - if (options.ShowHelp) - { - PrintUsage(); - return 0; - } - - if (arguments.Length == 0) - { - Console.WriteLine("No arguments supplied; running all --clean --spv-descriptor-heap-ext."); - } - - string projectRoot = FindProjectRoot(); - string shaderDirectory = Path.Combine(projectRoot, "Shaders"); - string outputRoot = Path.Combine(projectRoot, "out"); - - if (options.Clean && Directory.Exists(outputRoot)) - { - Directory.Delete(outputRoot, recursive: true); - } - - if (options.Target.Equals("reflection", StringComparison.OrdinalIgnoreCase)) - { - DumpReflection(shaderDirectory, outputRoot, options.UseSpvDescriptorHeapExt); - return 0; - } - - foreach (string target in ExpandTargets(options.Target)) - { - CompileTarget(shaderDirectory, outputRoot, CreateTargetConfig(target, options.UseSpvDescriptorHeapExt)); - } - - return 0; - } - catch (Exception exception) - { - Console.Error.WriteLine(exception.Message); - return 1; - } - } - - private static void DumpReflection(string shaderDirectory, string outputRoot, bool useSpvDescriptorHeapExt) - { - string outputDirectory = Path.Combine(outputRoot, "reflection"); - Directory.CreateDirectory(outputDirectory); - - foreach (ShaderTest testCase in TestCases.Where(static item => item.Name is "05_fixed_texture_array" or "06_flattened_textures")) - { - ShaderEntry entry = testCase.Entries.Single(static item => item.Name == "fragmentMain"); - string source = Path.Combine(shaderDirectory, testCase.File); - - foreach (string target in new[] { "dxil", "spirv", "metal-source" }) - { - TargetConfig targetConfig = CreateTargetConfig(target, useSpvDescriptorHeapExt); - string[] compilerArguments = - [ - source, - "-I", shaderDirectory, - "-entry", entry.Name, - "-stage", entry.Stage, - "-matrix-layout-row-major", - .. targetConfig.Arguments - ]; - - Console.WriteLine($"[reflection:{targetConfig.Name}] {testCase.Name}::{entry.Name}"); - - byte[] shaderBytes = SlangCompiler.CompileWithReflection(compilerArguments, out SlangReflection reflection); - string outputName = $"{testCase.Name}.{targetConfig.Name}"; - File.WriteAllText(Path.Combine(outputDirectory, $"{outputName}.members.txt"), $"ShaderBytes: {shaderBytes.Length}" + Environment.NewLine + DescribeObject(reflection)); - - if (TryGetReflectionJson(reflection, out string reflectionJson)) - { - File.WriteAllText(Path.Combine(outputDirectory, $"{outputName}.reflection.json"), PrettyJson(reflectionJson)); - } - } - } - } - - private static string DescribeObject(object value) - { - Type type = value.GetType(); - List lines = [$"Type: {type.FullName}"]; - - foreach (System.Reflection.PropertyInfo property in type.GetProperties()) - { - object? propertyValue = property.GetValue(value); - lines.Add($"Property {property.Name}: {FormatMemberValue(propertyValue)}"); - } - - foreach (System.Reflection.FieldInfo field in type.GetFields()) - { - object? fieldValue = field.GetValue(value); - lines.Add($"Field {field.Name}: {FormatMemberValue(fieldValue)}"); - } - - return string.Join(Environment.NewLine, lines); - } - - private static string FormatMemberValue(object? value) => value switch - { - null => "", - byte[] bytes => $"byte[{bytes.Length}]", - string text => text.Length > 120 ? $"string[{text.Length}] {text[..120]}..." : $"string[{text.Length}] {text}", - _ => $"{value.GetType().FullName}: {value}" - }; - - private static bool TryGetReflectionJson(object value, out string reflectionJson) - { - Type type = value.GetType(); - - foreach (System.Reflection.PropertyInfo property in type.GetProperties()) - { - if (property.PropertyType == typeof(string) && IsLikelyReflectionMember(property.Name, property.GetValue(value) as string, out reflectionJson)) - { - return true; - } - } - - foreach (System.Reflection.FieldInfo field in type.GetFields()) - { - if (field.FieldType == typeof(string) && IsLikelyReflectionMember(field.Name, field.GetValue(value) as string, out reflectionJson)) - { - return true; - } - } - - reflectionJson = string.Empty; - return false; - } - - private static bool IsLikelyReflectionMember(string name, string? value, out string reflectionJson) - { - if (value is not null && - (name.Contains("Reflection", StringComparison.OrdinalIgnoreCase) || value.TrimStart().StartsWith('{') || value.TrimStart().StartsWith('['))) - { - reflectionJson = value; - return true; - } - - reflectionJson = string.Empty; - return false; - } - - private static string PrettyJson(string json) - { - using JsonDocument document = JsonDocument.Parse(json); - return JsonSerializer.Serialize(document, new JsonSerializerOptions { WriteIndented = true }); - } - - private static void CompileTarget(string shaderDirectory, string outputRoot, TargetConfig targetConfig) - { - string outputDirectory = Path.Combine(outputRoot, targetConfig.Name); - Directory.CreateDirectory(outputDirectory); - - Console.WriteLine($"[{targetConfig.Name}] args: {string.Join(' ', targetConfig.Arguments)}"); - - foreach (ShaderTest testCase in TestCases) - { - string source = Path.Combine(shaderDirectory, testCase.File); - - foreach (ShaderEntry entry in testCase.Entries) - { - string output = Path.Combine(outputDirectory, $"{testCase.Name}.{entry.Name}{targetConfig.OutputExtension}"); - string[] compilerArguments = - [ - source, - "-I", shaderDirectory, - "-entry", entry.Name, - "-stage", entry.Stage, - "-matrix-layout-row-major", - .. targetConfig.Arguments - ]; - - Console.WriteLine($"[{targetConfig.Name}] {testCase.Name}::{entry.Name}"); - - try - { - byte[] shaderBytes = SlangCompiler.Compile(compilerArguments); - File.WriteAllBytes(output, shaderBytes); - } - catch (Exception exception) when (IsMissingDxilCompiler(targetConfig, exception)) - { - throw new InvalidOperationException( - "DXIL compilation requires DXC/dxcompiler to be available to Slang. " + - "On macOS, run the SPIR-V or Metal-source targets for this experiment, or install DXC and make libdxcompiler loadable before running the dxil target.", - exception); - } - } - } - } - - private static string[] ExpandTargets(string target) - { - return target.Equals("all", StringComparison.OrdinalIgnoreCase) - ? CreateDefaultTargets() - : [target]; - } - - private static string[] CreateDefaultTargets() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return ["dxil", "spirv", "metal-source"]; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return ["spirv", "metal-source", "metal"]; - } - - return ["spirv", "metal-source"]; - } - - private static TargetConfig CreateTargetConfig(string target, bool useSpvDescriptorHeapExt) - { - return target.ToLowerInvariant() switch - { - "dxil" => new("dxil", ".dxil", [.. CreateTargetDefines("dxil"), "-target", "dxil", "-profile", "sm_6_6"]), - "spirv" => new("spirv", ".spv", [.. CreateTargetDefines("spirv"), .. CreateSpirvArguments(useSpvDescriptorHeapExt)]), - "metal-source" => new("metal-source", ".metal", [.. CreateTargetDefines("metal"), "-target", "metal"]), - "metal" => new("metal", ".metallib", [.. CreateTargetDefines("metal"), "-target", "metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]), - _ => throw new ArgumentException($"Unknown target '{target}'.") - }; - } - - private static string[] CreateTargetDefines(string target) - { - return target.ToLowerInvariant() switch - { - "dxil" => ["-DZENITH_TARGET_DX=1", "-DZENITH_TARGET_SPIRV=0", "-DZENITH_TARGET_METAL=0"], - "spirv" => ["-DZENITH_TARGET_DX=0", "-DZENITH_TARGET_SPIRV=1", "-DZENITH_TARGET_METAL=0"], - "metal" => ["-DZENITH_TARGET_DX=0", "-DZENITH_TARGET_SPIRV=0", "-DZENITH_TARGET_METAL=1"], - _ => throw new ArgumentException($"Unknown target '{target}'.") - }; - } - - private static string[] CreateSpirvArguments(bool useSpvDescriptorHeapExt) - { - List arguments = - [ - "-target", "spirv", - "-capability", "spirv_latest", - "-fvk-use-entrypoint-name", - "-bindless-space-index", "100" - ]; - - if (useSpvDescriptorHeapExt) - { - arguments.AddRange(["-capability", "spvDescriptorHeapEXT"]); - } - - return [.. arguments]; - } - - private static bool IsMissingDxilCompiler(TargetConfig targetConfig, Exception exception) - { - if (!targetConfig.Name.Equals("dxil", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - string message = exception.ToString(); - return message.Contains("dxc", StringComparison.OrdinalIgnoreCase) || - message.Contains("dxcompiler", StringComparison.OrdinalIgnoreCase) || - message.Contains("pass-through compiler", StringComparison.OrdinalIgnoreCase); - } - - private static string FindProjectRoot() - { - foreach (string seed in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory }) - { - DirectoryInfo? directory = new(Path.GetFullPath(seed)); - while (directory is not null) - { - if (File.Exists(Path.Combine(directory.FullName, "SlangResourceHeap.csproj"))) - { - return directory.FullName; - } - - directory = directory.Parent; - } - } - - throw new DirectoryNotFoundException("Could not locate SlangResourceHeap.csproj from the current directory or app base directory."); - } - - private static void PrintUsage() - { - Console.WriteLine("Usage: dotnet run --project SlangResourceHeap.csproj -- [all|dxil|spirv|metal-source|metal|reflection] [--spv-descriptor-heap-ext] [--clean]"); - Console.WriteLine("No arguments runs: all --clean --spv-descriptor-heap-ext."); - Console.WriteLine("Note: all includes dxil on Windows, metallib on macOS, and skips platform downstream targets elsewhere unless requested explicitly."); - } -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/README.md b/sources/Experiments/SlangResourceHeap/README.md deleted file mode 100644 index b658da05..00000000 --- a/sources/Experiments/SlangResourceHeap/README.md +++ /dev/null @@ -1,671 +0,0 @@ -# Zenith DescriptorHandle Minimal Tests - -This folder is independent from the Zenith.NET runtime projects. It contains a -small C# console app that references `Slangc.NET` directly, plus Slang shader -files. The current experiment follows the Slang-official `DescriptorHandle` -model instead of a Zenith-owned shader compatibility layer. - -The proposed public shape is: - -```text -ConstantBuffer + DescriptorHandle + backend-specific binding API -``` - -Compilation is driven through the NuGet `Slangc.NET` package instead of any -`slangc.exe` that may appear on `PATH`. - -## Route A Contract - -- Shader code uses Slang's native `DescriptorHandle` type directly. -- DXIL and SPIR-V may treat descriptor handles as integer descriptor slots. In - practice, Slang's default descriptor-handle dereference logic lowers to - direct heap indexing or bindless descriptor arrays. -- Metal must not be forced into the same integer ABI. Slang intentionally lowers - `DescriptorHandle` to the native layout of `T` on Metal: - - ```text - DescriptorHandle> -> texture2d - DescriptorHandle -> sampler - DescriptorHandle> -> device T* - StructuredBuffer>> -> texture2d<...> device* - ``` - -- Therefore the Metal backend should handle compatibility in the runtime API, - not in shader source. Metal should follow reflection and bind native resources - or fill Metal argument buffers, similar to Slang-RHI. - -The important design split is: - -```text -DX12/Vulkan: - resource.GetDescriptorHandle() -> integer slot -> setDescriptorHandle() - -Metal: - resource binding / ParameterBlock / argument buffer -> native resource id or gpu address -``` - -## Official Slang-RHI Behavior - -Slang-RHI exposes a host-side descriptor handle type: - -```cpp -struct DescriptorHandle -{ - DescriptorHandleType type; - uint64_t value; -}; -``` - -On D3D12 and Vulkan, `getDescriptorHandle` allocates a bindless descriptor slot -and `setDescriptorHandle` writes the 8-byte `value` into shader object uniform -data. - -On Metal, resource `getDescriptorHandle` is not implemented. The official API -support table marks Metal as unsupported for: - -- `IBuffer::getDescriptorHandle` -- `ITextureView::getDescriptorHandle` -- `ISampler::getDescriptorHandle` -- `IAccelerationStructure::getDescriptorHandle` - -The Metal backend instead binds native resources. Its argument-buffer path writes -Metal `gpuResourceID()` values for textures, samplers, and acceleration -structures, and writes `gpuAddress()` values for buffers. If residency sets are -not available, it tracks resources for per-encoder `useResource` fallback. - -Zenith should mirror that policy for Route A: keep shader code official, and let -the Metal API layer translate application resource bindings to Metal-native -layout. - -## Shader Usage - -Use `DescriptorHandle` in parameter structs, then let Slang implicitly -dereference handles when a resource value is needed. The official docs state -that `getDescriptorFromHandle` is not supposed to be called from user code -directly; it is a customization hook that an engine can provide to override the -default dereference behavior. - -```hlsl -struct DrawParams -{ - DescriptorHandle> BaseColorTexture; - DescriptorHandle LinearSampler; - float4 Tint; -}; - -ConstantBuffer gDraw : register(b0); - -float4 fragmentMain(float2 uv : TEXCOORD0) : SV_Target -{ - return gDraw.BaseColorTexture->Sample(gDraw.LinearSampler, uv) * gDraw.Tint; -} -``` - -When a descriptor handle value is loaded through a per-thread-varying path, mark -it non-uniform immediately before dereferencing: - -```hlsl -DescriptorHandle> handle = materials[index].BaseColorTexture; -float4 color = nonuniform(handle)->Sample(sampler, uv); -``` - -For buffer resources, the handle can be used as if it were the underlying -buffer: - -```hlsl -gBuffers.Destination[index] = gBuffers.Source[index] * scale; -``` - -## Resource Binding API Direction - -The C# side should expose a single backend-neutral `ResourceHandle` value as the -host counterpart of shader-side `DescriptorHandle`. It is not a custom Slang -shader type; shaders should keep using the official `DescriptorHandle`. - -```csharp -using System.Runtime.InteropServices; - -[StructLayout(LayoutKind.Sequential)] -public struct ResourceHandle -{ - public uint X; - public uint Y; -} - -public abstract class Buffer -{ - public ResourceHandle UniformHandle { get; } - public ResourceHandle StorageReadOnlyHandle { get; } - public ResourceHandle StorageReadWriteHandle { get; } -} - - public abstract class BufferView - { - public ResourceHandle UniformHandle { get; } - public ResourceHandle StorageReadOnlyHandle { get; } - public ResourceHandle StorageReadWriteHandle { get; } - } - -public abstract class Texture -{ - public ResourceHandle SampledHandle { get; } - public ResourceHandle StorageHandle { get; } -} - - public abstract class TextureView - { - public ResourceHandle SampledHandle { get; } - public ResourceHandle StorageHandle { get; } - } - -public abstract class Sampler -{ - public ResourceHandle Handle { get; } -} -``` - -Draw constants can then stay resource-oriented and backend-neutral: - -```csharp -public struct DrawParams -{ - public Matrix4x4 World; - public Matrix4x4 ViewProjection; - public ResourceHandle MaterialBuffer; - public ResourceHandle BaseColor; - public ResourceHandle LinearSampler; - public uint MaterialIndex; -} - -cmd.SetPipeline(pipeline); -cmd.SetConstants(new DrawParams -{ - World = world, - ViewProjection = camera.ViewProjection, - MaterialBuffer = materialBuffer.StorageReadOnlyHandle, - BaseColor = texture.SampledHandle, - LinearSampler = sampler.Handle, - MaterialIndex = materialIndex, -}); -cmd.DrawIndexed(indexCount); -``` - -Backend policy: - -- DX12/Vulkan: `ResourceHandle` stores descriptor-heap indexes, so - `SetConstants` can copy the struct bytes directly into uniform data. -- Metal: `SetConstants` must be reflection-aware. It copies ordinary POD - fields, resolves `ResourceHandle` fields to native Metal resources, and writes - those resources into a command/frame argument table. The user-facing C# struct - stays the same, but Metal does not upload the `ResourceHandle` bytes as the - final shader ABI. -- CPU-authored buffers must not contain shader-side `DescriptorHandle` values - when Metal is a target. Large material systems should store resource indices - and bind resource arrays or argument tables explicitly. - -This keeps the public API simple while preserving the official Slang rule: -shader source uses `DescriptorHandle`, and backend-specific ABI work happens -in the runtime binding layer. - -## AOT Binding Generation - -Metal needs resource-layout information, but that does not require runtime -.NET reflection. The preferred AOT path is to move reflection to build time: - -1. The user marks constants structs with `[Constants]` and declares them as - `partial`. -2. A shader build step compiles Slang and extracts reflection for each entry - point, including parameter blocks, `DescriptorHandle` fields, ordinary POD - fields, Metal argument-buffer indexes, resource kinds, access modes, and byte - offsets. -3. A C# source generator matches that shader reflection with user constants - structs such as `DrawParams` and validates that every `ResourceHandle` field - corresponds to the expected shader-side `DescriptorHandle` field. -4. The generator emits the other partial declaration that implements a static - layout interface. The runtime calls the static interface member instead of - inspecting fields through reflection. -5. The runtime command path constrains `SetConstants` to the layout - interface: - - ```csharp - cmd.SetConstants(in drawParams); - ``` - -The user-authored side stays small: - -```csharp -[Constants] -public partial struct DrawParams -{ - public Matrix4x4 World; - public Matrix4x4 ViewProjection; - public ResourceHandle Buffers; - public ResourceHandle BaseColor; - public ResourceHandle LinearSampler; - public uint MaterialIndex; -} -``` - -The generated side supplies the AOT layout contract: - -```csharp -public interface IConstantsLayout - where TSelf : unmanaged, IConstantsLayout -{ - static abstract ConstantsLayout GetLayout(); -} - -public partial struct DrawParams : IConstantsLayout -{ - public static ConstantsLayout GetLayout() => DrawParamsLayout.Value; -} -``` - -`ConstantsLayout` should be a manifest that contains per-backend layouts derived -from `SlangReflection.Json`, not a single universal byte layout. Fixed resource -arrays prove that DXIL, SPIR-V, and Metal can all differ for the same source. - -Then the public command API can be both simple and AOT-safe: - -```csharp -public void SetConstants(in TConstants constants) - where TConstants : unmanaged, IConstantsLayout -{ - ConstantsLayout layout = TConstants.GetLayout(); - BackendConstantsLayout backendLayout = layout.For(Context.Backend); - // DX/VK pack bytes from generated offsets; Metal consumes resource slots. -} -``` - -Generated layout shape: - -```csharp -file static class DrawParamsLayout -{ - public static readonly ConstantsLayout Value = new( - sizeInBytes: 160, - resourceFields: - [ - new("Buffers", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 128, metalIndex: 0), - new("BaseColor", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 136, metalIndex: 0), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 144, metalIndex: 0), - ]); -} -``` - -The runtime still has one generic implementation, but it consumes generated -metadata rather than reflection metadata: - -- DX12/Vulkan copy the bytes directly because the `ResourceHandle` values are - already descriptor indexes. -- Metal copies POD bytes and patches resource fields into the generated Metal - argument layout by resolving `ResourceHandle` to `MTLBuffer` GPU address, - `MTLTexture`, or `MTLSamplerState`. - -For maximum AOT friendliness, keep the generated path explicit. Do not discover -binders by scanning assemblies. Either call the generated binding type directly, -or have the shader/pipeline source generator emit pipeline factory code that -registers the manifest through ordinary static references. - -Important limitation: the layout interface cannot make this shader type portable -to Metal: - -```slang -DescriptorHandle>>> Textures; -``` - -DX12 and Vulkan can treat the buffer payload as a sequence of descriptor-handle -tokens, but Slang lowers the Metal version to a buffer of native texture -resources: - -```metal -texture2d device* Textures; -``` - -That is not an ordinary `MTLBuffer` payload that Zenith can assemble from a C# -`ResourceHandle[]`. A generator can know the recursive type, but it still cannot -write texture objects into a structured-buffer byte stream for Metal. - -The AOT generator should therefore diagnose this pattern when Metal is enabled, -for example: - -```text -ZENITH0001: StructuredBuffer> is not portable to Metal. -Use uint resource indices plus an explicit resource table/argument table. -``` - -The portable model is: - -```slang -struct TextureArrayConstants -{ - DescriptorHandle> TextureIndices; - DescriptorHandle LinearSampler; - uint TextureCount; - float UvScale; -}; - -// Bound by the generated pipeline binding manifest. -Texture2D gTextureTable[]; - -ConstantBuffer gTextures; - -float4 sampleTexture(uint logicalIndex, float2 uv) -{ - uint textureIndex = gTextures.TextureIndices[logicalIndex]; - return gTextureTable[nonuniform(textureIndex)].Sample(gTextures.LinearSampler, uv); -} -``` - -The generated constants layout then only describes the index buffer and sampler: - -```csharp -file static class TextureIndexParamsLayout -{ - public static readonly ConstantsLayout Value = new( - sizeInBytes: 32, - resourceFields: - [ - new("TextureIndices", ResourceKind.Buffer, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 8, metalIndex: 0), - ]); -} -``` - -The texture table itself is a pipeline/resource-table binding generated from -shader reflection. DX12/Vulkan may still implement it as descriptor heap indexes; -Metal implements it as an argument table/resource array. Users who do not want -the generator can manually implement `IConstantsLayout`, but they still need -to avoid `StructuredBuffer>` for portable Metal shaders. - -Fixed-size arrays are different. This shader shape is portable as a small, -compile-time-sized binding group: - -```slang -static const uint TextureSlotCount = 4; - -struct FixedTextureArrayConstants -{ - DescriptorHandle> Textures[TextureSlotCount]; - DescriptorHandle LinearSampler; - uint TextureIndex; - float UvScale; -}; -``` - -Slang lowers it to `uint2 Textures[4]` for HLSL/DXIL-style targets and to a -Metal native resource array: - -```metal -array, int(4)> Textures; -``` - -`SlangCompiler.CompileWithReflection(args, out SlangReflection reflection)` -returns the compiled bytes and exposes `reflection.Json`. For this fixed array, -the JSON shows that every backend needs its own generated layout: - -```text -DXIL: - Textures uniform offset 0, size 56, elementStride 16 - LinearSampler uniform offset 56, size 8 - TextureIndex uniform offset 64, size 4 - UvScale uniform offset 68, size 4 - CBO size 72 - -SPIR-V + spvDescriptorHeapEXT: - Textures uniform offset 0, size 64, elementStride 16 - LinearSampler uniform offset 64, size 8 - TextureIndex uniform offset 72, size 4 - UvScale uniform offset 76, size 4 - CBO size 80 - -Metal: - Textures shaderResource index 0, count 4 - LinearSampler samplerState index 0 - TextureIndex uniform offset 0, size 4 - UvScale uniform offset 4, size 4 - CBO size 8 -``` - -That means fixed arrays can be supported, but they are not ordinary tight -`ResourceHandle[]` data on every backend. Even DXIL and SPIR-V differ in the -uniform size for the same Slang source. The source generator must consume Slang -reflection per target and emit a per-backend layout, not just preserve C# struct -field order. - -The generated layout should therefore distinguish source offsets, shader uniform -offsets, array strides, and Metal resource slots. A simplified SPIR-V layout -looks like this: - -```csharp -file static class FixedTextureArrayParamsLayout -{ - public static readonly ConstantsLayout Spirv = new( - sizeInBytes: 80, - resourceFields: - [ - new("Textures", ResourceKind.Texture, ResourceAccess.Read, offsetInBytes: 0, metalIndex: 0, count: 4, strideInBytes: 16), - new("LinearSampler", ResourceKind.Sampler, ResourceAccess.Read, offsetInBytes: 64, metalIndex: 0), - ]); -} -``` - -The fixed-length C# value type can also be generated. A source generator cannot -rewrite a field type, so this is not possible: - -```csharp -[ResourceHandleArray(4)] -public ResourceHandle Textures; // Cannot be rewritten into an inline array. -``` - -Instead, let the user declare a named partial value type and let the generator -complete it: - -```csharp -[ResourceHandleArray(4)] -public partial struct TextureHandle4 -{ -} -``` - -Generated source: - -```csharp -[InlineArray(4)] -public partial struct TextureHandle4 -{ - private ResourceHandle element0; -} -``` - -Then constants structs use the generated value type: - -```csharp -[Constants] -public partial struct FixedTextureArrayParams -{ - public TextureHandle4 Textures; - public ResourceHandle LinearSampler; - public uint TextureIndex; - public float UvScale; -} -``` - -`TextureHandle4` is unmanaged and indexable, but the user does not write the -`InlineArray` backing field. The generator can ship common predefined types such -as `ResourceHandle2`, `ResourceHandle4`, `ResourceHandle8`, and -`ResourceHandle16`, and also support project-local marker declarations for less -common lengths. Shader reflection still decides whether a field named `Textures` -matches `DescriptorHandle>[4]`, so the array type itself does -not need to encode texture/buffer/sampler kind. - -Metal operation model: - -- The Metal constants buffer only contains ordinary POD fields here: - `TextureIndex` and `UvScale`. -- `Textures[0..3]` are flattened into four reflected texture resource slots. -- `SetConstants` reads the four C# `ResourceHandle` values, resolves them to - `MTLTexture` objects or resource IDs, and writes them into the command/frame - argument table at `metalIndex + elementIndex`. -- The sampler is resolved separately into the sampler table. - -This is a good model for small fixed groups such as four material textures. It -is not a good model for large or frequently changing arrays, because Metal work -is proportional to the fixed count. For large sets, use `uint` indices plus a -resource table/argument table object that can be cached and rebound. - -Flattening the fixed group into named fields avoids the constant-buffer array -stride difference between DXIL and SPIR-V: - -```slang -struct FlattenedTextureConstants -{ - DescriptorHandle> Texture0; - DescriptorHandle> Texture1; - DescriptorHandle> Texture2; - DescriptorHandle> Texture3; - DescriptorHandle LinearSampler; - uint TextureIndex; - float UvScale; -}; -``` - -Reflection for both DXIL and SPIR-V reports the same uniform layout: - -```text -Texture0 offset 0, size 8 -Texture1 offset 8, size 8 -Texture2 offset 16, size 8 -Texture3 offset 24, size 8 -LinearSampler offset 32, size 8 -TextureIndex offset 40, size 4 -UvScale offset 44, size 4 -CBO size 48 -``` - -Metal still removes the resource handles from the constant bytes, but the -reflection is straightforward: - -```text -Texture0 shaderResource index 0 -Texture1 shaderResource index 1 -Texture2 shaderResource index 2 -Texture3 shaderResource index 3 -LinearSampler samplerState index 0 -TextureIndex uniform offset 0, size 4 -UvScale uniform offset 4, size 4 -CBO size 8 -``` - -So for small fixed material groups, flattened fields are simpler than -`DescriptorHandle[N]`: DX/VK get a compact stable layout, and Metal gets the -same four resource slots. The tradeoff is shader ergonomics; users need a small -generated accessor or a switch helper when indexing dynamically. - -## Metal API Implications - -The experiment still compiles Metal source for `DescriptorHandle` so we can -inspect Slang's generated MSL. That does not mean Zenith should upload the same -CPU byte layout to Metal. - -For Metal, Zenith should build a pipeline binding manifest from Slang -reflection: - -- Ordinary POD fields are copied into Metal constant data. -- Texture fields are bound as `MTLTexture` or written as argument-buffer resource - IDs. -- Sampler fields are bound as `MTLSamplerState` or written as argument-buffer - resource IDs. -- Buffer fields are bound as `MTLBuffer`/offset or written as GPU addresses. -- Pointer/resource fields referenced from ordinary data must be included in - residency tracking. - -That means a high-level call can stay simple: - -```csharp -cmd.SetPipeline(pipeline); -cmd.SetConstants(drawParams); -cmd.DrawIndexed(...); -``` - -but `SetConstants` is backend-aware: - -- DX12/Vulkan encode descriptor slot integers into the uniform data. -- Metal resolves resource references and populates native bindings or argument - buffers according to reflection. - -## Test Cases - -1. `Shaders/01_cbo_texture_sampler.slang` - - Fragment shader samples a `DescriptorHandle>` and - `DescriptorHandle` stored in a CBO. -2. `Shaders/02_cbo_buffer_uav.slang` - - Compute shader reads a `DescriptorHandle>` and - writes a `DescriptorHandle>` stored in a CBO. -3. `Shaders/03_nonuniform_material_texture.slang` - - Fragment shader reads a material record from a structured-buffer handle. - Each material contains a `DescriptorHandle>`. -4. `Shaders/04_texture_handle_array.slang` - - Fragment shader reads a descriptor handle directly from a structured buffer - of texture handles: - `DescriptorHandle>>>`. -5. `Shaders/05_fixed_texture_array.slang` - - Fragment shader samples from a fixed-size - `DescriptorHandle>[4]` array in a constants block. - This is portable as a reflected fixed resource group; Metal lowers it to a - native `array, 4>`. -6. `Shaders/06_flattened_textures.slang` - - Fragment shader samples from four named texture handle fields. DXIL and - SPIR-V both report a compact 48-byte uniform layout, while Metal reports - four texture resource slots plus an 8-byte POD constant block. - -The fourth case is intentionally a stress case. It is natural on DX12/Vulkan, -but Metal lowers the nested texture handles to native texture-resource layout. -Zenith should treat it as a compile-time lowering inspection, not as a portable -Metal runtime binding model. For large material systems, use `uint` material -texture indices plus API-bound texture arrays or argument buffers. - -## Compile - -Start the C# project directly to run the default platform test matrix. No -arguments is equivalent to `all --clean --spv-descriptor-heap-ext`: - -```powershell -dotnet run --project SlangResourceHeap.csproj -``` - -Use explicit arguments when you want a narrower target: - -```powershell -dotnet run --project SlangResourceHeap.csproj -- all --clean -dotnet run --project SlangResourceHeap.csproj -- dxil -dotnet run --project SlangResourceHeap.csproj -- spirv -dotnet run --project SlangResourceHeap.csproj -- spirv --spv-descriptor-heap-ext -dotnet run --project SlangResourceHeap.csproj -- metal-source -dotnet run --project SlangResourceHeap.csproj -- metal -``` - -The `all` target is host-aware: - -- Windows: DXIL, SPIR-V, and Metal source. -- macOS: SPIR-V, Metal source, and `metallib`. -- Linux: SPIR-V and Metal source. - -`metal-source` emits MSL and does not require Apple's Metal compiler. `metal` -emits `metallib` with `-target metallib -capability metallib_latest` and passes -`-std=metal4.0` to Apple's downstream `metal` compiler, so it is the target to -use when validating the Apple Metal 4 library path. On macOS and Linux, `all` -skips DXIL because Slang's DXIL target needs a loadable DXC / `dxcompiler` -downstream compiler. Run `dxil` explicitly only on a machine where DXC is -installed and loadable by Slang. - -The `metal` target emits `metallib` and is intended for a machine with the Metal -downstream compiler available. The project writes outputs under `out/`. It does -not run any host-side binding code. - -If `metal-source` succeeds but `metal` fails on a non-Apple machine, the missing -piece is the platform Metal compiler rather than the shader syntax. - -This experiment uses the repository's central `Slangc.NET` package version, so -updating `sources/Directory.Packages.props` is enough to move the compiler used -by the test app. \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang b/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang deleted file mode 100644 index 89af3d2d..00000000 --- a/sources/Experiments/SlangResourceHeap/Shaders/01_cbo_texture_sampler.slang +++ /dev/null @@ -1,33 +0,0 @@ -struct PassConstants -{ - DescriptorHandle> AlbedoTexture; - DescriptorHandle LinearSampler; - float4 Tint; -}; - -struct FullscreenVertex -{ - float4 Position : SV_POSITION; - float2 Uv : TEXCOORD0; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer gPass : register(b0); - -[shader("vertex")] -FullscreenVertex vertexMain(uint vertexId : SV_VertexID) -{ - float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); - - FullscreenVertex output; - output.Position = float4(position, 0.0, 1.0); - output.Uv = position * 0.5 + 0.5; - - return output; -} - -[shader("fragment")] -float4 fragmentMain(FullscreenVertex input) : SV_TARGET -{ - return gPass.AlbedoTexture->Sample(gPass.LinearSampler, input.Uv) * gPass.Tint; -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang b/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang deleted file mode 100644 index b6eb8fbb..00000000 --- a/sources/Experiments/SlangResourceHeap/Shaders/02_cbo_buffer_uav.slang +++ /dev/null @@ -1,23 +0,0 @@ -struct BufferConstants -{ - DescriptorHandle> Source; - DescriptorHandle> Destination; - uint Count; - float4 Scale; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer gBuffers : register(b0); - -[shader("compute")] -[numthreads(64, 1, 1)] -void computeMain(uint3 dispatchThreadId : SV_DispatchThreadID) -{ - uint index = dispatchThreadId.x; - if (index >= gBuffers.Count) - { - return; - } - - gBuffers.Destination[index] = gBuffers.Source[index] * gBuffers.Scale; -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang b/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang deleted file mode 100644 index d78edcf6..00000000 --- a/sources/Experiments/SlangResourceHeap/Shaders/03_nonuniform_material_texture.slang +++ /dev/null @@ -1,45 +0,0 @@ -struct MaterialRecord -{ - DescriptorHandle> BaseColorTexture; - float4 Tint; -}; - -struct MaterialConstants -{ - DescriptorHandle> Materials; - DescriptorHandle LinearSampler; - uint MaterialCount; - float UvScale; -}; - -struct FullscreenVertex -{ - float4 Position : SV_POSITION; - float2 Uv : TEXCOORD0; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer gMaterials : register(b0); - -[shader("vertex")] -FullscreenVertex vertexMain(uint vertexId : SV_VertexID) -{ - float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); - - FullscreenVertex output; - output.Position = float4(position, 0.0, 1.0); - output.Uv = position * 0.5 + 0.5; - - return output; -} - -[shader("fragment")] -float4 fragmentMain(FullscreenVertex input) : SV_TARGET -{ - uint count = max(gMaterials.MaterialCount, 1u); - uint materialIndex = min(uint(input.Uv.x * float(count)), count - 1u); - MaterialRecord material = gMaterials.Materials[materialIndex]; - SamplerState linearSampler = gMaterials.LinearSampler; - - return nonuniform(material.BaseColorTexture)->Sample(linearSampler, input.Uv * gMaterials.UvScale) * material.Tint; -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang b/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang deleted file mode 100644 index 0540eaee..00000000 --- a/sources/Experiments/SlangResourceHeap/Shaders/04_texture_handle_array.slang +++ /dev/null @@ -1,39 +0,0 @@ -struct TextureArrayConstants -{ - DescriptorHandle>>> Textures; - DescriptorHandle LinearSampler; - uint TextureCount; - float UvScale; -}; - -struct FullscreenVertex -{ - float4 Position : SV_POSITION; - float2 Uv : TEXCOORD0; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer gTextures : register(b0); - -[shader("vertex")] -FullscreenVertex vertexMain(uint vertexId : SV_VertexID) -{ - float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); - - FullscreenVertex output; - output.Position = float4(position, 0.0, 1.0); - output.Uv = position * 0.5 + 0.5; - - return output; -} - -[shader("fragment")] -float4 fragmentMain(FullscreenVertex input) : SV_TARGET -{ - uint count = max(gTextures.TextureCount, 1u); - uint textureIndex = min(uint(input.Uv.x * float(count)), count - 1u); - DescriptorHandle> textureHandle = gTextures.Textures[textureIndex]; - SamplerState linearSampler = gTextures.LinearSampler; - - return nonuniform(textureHandle)->Sample(linearSampler, input.Uv * gTextures.UvScale); -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang b/sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang deleted file mode 100644 index 70895286..00000000 --- a/sources/Experiments/SlangResourceHeap/Shaders/05_fixed_texture_array.slang +++ /dev/null @@ -1,39 +0,0 @@ -static const uint TextureSlotCount = 4; - -struct FixedTextureArrayConstants -{ - DescriptorHandle> Textures[TextureSlotCount]; - DescriptorHandle LinearSampler; - uint TextureIndex; - float UvScale; -}; - -struct FullscreenVertex -{ - float4 Position : SV_POSITION; - float2 Uv : TEXCOORD0; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer gTextures : register(b0); - -[shader("vertex")] -FullscreenVertex vertexMain(uint vertexId : SV_VertexID) -{ - float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); - - FullscreenVertex output; - output.Position = float4(position, 0.0, 1.0); - output.Uv = position * 0.5 + 0.5; - - return output; -} - -[shader("fragment")] -float4 fragmentMain(FullscreenVertex input) : SV_TARGET -{ - uint textureIndex = min(gTextures.TextureIndex, TextureSlotCount - 1u); - DescriptorHandle> textureHandle = gTextures.Textures[textureIndex]; - - return nonuniform(textureHandle)->Sample(gTextures.LinearSampler, input.Uv * gTextures.UvScale); -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang b/sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang deleted file mode 100644 index f4fc36f8..00000000 --- a/sources/Experiments/SlangResourceHeap/Shaders/06_flattened_textures.slang +++ /dev/null @@ -1,54 +0,0 @@ -struct FlattenedTextureConstants -{ - DescriptorHandle> Texture0; - DescriptorHandle> Texture1; - DescriptorHandle> Texture2; - DescriptorHandle> Texture3; - DescriptorHandle LinearSampler; - uint TextureIndex; - float UvScale; -}; - -struct FullscreenVertex -{ - float4 Position : SV_POSITION; - float2 Uv : TEXCOORD0; -}; - -[[vk::binding(0, 0)]] -ConstantBuffer gTextures : register(b0); - -[shader("vertex")] -FullscreenVertex vertexMain(uint vertexId : SV_VertexID) -{ - float2 position = float2(vertexId == 2 ? 3.0 : -1.0, vertexId == 1 ? 3.0 : -1.0); - - FullscreenVertex output; - output.Position = float4(position, 0.0, 1.0); - output.Uv = position * 0.5 + 0.5; - - return output; -} - -DescriptorHandle> selectTexture(uint textureIndex) -{ - switch (min(textureIndex, 3u)) - { - case 0u: - return gTextures.Texture0; - case 1u: - return gTextures.Texture1; - case 2u: - return gTextures.Texture2; - default: - return gTextures.Texture3; - } -} - -[shader("fragment")] -float4 fragmentMain(FullscreenVertex input) : SV_TARGET -{ - DescriptorHandle> textureHandle = selectTexture(gTextures.TextureIndex); - - return nonuniform(textureHandle)->Sample(gTextures.LinearSampler, input.Uv * gTextures.UvScale); -} \ No newline at end of file diff --git a/sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj b/sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj deleted file mode 100644 index 42b3df31..00000000 --- a/sources/Experiments/SlangResourceHeap/SlangResourceHeap.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - $(StandardTargetFramework) - - - - - - - \ No newline at end of file From 6bf71c749e6748c91a94ef00def7dab8f0c11863 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 18:29:15 +0800 Subject: [PATCH 068/466] Refactor structs, constants layout, and validation Replaces record structs with regular structs for all graphics data types and removes with expressions. Refactors BlendStates, DepthStencilStates, and RasterizerStates to use explicit struct initialization. Renames IConstantsLayout static members for clarity. Introduces a static LeaseLifetime for buffer leasing. Updates CommandSubmission and Submitted to regular structs with init properties. Adds reflection-based validation and caching for constants layouts in ValidationLayer, along with new error messages. Includes minor refactoring and consistency improvements throughout. --- sources/Zenith.NET/BlendStates.cs | 40 +++++++--- sources/Zenith.NET/CommandBuffer.cs | 2 +- sources/Zenith.NET/CommandQueue.cs | 11 ++- sources/Zenith.NET/Constants.cs | 18 +++-- sources/Zenith.NET/DepthStencilStates.cs | 68 +++++++++++++++-- sources/Zenith.NET/Downloader.cs | 6 +- .../Zenith.NET/Interfaces/IConstantsLayout.cs | 12 +-- sources/Zenith.NET/RasterizerStates.cs | 63 +++++++++++++--- .../Zenith.NET/Structs/AttachmentFormats.cs | 2 +- sources/Zenith.NET/Structs/BlendState.cs | 2 +- .../BottomLevelAccelerationStructureDesc.cs | 2 +- sources/Zenith.NET/Structs/BufferData.cs | 2 +- sources/Zenith.NET/Structs/BufferDesc.cs | 2 +- sources/Zenith.NET/Structs/BufferViewDesc.cs | 2 +- sources/Zenith.NET/Structs/ColorAttachment.cs | 2 +- .../Structs/ColorAttachmentBlendState.cs | 2 +- .../Zenith.NET/Structs/CommandSubmission.cs | 6 +- .../Zenith.NET/Structs/ComputePipelineDesc.cs | 2 +- .../Structs/DepthStencilAttachment.cs | 2 +- .../Zenith.NET/Structs/DepthStencilState.cs | 2 +- sources/Zenith.NET/Structs/Extent3D.cs | 2 +- .../Structs/GraphicsPipelineDesc.cs | 2 +- .../Structs/IndirectDispatchArgs.cs | 2 +- .../Structs/IndirectDispatchMeshArgs.cs | 2 +- .../Zenith.NET/Structs/IndirectDrawArgs.cs | 2 +- .../Structs/IndirectDrawIndexedArgs.cs | 2 +- sources/Zenith.NET/Structs/InputElement.cs | 2 +- sources/Zenith.NET/Structs/InputLayout.cs | 2 +- sources/Zenith.NET/Structs/MappedMemory.cs | 7 +- .../Structs/MeshShadingPipelineDesc.cs | 2 +- sources/Zenith.NET/Structs/Offset3D.cs | 2 +- sources/Zenith.NET/Structs/QueryHeapDesc.cs | 2 +- sources/Zenith.NET/Structs/RasterizerState.cs | 2 +- .../Structs/RayTracingAabbGeometry.cs | 2 +- .../Zenith.NET/Structs/RayTracingGeometry.cs | 2 +- .../Zenith.NET/Structs/RayTracingInstance.cs | 2 +- .../Structs/RayTracingTriangleGeometry.cs | 2 +- sources/Zenith.NET/Structs/RenderState.cs | 2 +- sources/Zenith.NET/Structs/SamplerDesc.cs | 2 +- sources/Zenith.NET/Structs/Scissor.cs | 2 +- sources/Zenith.NET/Structs/ShaderDesc.cs | 2 +- .../Zenith.NET/Structs/StencilFaceState.cs | 2 +- sources/Zenith.NET/Structs/Surface.cs | 2 +- sources/Zenith.NET/Structs/SwapChainDesc.cs | 2 +- sources/Zenith.NET/Structs/TextureData.cs | 2 +- .../Zenith.NET/Structs/TextureDataLayout.cs | 2 +- sources/Zenith.NET/Structs/TextureDesc.cs | 2 +- .../Zenith.NET/Structs/TextureSubresource.cs | 2 +- .../Structs/TextureSubresourceLayers.cs | 2 +- .../Structs/TextureSubresourceRange.cs | 2 +- sources/Zenith.NET/Structs/TextureViewDesc.cs | 2 +- .../TopLevelAccelerationStructureDesc.cs | 2 +- sources/Zenith.NET/Structs/Viewport.cs | 2 +- sources/Zenith.NET/Uploader.cs | 6 +- sources/Zenith.NET/ValidationLayer.cs | 74 ++++++++++++++++++- 55 files changed, 303 insertions(+), 96 deletions(-) diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs index 7b5d8d71..1a67ccef 100644 --- a/sources/Zenith.NET/BlendStates.cs +++ b/sources/Zenith.NET/BlendStates.cs @@ -22,13 +22,16 @@ public static class BlendStates { IsAlphaToCoverageEnabled = false, IsIndependentBlendEnabled = false, - ColorAttachment0 = Default.ColorAttachment0 with + ColorAttachment0 = new() { IsBlendingEnabled = true, SrcRgbFactor = BlendFactor.SourceAlpha, DstRgbFactor = BlendFactor.One, + RgbOp = BlendOp.Add, SrcAlphaFactor = BlendFactor.SourceAlpha, - DstAlphaFactor = BlendFactor.One + DstAlphaFactor = BlendFactor.One, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All } }; @@ -36,13 +39,16 @@ public static class BlendStates { IsAlphaToCoverageEnabled = false, IsIndependentBlendEnabled = false, - ColorAttachment0 = Default.ColorAttachment0 with + ColorAttachment0 = new() { IsBlendingEnabled = true, SrcRgbFactor = BlendFactor.One, DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + RgbOp = BlendOp.Add, SrcAlphaFactor = BlendFactor.One, - DstAlphaFactor = BlendFactor.OneMinusSourceAlpha + DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All } }; @@ -50,13 +56,16 @@ public static class BlendStates { IsAlphaToCoverageEnabled = false, IsIndependentBlendEnabled = false, - ColorAttachment0 = Default.ColorAttachment0 with + ColorAttachment0 = new() { IsBlendingEnabled = true, SrcRgbFactor = BlendFactor.SourceAlpha, DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + RgbOp = BlendOp.Add, SrcAlphaFactor = BlendFactor.SourceAlpha, - DstAlphaFactor = BlendFactor.OneMinusSourceAlpha + DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All } }; @@ -64,9 +73,16 @@ public static class BlendStates { IsAlphaToCoverageEnabled = false, IsIndependentBlendEnabled = false, - ColorAttachment0 = Default.ColorAttachment0 with + ColorAttachment0 = new() { - IsBlendingEnabled = false + IsBlendingEnabled = false, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.Zero, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.Zero, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All } }; @@ -74,9 +90,15 @@ public static class BlendStates { IsAlphaToCoverageEnabled = false, IsIndependentBlendEnabled = false, - ColorAttachment0 = Default.ColorAttachment0 with + ColorAttachment0 = new() { IsBlendingEnabled = false, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.Zero, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.Zero, + AlphaOp = BlendOp.Add, ColorWrites = ColorWrites.None } }; diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index d9f88db4..f0f2fb4c 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -552,7 +552,7 @@ private bool TryGetCurrentPipeline(string commandName, out Pipeline pipeline) return true; } - Context.ValidationLayer?.ValidateCurrentPipeline(commandName, currentPipeline); + Context.ValidationLayer?.ValidateCurrentPipeline(commandName); pipeline = null!; return false; diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 71200616..8288b6b8 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -29,9 +29,9 @@ internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan available = []; private readonly Dictionary> borrowed = []; @@ -75,9 +77,9 @@ private uint SizeInBytes() where T : unmanaged, IConstantsLayout { return context.GraphicsApi switch { - GraphicsApi.DirectX12 => T.SizeInBytesOnDirectX12, - GraphicsApi.Metal => T.SizeInBytesOnMetal, - GraphicsApi.Vulkan => T.SizeInBytesOnVulkan, + GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, + GraphicsApi.Metal => T.MetalSizeInBytes, + GraphicsApi.Vulkan => T.VulkanSizeInBytes, _ => 0 }; } @@ -87,15 +89,15 @@ private void Write(T data, Buffer buffer) where T : unmanaged, IConstantsLayo switch (context.GraphicsApi) { case GraphicsApi.DirectX12: - T.DirectX12(data, buffer); + T.WriteDirectX12(data, buffer); break; case GraphicsApi.Metal: - T.Metal(data, buffer); + T.WriteMetal(data, buffer); break; case GraphicsApi.Vulkan: - T.Vulkan(data, buffer); + T.WriteVulkan(data, buffer); break; } } @@ -107,7 +109,7 @@ private void CleanupExpiredLeases() private class Lease(Buffer buffer) { - private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; public Buffer Buffer { get; } = buffer; @@ -130,7 +132,7 @@ public bool TryExpire() public Lease Renew() { - expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + expirationTime = DateTime.UtcNow + LeaseLifetime; return this; } diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs index 63926cdd..d554cc21 100644 --- a/sources/Zenith.NET/DepthStencilStates.cs +++ b/sources/Zenith.NET/DepthStencilStates.cs @@ -26,19 +26,75 @@ public static class DepthStencilStates } }; - public static readonly DepthStencilState DefaultInverted = Default with + public static readonly DepthStencilState DefaultInverted = new() { - DepthCompareOp = CompareOp.GreaterEqual + IsDepthEnabled = true, + IsDepthWriteEnabled = true, + DepthCompareOp = CompareOp.GreaterEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + }, + BackFace = new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + } }; - public static readonly DepthStencilState DepthRead = Default with + public static readonly DepthStencilState DepthRead = new() { - IsDepthWriteEnabled = false + IsDepthEnabled = true, + IsDepthWriteEnabled = false, + DepthCompareOp = CompareOp.LessEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + }, + BackFace = new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + } }; - public static readonly DepthStencilState None = Default with + public static readonly DepthStencilState None = new() { IsDepthEnabled = false, - IsDepthWriteEnabled = false + IsDepthWriteEnabled = false, + DepthCompareOp = CompareOp.LessEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + }, + BackFace = new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + } }; } diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index 9a4b1469..d9d5d38d 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -2,6 +2,8 @@ internal class Downloader(GraphicsContext context) : DisposableObject { + private static readonly TimeSpan LeaseLifetime = TimeSpan.FromSeconds(120); + private readonly Lock @lock = new(); private readonly List available = []; private readonly Dictionary> borrowed = []; @@ -67,7 +69,7 @@ private void CleanupExpiredLeases() private class Lease(Buffer buffer) { - private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; private BufferData data; @@ -106,7 +108,7 @@ public bool TryExpire() public Lease Renew() { - expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + expirationTime = DateTime.UtcNow + LeaseLifetime; data = default; diff --git a/sources/Zenith.NET/Interfaces/IConstantsLayout.cs b/sources/Zenith.NET/Interfaces/IConstantsLayout.cs index a449aed5..4c8300e3 100644 --- a/sources/Zenith.NET/Interfaces/IConstantsLayout.cs +++ b/sources/Zenith.NET/Interfaces/IConstantsLayout.cs @@ -2,15 +2,15 @@ public interface IConstantsLayout where T : unmanaged, IConstantsLayout { - static abstract uint SizeInBytesOnDirectX12 { get; } + static abstract uint DirectX12SizeInBytes { get; } - static abstract uint SizeInBytesOnMetal { get; } + static abstract uint MetalSizeInBytes { get; } - static abstract uint SizeInBytesOnVulkan { get; } + static abstract uint VulkanSizeInBytes { get; } - static abstract void DirectX12(T data, Buffer dst); + static abstract void WriteDirectX12(T data, Buffer dst); - static abstract void Metal(T data, Buffer dst); + static abstract void WriteMetal(T data, Buffer dst); - static abstract void Vulkan(T data, Buffer dst); + static abstract void WriteVulkan(T data, Buffer dst); } diff --git a/sources/Zenith.NET/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs index 00cf959d..d4274ddb 100644 --- a/sources/Zenith.NET/RasterizerStates.cs +++ b/sources/Zenith.NET/RasterizerStates.cs @@ -14,36 +14,75 @@ public static class RasterizerStates IsScissorEnabled = false }; - public static readonly RasterizerState CullFront = Default with + public static readonly RasterizerState CullFront = new() { - CullMode = CullMode.Front + FillMode = FillMode.Solid, + CullMode = CullMode.Front, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false }; - public static readonly RasterizerState CullBack = Default with + public static readonly RasterizerState CullBack = new() { - CullMode = CullMode.Back + FillMode = FillMode.Solid, + CullMode = CullMode.Back, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false }; - public static readonly RasterizerState CullNone = Default with + public static readonly RasterizerState CullNone = new() { - CullMode = CullMode.None + FillMode = FillMode.Solid, + CullMode = CullMode.None, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false }; - public static readonly RasterizerState WireframeCullFront = Default with + public static readonly RasterizerState WireframeCullFront = new() { FillMode = FillMode.Wireframe, - CullMode = CullMode.Front + CullMode = CullMode.Front, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false }; - public static readonly RasterizerState WireframeCullBack = Default with + public static readonly RasterizerState WireframeCullBack = new() { FillMode = FillMode.Wireframe, - CullMode = CullMode.Back + CullMode = CullMode.Back, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false }; - public static readonly RasterizerState Wireframe = Default with + public static readonly RasterizerState Wireframe = new() { FillMode = FillMode.Wireframe, - CullMode = CullMode.None + CullMode = CullMode.None, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false }; } diff --git a/sources/Zenith.NET/Structs/AttachmentFormats.cs b/sources/Zenith.NET/Structs/AttachmentFormats.cs index 69b53784..46417a15 100644 --- a/sources/Zenith.NET/Structs/AttachmentFormats.cs +++ b/sources/Zenith.NET/Structs/AttachmentFormats.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct AttachmentFormats +public struct AttachmentFormats { public PixelFormat[] ColorFormats; diff --git a/sources/Zenith.NET/Structs/BlendState.cs b/sources/Zenith.NET/Structs/BlendState.cs index 3bee595c..7fe5a337 100644 --- a/sources/Zenith.NET/Structs/BlendState.cs +++ b/sources/Zenith.NET/Structs/BlendState.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BlendState +public struct BlendState { public bool IsAlphaToCoverageEnabled; diff --git a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs index 8eb03634..fad42b00 100644 --- a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs +++ b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BottomLevelAccelerationStructureDesc +public struct BottomLevelAccelerationStructureDesc { public RayTracingGeometry[] Geometries; diff --git a/sources/Zenith.NET/Structs/BufferData.cs b/sources/Zenith.NET/Structs/BufferData.cs index 91f2d623..cf8a128f 100644 --- a/sources/Zenith.NET/Structs/BufferData.cs +++ b/sources/Zenith.NET/Structs/BufferData.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BufferData +public struct BufferData { public nint Pointer; diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index cd67d952..d664e529 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BufferDesc +public struct BufferDesc { public uint SizeInBytes; diff --git a/sources/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Zenith.NET/Structs/BufferViewDesc.cs index 472c771a..60646491 100644 --- a/sources/Zenith.NET/Structs/BufferViewDesc.cs +++ b/sources/Zenith.NET/Structs/BufferViewDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct BufferViewDesc +public struct BufferViewDesc { public Buffer Buffer; diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 8a690199..13d0be2b 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -2,7 +2,7 @@ namespace Zenith.NET; -public record struct ColorAttachment +public struct ColorAttachment { public Texture Texture; diff --git a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs index aff3995c..4e9a51d4 100644 --- a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct ColorAttachmentBlendState +public struct ColorAttachmentBlendState { public bool IsBlendingEnabled; diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs index de8e65e3..9f16718f 100644 --- a/sources/Zenith.NET/Structs/CommandSubmission.cs +++ b/sources/Zenith.NET/Structs/CommandSubmission.cs @@ -1,7 +1,11 @@ namespace Zenith.NET; -public readonly record struct CommandSubmission(CommandQueue? Queue, ulong Value) +public readonly struct CommandSubmission { + public CommandQueue? Queue { get; init; } + + public ulong Value { get; init; } + public void Wait() { Queue?.Wait(Value); diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index e1dd6033..9d7c4ed3 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct ComputePipelineDesc +public struct ComputePipelineDesc { public Shader ComputeShader; } diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs index 88f3a8bd..115e9c25 100644 --- a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct DepthStencilAttachment +public struct DepthStencilAttachment { public Texture Texture; diff --git a/sources/Zenith.NET/Structs/DepthStencilState.cs b/sources/Zenith.NET/Structs/DepthStencilState.cs index f6276736..c27c1c96 100644 --- a/sources/Zenith.NET/Structs/DepthStencilState.cs +++ b/sources/Zenith.NET/Structs/DepthStencilState.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct DepthStencilState +public struct DepthStencilState { public bool IsDepthEnabled; diff --git a/sources/Zenith.NET/Structs/Extent3D.cs b/sources/Zenith.NET/Structs/Extent3D.cs index ebc8e55a..2dd5d5af 100644 --- a/sources/Zenith.NET/Structs/Extent3D.cs +++ b/sources/Zenith.NET/Structs/Extent3D.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct Extent3D +public struct Extent3D { public uint Width; diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 256846e3..2b772528 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct GraphicsPipelineDesc +public struct GraphicsPipelineDesc { public RenderState RenderState; diff --git a/sources/Zenith.NET/Structs/IndirectDispatchArgs.cs b/sources/Zenith.NET/Structs/IndirectDispatchArgs.cs index 0300559d..946643b2 100644 --- a/sources/Zenith.NET/Structs/IndirectDispatchArgs.cs +++ b/sources/Zenith.NET/Structs/IndirectDispatchArgs.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct IndirectDispatchArgs +public struct IndirectDispatchArgs { public uint GroupCountX; diff --git a/sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs b/sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs index 6b26270c..ac622185 100644 --- a/sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs +++ b/sources/Zenith.NET/Structs/IndirectDispatchMeshArgs.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct IndirectDispatchMeshArgs +public struct IndirectDispatchMeshArgs { public uint GroupCountX; diff --git a/sources/Zenith.NET/Structs/IndirectDrawArgs.cs b/sources/Zenith.NET/Structs/IndirectDrawArgs.cs index 187bf530..da1d1dba 100644 --- a/sources/Zenith.NET/Structs/IndirectDrawArgs.cs +++ b/sources/Zenith.NET/Structs/IndirectDrawArgs.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct IndirectDrawArgs +public struct IndirectDrawArgs { public uint VertexCount; diff --git a/sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs b/sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs index abd70765..2ea2b186 100644 --- a/sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs +++ b/sources/Zenith.NET/Structs/IndirectDrawIndexedArgs.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct IndirectDrawIndexedArgs +public struct IndirectDrawIndexedArgs { public uint IndexCount; diff --git a/sources/Zenith.NET/Structs/InputElement.cs b/sources/Zenith.NET/Structs/InputElement.cs index 30ac9d76..51224223 100644 --- a/sources/Zenith.NET/Structs/InputElement.cs +++ b/sources/Zenith.NET/Structs/InputElement.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct InputElement +public struct InputElement { public ElementFormat Format; diff --git a/sources/Zenith.NET/Structs/InputLayout.cs b/sources/Zenith.NET/Structs/InputLayout.cs index b8d20e43..2eb4bc18 100644 --- a/sources/Zenith.NET/Structs/InputLayout.cs +++ b/sources/Zenith.NET/Structs/InputLayout.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct InputLayout +public struct InputLayout { public InputElement[] InputElements; diff --git a/sources/Zenith.NET/Structs/MappedMemory.cs b/sources/Zenith.NET/Structs/MappedMemory.cs index 6bec606a..da67088a 100644 --- a/sources/Zenith.NET/Structs/MappedMemory.cs +++ b/sources/Zenith.NET/Structs/MappedMemory.cs @@ -1,3 +1,8 @@ namespace Zenith.NET; -public readonly record struct MappedMemory(nint Pointer, uint SizeInBytes); +public readonly struct MappedMemory +{ + public nint Pointer { get; init; } + + public uint SizeInBytes { get; init; } +} diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index e6964110..2d2c53f3 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct MeshShadingPipelineDesc +public struct MeshShadingPipelineDesc { public RenderState RenderState; diff --git a/sources/Zenith.NET/Structs/Offset3D.cs b/sources/Zenith.NET/Structs/Offset3D.cs index bf1c8b4a..8134c7bf 100644 --- a/sources/Zenith.NET/Structs/Offset3D.cs +++ b/sources/Zenith.NET/Structs/Offset3D.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct Offset3D +public struct Offset3D { public uint X; diff --git a/sources/Zenith.NET/Structs/QueryHeapDesc.cs b/sources/Zenith.NET/Structs/QueryHeapDesc.cs index ed7deb71..8b59d1f3 100644 --- a/sources/Zenith.NET/Structs/QueryHeapDesc.cs +++ b/sources/Zenith.NET/Structs/QueryHeapDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct QueryHeapDesc +public struct QueryHeapDesc { public QueryType Type; diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index c9f087c6..c9a8a9c2 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct RasterizerState +public struct RasterizerState { public FillMode FillMode; diff --git a/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs index e29f9246..c2481f52 100644 --- a/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct RayTracingAabbGeometry +public struct RayTracingAabbGeometry { public Buffer Buffer; diff --git a/sources/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Zenith.NET/Structs/RayTracingGeometry.cs index 17cb4726..18c1f21c 100644 --- a/sources/Zenith.NET/Structs/RayTracingGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingGeometry.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct RayTracingGeometry +public struct RayTracingGeometry { public RayTracingGeometryType Type; diff --git a/sources/Zenith.NET/Structs/RayTracingInstance.cs b/sources/Zenith.NET/Structs/RayTracingInstance.cs index 27405161..c92911c9 100644 --- a/sources/Zenith.NET/Structs/RayTracingInstance.cs +++ b/sources/Zenith.NET/Structs/RayTracingInstance.cs @@ -2,7 +2,7 @@ namespace Zenith.NET; -public record struct RayTracingInstance +public struct RayTracingInstance { public BottomLevelAccelerationStructure AccelerationStructure; diff --git a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index faf68c88..193317f1 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -2,7 +2,7 @@ namespace Zenith.NET; -public record struct RayTracingTriangleGeometry +public struct RayTracingTriangleGeometry { public Buffer VertexBuffer; diff --git a/sources/Zenith.NET/Structs/RenderState.cs b/sources/Zenith.NET/Structs/RenderState.cs index c24a3bbc..ed723be1 100644 --- a/sources/Zenith.NET/Structs/RenderState.cs +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -2,7 +2,7 @@ namespace Zenith.NET; -public record struct RenderState +public struct RenderState { public RasterizerState RasterizerState; diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 7c507786..30d2a8dd 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct SamplerDesc +public struct SamplerDesc { public FilterMode MinFilter; diff --git a/sources/Zenith.NET/Structs/Scissor.cs b/sources/Zenith.NET/Structs/Scissor.cs index ad4a0bdf..835af6ec 100644 --- a/sources/Zenith.NET/Structs/Scissor.cs +++ b/sources/Zenith.NET/Structs/Scissor.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct Scissor +public struct Scissor { public int X; diff --git a/sources/Zenith.NET/Structs/ShaderDesc.cs b/sources/Zenith.NET/Structs/ShaderDesc.cs index 958d780e..7992edb8 100644 --- a/sources/Zenith.NET/Structs/ShaderDesc.cs +++ b/sources/Zenith.NET/Structs/ShaderDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct ShaderDesc +public struct ShaderDesc { public byte[] Bytecode; diff --git a/sources/Zenith.NET/Structs/StencilFaceState.cs b/sources/Zenith.NET/Structs/StencilFaceState.cs index 156c00ab..73bc2f15 100644 --- a/sources/Zenith.NET/Structs/StencilFaceState.cs +++ b/sources/Zenith.NET/Structs/StencilFaceState.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct StencilFaceState +public struct StencilFaceState { public StencilOp FailOp; diff --git a/sources/Zenith.NET/Structs/Surface.cs b/sources/Zenith.NET/Structs/Surface.cs index 399c5f58..8bef4112 100644 --- a/sources/Zenith.NET/Structs/Surface.cs +++ b/sources/Zenith.NET/Structs/Surface.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct Surface +public struct Surface { public SurfaceType Type; diff --git a/sources/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Zenith.NET/Structs/SwapChainDesc.cs index 5abdcb49..06e0c79e 100644 --- a/sources/Zenith.NET/Structs/SwapChainDesc.cs +++ b/sources/Zenith.NET/Structs/SwapChainDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct SwapChainDesc +public struct SwapChainDesc { public Surface Surface; diff --git a/sources/Zenith.NET/Structs/TextureData.cs b/sources/Zenith.NET/Structs/TextureData.cs index f6f866dd..47b6c8aa 100644 --- a/sources/Zenith.NET/Structs/TextureData.cs +++ b/sources/Zenith.NET/Structs/TextureData.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureData +public struct TextureData { public nint Pointer; diff --git a/sources/Zenith.NET/Structs/TextureDataLayout.cs b/sources/Zenith.NET/Structs/TextureDataLayout.cs index e3ecb5c1..cabb3834 100644 --- a/sources/Zenith.NET/Structs/TextureDataLayout.cs +++ b/sources/Zenith.NET/Structs/TextureDataLayout.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureDataLayout +public struct TextureDataLayout { public uint SizeInBytes; diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index 4b14f619..932e9049 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureDesc +public struct TextureDesc { public TextureType Type; diff --git a/sources/Zenith.NET/Structs/TextureSubresource.cs b/sources/Zenith.NET/Structs/TextureSubresource.cs index d82cd079..82156aed 100644 --- a/sources/Zenith.NET/Structs/TextureSubresource.cs +++ b/sources/Zenith.NET/Structs/TextureSubresource.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureSubresource +public struct TextureSubresource { public uint MipLevel; diff --git a/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs b/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs index a0f00926..6a00da7b 100644 --- a/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs +++ b/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureSubresourceLayers +public struct TextureSubresourceLayers { public uint MipLevel; diff --git a/sources/Zenith.NET/Structs/TextureSubresourceRange.cs b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs index 542e8bf6..5da89975 100644 --- a/sources/Zenith.NET/Structs/TextureSubresourceRange.cs +++ b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureSubresourceRange +public struct TextureSubresourceRange { public uint BaseMipLevel; diff --git a/sources/Zenith.NET/Structs/TextureViewDesc.cs b/sources/Zenith.NET/Structs/TextureViewDesc.cs index 2455e653..dc17f493 100644 --- a/sources/Zenith.NET/Structs/TextureViewDesc.cs +++ b/sources/Zenith.NET/Structs/TextureViewDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureViewDesc +public struct TextureViewDesc { public Texture Texture; diff --git a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs index 883f6fe5..8d85e37e 100644 --- a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs +++ b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TopLevelAccelerationStructureDesc +public struct TopLevelAccelerationStructureDesc { public RayTracingInstance[] Instances; diff --git a/sources/Zenith.NET/Structs/Viewport.cs b/sources/Zenith.NET/Structs/Viewport.cs index b6fc64c2..d57c6542 100644 --- a/sources/Zenith.NET/Structs/Viewport.cs +++ b/sources/Zenith.NET/Structs/Viewport.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct Viewport +public struct Viewport { public float X; diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index d5785291..1d3960b5 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -2,6 +2,8 @@ internal class Uploader(GraphicsContext context) : DisposableObject { + private static readonly TimeSpan LeaseLifetime = TimeSpan.FromSeconds(120); + private readonly Lock @lock = new(); private readonly List available = []; private readonly Dictionary> borrowed = []; @@ -67,7 +69,7 @@ private void CleanupExpiredLeases() private class Lease(Buffer buffer) { - private DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; public Buffer Buffer { get; } = buffer; @@ -90,7 +92,7 @@ public bool TryExpire() public Lease Renew() { - expirationTime = DateTime.UtcNow + TimeSpan.FromSeconds(120); + expirationTime = DateTime.UtcNow + LeaseLifetime; return this; } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index e0887b05..15eb0437 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -1,4 +1,7 @@ -using System.Numerics; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Reflection; namespace Zenith.NET; @@ -593,7 +596,7 @@ internal bool ValidateViewports(ReadOnlySpan viewports) return isValid; } - internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) + internal bool ValidateCurrentPipeline(string commandName) { ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); @@ -649,6 +652,30 @@ internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFor internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout { + bool isLayoutValid = ConstantsLayoutCache.GetOrAdd(typeof(T), CheckConstantsLayout); + + if (!isLayoutValid) + { + ReportError(string.Format(ValidationMessages.HasInvalidConstantsLayout, typeof(T).Name)); + + return false; + } + + uint sizeInBytes = Context.GraphicsApi switch + { + GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, + GraphicsApi.Metal => T.MetalSizeInBytes, + GraphicsApi.Vulkan => T.VulkanSizeInBytes, + _ => 0 + }; + + if (sizeInBytes is 0) + { + ReportError(string.Format(ValidationMessages.MustHaveNonZeroConstantsSize, typeof(T).Name, Context.GraphicsApi)); + + return false; + } + return true; } @@ -1437,6 +1464,45 @@ private void ReportWarning(string message) { Report(MessageSource.Framework, MessageSeverity.Warning, message); } + + private static readonly ConcurrentDictionary ConstantsLayoutCache = new(); + + [UnconditionalSuppressMessage("Trimming", "IL2070", + Justification = "Constants layout types are user-defined structs referenced via T; the trimmer preserves their fields and attributes through the static abstract IConstantsLayout contract.")] + private static bool CheckConstantsLayout(Type type) + { + bool isValid = true; + + if (type.GetCustomAttribute() is null) + { + isValid = false; + } + + foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + int bindingAttributeCount = 0; + bool expectsResourceHandle = false; + + if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; } + if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; } + if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; } + if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; expectsResourceHandle = true; } + if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; expectsResourceHandle = true; } + if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; expectsResourceHandle = true; } + + if (bindingAttributeCount > 1) + { + isValid = false; + } + + if (expectsResourceHandle && field.FieldType != typeof(ResourceHandle)) + { + isValid = false; + } + } + + return isValid; + } } internal static class ValidationConstants @@ -1539,4 +1605,8 @@ file static class ValidationMessages public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; + + public const string HasInvalidConstantsLayout = "{0} is not a valid constants layout. It must be marked with [Constants], each field may carry at most one binding attribute, and any field marked with [Sampled], [Storage], or [Sampler] must be of type ResourceHandle."; + + public const string MustHaveNonZeroConstantsSize = "{0} reports a zero size for {1}; the constants layout has no payload for the current backend."; } From 52d91c333af4b7aa354297b815453db23395ba77 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 19:29:35 +0800 Subject: [PATCH 069/466] Refactor state creation to use static factory methods Refactored BlendState, DepthStencilState, RasterizerState, and related structs to use static factory methods instead of static fields for common/default states. Updated CommandBuffer and platform backends to set stencil reference and blend constants dynamically. Removed BlendStates.cs, DepthStencilStates.cs, and RasterizerStates.cs, moving logic into struct methods. Added static helpers for common resource descriptors and performed minor cleanups. --- .../Renderers/RasterizationRenderer.cs | 6 +- .../ImGuiRenderer.cs | 6 +- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 45 +++--- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 10 ++ sources/Zenith.NET.Metal/MTLCommandEncoder.cs | 16 +- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 12 ++ .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 16 +- .../VKMeshShadingPipeline.cs | 16 +- sources/Zenith.NET/BlendStates.cs | 105 ------------- sources/Zenith.NET/CommandBuffer.cs | 18 ++- sources/Zenith.NET/DepthStencilStates.cs | 100 ------------ sources/Zenith.NET/RasterizerStates.cs | 88 ----------- sources/Zenith.NET/Structs/BlendState.cs | 50 ++++++ sources/Zenith.NET/Structs/BufferDesc.cs | 77 ++++++++++ sources/Zenith.NET/Structs/BufferViewDesc.cs | 33 ++++ sources/Zenith.NET/Structs/ColorAttachment.cs | 42 +++++ .../Structs/ColorAttachmentBlendState.cs | 75 +++++++++ .../Structs/DepthStencilAttachment.cs | 45 ++++++ .../Zenith.NET/Structs/DepthStencilState.cs | 75 +++++++++ sources/Zenith.NET/Structs/QueryHeapDesc.cs | 27 ++++ sources/Zenith.NET/Structs/RasterizerState.cs | 90 +++++++++++ .../Zenith.NET/Structs/RayTracingGeometry.cs | 22 +++ sources/Zenith.NET/Structs/RenderState.cs | 6 - sources/Zenith.NET/Structs/SamplerDesc.cs | 95 ++++++++++++ sources/Zenith.NET/Structs/ShaderDesc.cs | 50 ++++++ .../Zenith.NET/Structs/StencilFaceState.cs | 11 ++ sources/Zenith.NET/Structs/TextureDesc.cs | 144 ++++++++++++++++++ sources/Zenith.NET/Structs/TextureViewDesc.cs | 119 +++++++++++++++ 28 files changed, 1035 insertions(+), 364 deletions(-) delete mode 100644 sources/Zenith.NET/BlendStates.cs delete mode 100644 sources/Zenith.NET/DepthStencilStates.cs delete mode 100644 sources/Zenith.NET/RasterizerStates.cs diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index bffcca6e..236cfbbd 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -88,9 +88,9 @@ public RasterizationRenderer() { RenderStates = new() { - RasterizerState = RasterizerStates.CullNone, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque + RasterizerState = RasterizerState.CullNone(), + DepthStencilState = DepthStencilState.DepthReadWrite(), + BlendState = BlendState.Opaque() }, Vertex = vertexShader, Pixel = pixelShader, diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 37d35a6f..d13dd5f1 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -174,9 +174,9 @@ public ImGuiRenderer(GraphicsContext context, Output output, ImGuiColorSpace col { RenderStates = new() { - RasterizerState = RasterizerStates.Default, - DepthStencilState = DepthStencilStates.None, - BlendState = BlendStates.AlphaBlend + RasterizerState = RasterizerState.CullNone(), + DepthStencilState = DepthStencilState.DepthNone(), + BlendState = BlendState.AlphaBlend() }, Vertex = vertex, Pixel = pixel, diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index f54522c9..a2f7ed23 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -1,4 +1,5 @@ -using Silk.NET.Core.Native; +using System.Numerics; +using Silk.NET.Core.Native; using Silk.NET.Direct3D12; using Silk.NET.DXGI; using Silk.NET.Maths; @@ -385,21 +386,6 @@ protected override void SetPipelineImpl(GraphicsPipeline pipeline) GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); GraphicsCommandList4.SetGraphicsRootSignature(dxPipeline.RootSignature); - GraphicsCommandList4.OMSetStencilRef(dxPipeline.Desc.RenderStates.StencilReference); - - if (dxPipeline.Desc.RenderStates.BlendFactor.HasValue) - { - float[] blendFactor = - [ - dxPipeline.Desc.RenderStates.BlendFactor.Value.X, - dxPipeline.Desc.RenderStates.BlendFactor.Value.Y, - dxPipeline.Desc.RenderStates.BlendFactor.Value.Z, - dxPipeline.Desc.RenderStates.BlendFactor.Value.W - ]; - - GraphicsCommandList4.OMSetBlendFactor(ref blendFactor[0]); - } - GraphicsCommandList4.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).PrimitiveTopology); } @@ -417,21 +403,24 @@ protected override void SetPipelineImpl(MeshShadingPipeline pipeline) GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); GraphicsCommandList4.SetGraphicsRootSignature(dxPipeline.RootSignature); + } - GraphicsCommandList4.OMSetStencilRef(dxPipeline.Desc.RenderStates.StencilReference); + protected override void SetStencilReferenceImpl(uint reference) + { + GraphicsCommandList4.OMSetStencilRef(reference); + } - if (dxPipeline.Desc.RenderStates.BlendFactor.HasValue) + protected override void SetBlendConstantImpl(Vector4 blendConstant) + { + float* blendFactor = stackalloc float[4] { - float[] blendFactor = - [ - dxPipeline.Desc.RenderStates.BlendFactor.Value.X, - dxPipeline.Desc.RenderStates.BlendFactor.Value.Y, - dxPipeline.Desc.RenderStates.BlendFactor.Value.Z, - dxPipeline.Desc.RenderStates.BlendFactor.Value.W - ]; - - GraphicsCommandList4.OMSetBlendFactor(ref blendFactor[0]); - } + blendConstant.X, + blendConstant.Y, + blendConstant.Z, + blendConstant.W + }; + + GraphicsCommandList4.OMSetBlendFactor(blendFactor); } protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 5f2e18e4..c9b7c392 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -276,6 +276,16 @@ protected override void SetPipelineImpl(MeshShadingPipeline pipeline) CommandEncoder.SetPipeline(pipeline); } + protected override void SetStencilReferenceImpl(uint reference) + { + CommandEncoder.SetStencilReference(reference); + } + + protected override void SetBlendConstantImpl(Vector4 blendConstant) + { + CommandEncoder.SetBlendConstant(blendConstant); + } + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) { CommandEncoder.SetVertexBuffer(buffer, offsetInBytes, index); diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs index 6958e800..0ea499d6 100644 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs @@ -215,6 +215,16 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat format IndexType = MTLFormats.Metal(format); } + public void SetStencilReference(uint reference) + { + Render?.SetStencilReferenceValue(reference); + } + + public void SetBlendConstant(System.Numerics.Vector4 blendConstant) + { + Render?.SetBlendColor(blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W); + } + public void PushResourceTable(ResourceTable resourceTable) { currentResourceTable = resourceTable; @@ -365,13 +375,7 @@ private void BindRenderPipeline(RenderStates renderStates, MTLRenderPipelineStat Render?.SetTriangleFillMode(MTLFormats.Metal(renderStates.RasterizerState.FillMode)); - if (renderStates.BlendFactor.HasValue) - { - Render?.SetBlendColor(renderStates.BlendFactor.Value.X, renderStates.BlendFactor.Value.Y, renderStates.BlendFactor.Value.Z, renderStates.BlendFactor.Value.W); - } - Render?.SetDepthStencilState(depthStencilState); - Render?.SetStencilReferenceValue(renderStates.StencilReference); Render?.SetFrontFacing(MTLFormats.Metal(renderStates.RasterizerState.FrontFace)); } diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index fc653a1f..a2d0a913 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -459,6 +459,18 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf Context.Vk.CmdBindIndexBuffer(CommandBuffer, buffer.Vulkan().Buffer, offsetInBytes, VKFormats.Vulkan(format)); } + protected override void SetStencilReferenceImpl(uint reference) + { + Context.Vk.CmdSetStencilReference(CommandBuffer, StencilFaceFlags.FrontAndBackBit, reference); + } + + protected override void SetBlendConstantImpl(Vector4 blendConstant) + { + Span values = stackalloc float[4] { blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W }; + + Context.Vk.CmdSetBlendConstants(CommandBuffer, values); + } + protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) { (PipelineBindPoint pipelineBindPoint, PipelineLayout pipelineLayout, uint descriptorWriteCount) = pipeline switch diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index ba8d7357..98974aa2 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -92,7 +92,7 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilFunc), CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = desc.RenderStates.StencilReference + Reference = 0 }, Back = new() { @@ -102,7 +102,7 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilFunc), CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = desc.RenderStates.StencilReference + Reference = 0 }, MinDepthBounds = 0.0f, MaxDepthBounds = 1.0f @@ -134,14 +134,6 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) StencilAttachmentFormat = ZenithHelper.HasStencil(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown) ? depthStencilAttachmentFormat : Format.Undefined }; - if (desc.RenderStates.BlendFactor.HasValue) - { - colorBlendState.BlendConstants[0] = desc.RenderStates.BlendFactor.Value.X; - colorBlendState.BlendConstants[1] = desc.RenderStates.BlendFactor.Value.Y; - colorBlendState.BlendConstants[2] = desc.RenderStates.BlendFactor.Value.Z; - colorBlendState.BlendConstants[3] = desc.RenderStates.BlendFactor.Value.W; - } - createInfo.PRasterizationState = &rasterizationState; createInfo.PDepthStencilState = &depthStencilState; createInfo.PColorBlendState = &colorBlendState; @@ -224,8 +216,8 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) PipelineDynamicStateCreateInfo dynamicState = new() { SType = StructureType.PipelineDynamicStateCreateInfo, - DynamicStateCount = 2, - PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor]) + DynamicStateCount = 4, + PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.StencilReference, DynamicState.BlendConstants]) }; createInfo.PDynamicState = &dynamicState; diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 1568f9c3..bb9c8859 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -86,7 +86,7 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilFunc), CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = desc.RenderStates.StencilReference + Reference = 0 }, Back = new() { @@ -96,7 +96,7 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilFunc), CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = desc.RenderStates.StencilReference + Reference = 0 }, MinDepthBounds = 0.0f, MaxDepthBounds = 1.0f @@ -128,14 +128,6 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc StencilAttachmentFormat = ZenithHelper.HasStencil(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown) ? depthStencilAttachmentFormat : Format.Undefined }; - if (desc.RenderStates.BlendFactor.HasValue) - { - colorBlendState.BlendConstants[0] = desc.RenderStates.BlendFactor.Value.X; - colorBlendState.BlendConstants[1] = desc.RenderStates.BlendFactor.Value.Y; - colorBlendState.BlendConstants[2] = desc.RenderStates.BlendFactor.Value.Z; - colorBlendState.BlendConstants[3] = desc.RenderStates.BlendFactor.Value.W; - } - createInfo.PRasterizationState = &rasterizationState; createInfo.PDepthStencilState = &depthStencilState; createInfo.PColorBlendState = &colorBlendState; @@ -195,8 +187,8 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc PipelineDynamicStateCreateInfo dynamicState = new() { SType = StructureType.PipelineDynamicStateCreateInfo, - DynamicStateCount = 2, - PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor]) + DynamicStateCount = 4, + PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.StencilReference, DynamicState.BlendConstants]) }; createInfo.PDynamicState = &dynamicState; diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs deleted file mode 100644 index 1a67ccef..00000000 --- a/sources/Zenith.NET/BlendStates.cs +++ /dev/null @@ -1,105 +0,0 @@ -namespace Zenith.NET; - -public static class BlendStates -{ - public static readonly BlendState Default = new() - { - IsAlphaToCoverageEnabled = false, - IsIndependentBlendEnabled = false, - ColorAttachment0 = new() - { - SrcRgbFactor = BlendFactor.One, - DstRgbFactor = BlendFactor.Zero, - RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.One, - DstAlphaFactor = BlendFactor.Zero, - AlphaOp = BlendOp.Add, - ColorWrites = ColorWrites.All - } - }; - - public static readonly BlendState Additive = new() - { - IsAlphaToCoverageEnabled = false, - IsIndependentBlendEnabled = false, - ColorAttachment0 = new() - { - IsBlendingEnabled = true, - SrcRgbFactor = BlendFactor.SourceAlpha, - DstRgbFactor = BlendFactor.One, - RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.SourceAlpha, - DstAlphaFactor = BlendFactor.One, - AlphaOp = BlendOp.Add, - ColorWrites = ColorWrites.All - } - }; - - public static readonly BlendState AlphaBlend = new() - { - IsAlphaToCoverageEnabled = false, - IsIndependentBlendEnabled = false, - ColorAttachment0 = new() - { - IsBlendingEnabled = true, - SrcRgbFactor = BlendFactor.One, - DstRgbFactor = BlendFactor.OneMinusSourceAlpha, - RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.One, - DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, - AlphaOp = BlendOp.Add, - ColorWrites = ColorWrites.All - } - }; - - public static readonly BlendState NonPremultiplied = new() - { - IsAlphaToCoverageEnabled = false, - IsIndependentBlendEnabled = false, - ColorAttachment0 = new() - { - IsBlendingEnabled = true, - SrcRgbFactor = BlendFactor.SourceAlpha, - DstRgbFactor = BlendFactor.OneMinusSourceAlpha, - RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.SourceAlpha, - DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, - AlphaOp = BlendOp.Add, - ColorWrites = ColorWrites.All - } - }; - - public static readonly BlendState Opaque = new() - { - IsAlphaToCoverageEnabled = false, - IsIndependentBlendEnabled = false, - ColorAttachment0 = new() - { - IsBlendingEnabled = false, - SrcRgbFactor = BlendFactor.One, - DstRgbFactor = BlendFactor.Zero, - RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.One, - DstAlphaFactor = BlendFactor.Zero, - AlphaOp = BlendOp.Add, - ColorWrites = ColorWrites.All - } - }; - - public static readonly BlendState ColorDisabled = new() - { - IsAlphaToCoverageEnabled = false, - IsIndependentBlendEnabled = false, - ColorAttachment0 = new() - { - IsBlendingEnabled = false, - SrcRgbFactor = BlendFactor.One, - DstRgbFactor = BlendFactor.Zero, - RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.One, - DstAlphaFactor = BlendFactor.Zero, - AlphaOp = BlendOp.Add, - ColorWrites = ColorWrites.None - } - }; -} diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index f0f2fb4c..20a80913 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -1,4 +1,6 @@ -namespace Zenith.NET; +using System.Numerics; + +namespace Zenith.NET; public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { @@ -281,6 +283,16 @@ public void SetPipeline(MeshShadingPipeline pipeline) currentPipeline = pipeline; } + public void SetStencilReference(uint reference) + { + SetStencilReferenceImpl(reference); + } + + public void SetBlendConstant(Vector4 blendConstant) + { + SetBlendConstantImpl(blendConstant); + } + public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) { if (!TryGetCurrentPipeline(nameof(SetVertexBuffer), out GraphicsPipeline pipeline)) @@ -618,6 +630,10 @@ private bool TryGetCurrentPipeline(string commandName, out TPipeline protected abstract void SetPipelineImpl(MeshShadingPipeline pipeline); + protected abstract void SetStencilReferenceImpl(uint reference); + + protected abstract void SetBlendConstantImpl(Vector4 blendConstant); + protected abstract void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot); protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs deleted file mode 100644 index d554cc21..00000000 --- a/sources/Zenith.NET/DepthStencilStates.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace Zenith.NET; - -public static class DepthStencilStates -{ - public static readonly DepthStencilState Default = new() - { - IsDepthEnabled = true, - IsDepthWriteEnabled = true, - DepthCompareOp = CompareOp.LessEqual, - IsStencilEnabled = false, - StencilReadMask = 0xFF, - StencilWriteMask = 0xFF, - FrontFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - }, - BackFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - } - }; - - public static readonly DepthStencilState DefaultInverted = new() - { - IsDepthEnabled = true, - IsDepthWriteEnabled = true, - DepthCompareOp = CompareOp.GreaterEqual, - IsStencilEnabled = false, - StencilReadMask = 0xFF, - StencilWriteMask = 0xFF, - FrontFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - }, - BackFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - } - }; - - public static readonly DepthStencilState DepthRead = new() - { - IsDepthEnabled = true, - IsDepthWriteEnabled = false, - DepthCompareOp = CompareOp.LessEqual, - IsStencilEnabled = false, - StencilReadMask = 0xFF, - StencilWriteMask = 0xFF, - FrontFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - }, - BackFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - } - }; - - public static readonly DepthStencilState None = new() - { - IsDepthEnabled = false, - IsDepthWriteEnabled = false, - DepthCompareOp = CompareOp.LessEqual, - IsStencilEnabled = false, - StencilReadMask = 0xFF, - StencilWriteMask = 0xFF, - FrontFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - }, - BackFace = new() - { - FailOp = StencilOp.Keep, - DepthFailOp = StencilOp.Keep, - PassOp = StencilOp.Keep, - CompareOp = CompareOp.Always - } - }; -} diff --git a/sources/Zenith.NET/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs deleted file mode 100644 index d4274ddb..00000000 --- a/sources/Zenith.NET/RasterizerStates.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace Zenith.NET; - -public static class RasterizerStates -{ - public static readonly RasterizerState Default = new() - { - FillMode = FillMode.Solid, - CullMode = CullMode.None, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; - - public static readonly RasterizerState CullFront = new() - { - FillMode = FillMode.Solid, - CullMode = CullMode.Front, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; - - public static readonly RasterizerState CullBack = new() - { - FillMode = FillMode.Solid, - CullMode = CullMode.Back, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; - - public static readonly RasterizerState CullNone = new() - { - FillMode = FillMode.Solid, - CullMode = CullMode.None, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; - - public static readonly RasterizerState WireframeCullFront = new() - { - FillMode = FillMode.Wireframe, - CullMode = CullMode.Front, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; - - public static readonly RasterizerState WireframeCullBack = new() - { - FillMode = FillMode.Wireframe, - CullMode = CullMode.Back, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; - - public static readonly RasterizerState Wireframe = new() - { - FillMode = FillMode.Wireframe, - CullMode = CullMode.None, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false - }; -} diff --git a/sources/Zenith.NET/Structs/BlendState.cs b/sources/Zenith.NET/Structs/BlendState.cs index 7fe5a337..f1fc0d0f 100644 --- a/sources/Zenith.NET/Structs/BlendState.cs +++ b/sources/Zenith.NET/Structs/BlendState.cs @@ -21,4 +21,54 @@ public struct BlendState public ColorAttachmentBlendState ColorAttachment6; public ColorAttachmentBlendState ColorAttachment7; + + public static BlendState Opaque() + { + return new() + { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, + ColorAttachment0 = ColorAttachmentBlendState.Opaque() + }; + } + + public static BlendState AlphaBlend() + { + return new() + { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, + ColorAttachment0 = ColorAttachmentBlendState.AlphaBlend() + }; + } + + public static BlendState Additive() + { + return new() + { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, + ColorAttachment0 = ColorAttachmentBlendState.Additive() + }; + } + + public static BlendState NonPremultiplied() + { + return new() + { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, + ColorAttachment0 = ColorAttachmentBlendState.NonPremultiplied() + }; + } + + public static BlendState ColorDisabled() + { + return new() + { + IsAlphaToCoverageEnabled = false, + IsIndependentBlendEnabled = false, + ColorAttachment0 = ColorAttachmentBlendState.ColorDisabled() + }; + } } diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index d664e529..62de9e8e 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -9,4 +9,81 @@ public struct BufferDesc public BufferAccess Access; public BufferUsages Usages; + + public static BufferDesc Vertex(uint sizeInBytes, uint strideInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes, + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.Vertex | BufferUsages.CopyDst + }; + } + + public static BufferDesc Index(uint sizeInBytes, uint strideInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes, + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.Index | BufferUsages.CopyDst + }; + } + + public static BufferDesc Uniform(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.Uniform | BufferUsages.CopyDst + }; + } + + public static BufferDesc StorageReadOnly(uint sizeInBytes, uint strideInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes, + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst + }; + } + + public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes, + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.StorageReadWrite | BufferUsages.CopyDst + }; + } + + public static BufferDesc Indirect(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.Indirect | BufferUsages.CopyDst + }; + } + + public static BufferDesc Staging(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.CopySrc + }; + } } diff --git a/sources/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Zenith.NET/Structs/BufferViewDesc.cs index 60646491..e8be7c1b 100644 --- a/sources/Zenith.NET/Structs/BufferViewDesc.cs +++ b/sources/Zenith.NET/Structs/BufferViewDesc.cs @@ -9,4 +9,37 @@ public struct BufferViewDesc public uint SizeInBytes; public uint StrideInBytes; + + public static BufferViewDesc Uniform(Buffer buffer, uint offsetInBytes, uint sizeInBytes) + { + return new() + { + Buffer = buffer, + OffsetInBytes = offsetInBytes, + SizeInBytes = sizeInBytes, + StrideInBytes = 0 + }; + } + + public static BufferViewDesc StorageReadOnly(Buffer buffer, uint offsetInBytes, uint sizeInBytes, uint strideInBytes) + { + return new() + { + Buffer = buffer, + OffsetInBytes = offsetInBytes, + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes + }; + } + + public static BufferViewDesc StorageReadWrite(Buffer buffer, uint offsetInBytes, uint sizeInBytes, uint strideInBytes) + { + return new() + { + Buffer = buffer, + OffsetInBytes = offsetInBytes, + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes + }; + } } diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 13d0be2b..2cfe6fc5 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -17,4 +17,46 @@ public struct ColorAttachment public StoreOp StoreOp; public Vector4 ClearColor; + + public static ColorAttachment Clear(Texture texture, Vector4 clearColor) + { + return new() + { + Texture = texture, + Subresource = new(), + ResolveTexture = null, + ResolveSubresource = new(), + LoadOp = LoadOp.Clear, + StoreOp = StoreOp.Store, + ClearColor = clearColor + }; + } + + public static ColorAttachment Load(Texture texture) + { + return new() + { + Texture = texture, + Subresource = new(), + ResolveTexture = null, + ResolveSubresource = new(), + LoadOp = LoadOp.Load, + StoreOp = StoreOp.Store, + ClearColor = Vector4.Zero + }; + } + + public static ColorAttachment DontCare(Texture texture) + { + return new() + { + Texture = texture, + Subresource = new(), + ResolveTexture = null, + ResolveSubresource = new(), + LoadOp = LoadOp.DontCare, + StoreOp = StoreOp.Store, + ClearColor = Vector4.Zero + }; + } } diff --git a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs index 4e9a51d4..ecfc0a61 100644 --- a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -17,4 +17,79 @@ public struct ColorAttachmentBlendState public BlendOp AlphaOp; public ColorWrites ColorWrites; + + public static ColorAttachmentBlendState Opaque() + { + return new() + { + IsBlendingEnabled = false, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.Zero, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.Zero, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + }; + } + + public static ColorAttachmentBlendState AlphaBlend() + { + return new() + { + IsBlendingEnabled = true, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + }; + } + + public static ColorAttachmentBlendState Additive() + { + return new() + { + IsBlendingEnabled = true, + SrcRgbFactor = BlendFactor.SourceAlpha, + DstRgbFactor = BlendFactor.One, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.SourceAlpha, + DstAlphaFactor = BlendFactor.One, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + }; + } + + public static ColorAttachmentBlendState NonPremultiplied() + { + return new() + { + IsBlendingEnabled = true, + SrcRgbFactor = BlendFactor.SourceAlpha, + DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.SourceAlpha, + DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + }; + } + + public static ColorAttachmentBlendState ColorDisabled() + { + return new() + { + IsBlendingEnabled = false, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.Zero, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.Zero, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.None + }; + } } \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs index 115e9c25..29123ec9 100644 --- a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -17,4 +17,49 @@ public struct DepthStencilAttachment public StoreOp StencilStoreOp; public byte ClearStencil; + + public static DepthStencilAttachment Clear(Texture texture, float clearDepth, byte clearStencil) + { + return new() + { + Texture = texture, + Subresource = new(), + DepthLoadOp = LoadOp.Clear, + DepthStoreOp = StoreOp.Store, + ClearDepth = clearDepth, + StencilLoadOp = LoadOp.Clear, + StencilStoreOp = StoreOp.Store, + ClearStencil = clearStencil + }; + } + + public static DepthStencilAttachment Load(Texture texture) + { + return new() + { + Texture = texture, + Subresource = new(), + DepthLoadOp = LoadOp.Load, + DepthStoreOp = StoreOp.Store, + ClearDepth = 1.0f, + StencilLoadOp = LoadOp.Load, + StencilStoreOp = StoreOp.Store, + ClearStencil = 0 + }; + } + + public static DepthStencilAttachment DontCare(Texture texture) + { + return new() + { + Texture = texture, + Subresource = new(), + DepthLoadOp = LoadOp.DontCare, + DepthStoreOp = StoreOp.Store, + ClearDepth = 1.0f, + StencilLoadOp = LoadOp.DontCare, + StencilStoreOp = StoreOp.Store, + ClearStencil = 0 + }; + } } diff --git a/sources/Zenith.NET/Structs/DepthStencilState.cs b/sources/Zenith.NET/Structs/DepthStencilState.cs index c27c1c96..90b2ee52 100644 --- a/sources/Zenith.NET/Structs/DepthStencilState.cs +++ b/sources/Zenith.NET/Structs/DepthStencilState.cs @@ -17,4 +17,79 @@ public struct DepthStencilState public StencilFaceState FrontFace; public StencilFaceState BackFace; + + public static DepthStencilState DepthReadWrite() + { + return new() + { + IsDepthEnabled = true, + IsDepthWriteEnabled = true, + DepthCompareOp = CompareOp.LessEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = StencilFaceState.Keep(), + BackFace = StencilFaceState.Keep() + }; + } + + public static DepthStencilState DepthReadWriteReverseZ() + { + return new() + { + IsDepthEnabled = true, + IsDepthWriteEnabled = true, + DepthCompareOp = CompareOp.GreaterEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = StencilFaceState.Keep(), + BackFace = StencilFaceState.Keep() + }; + } + + public static DepthStencilState DepthRead() + { + return new() + { + IsDepthEnabled = true, + IsDepthWriteEnabled = false, + DepthCompareOp = CompareOp.LessEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = StencilFaceState.Keep(), + BackFace = StencilFaceState.Keep() + }; + } + + public static DepthStencilState DepthReadReverseZ() + { + return new() + { + IsDepthEnabled = true, + IsDepthWriteEnabled = false, + DepthCompareOp = CompareOp.GreaterEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = StencilFaceState.Keep(), + BackFace = StencilFaceState.Keep() + }; + } + + public static DepthStencilState DepthNone() + { + return new() + { + IsDepthEnabled = false, + IsDepthWriteEnabled = false, + DepthCompareOp = CompareOp.LessEqual, + IsStencilEnabled = false, + StencilReadMask = 0xFF, + StencilWriteMask = 0xFF, + FrontFace = StencilFaceState.Keep(), + BackFace = StencilFaceState.Keep() + }; + } } diff --git a/sources/Zenith.NET/Structs/QueryHeapDesc.cs b/sources/Zenith.NET/Structs/QueryHeapDesc.cs index 8b59d1f3..b1299377 100644 --- a/sources/Zenith.NET/Structs/QueryHeapDesc.cs +++ b/sources/Zenith.NET/Structs/QueryHeapDesc.cs @@ -5,4 +5,31 @@ public struct QueryHeapDesc public QueryType Type; public uint Count; + + public static QueryHeapDesc Timestamp(uint count) + { + return new() + { + Type = QueryType.Timestamp, + Count = count + }; + } + + public static QueryHeapDesc Occlusion(uint count) + { + return new() + { + Type = QueryType.Occlusion, + Count = count + }; + } + + public static QueryHeapDesc BinaryOcclusion(uint count) + { + return new() + { + Type = QueryType.BinaryOcclusion, + Count = count + }; + } } diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index c9a8a9c2..e6caab98 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -17,4 +17,94 @@ public struct RasterizerState public bool IsDepthClipEnabled; public bool IsScissorEnabled; + + public static RasterizerState CullFront() + { + return new() + { + FillMode = FillMode.Solid, + CullMode = CullMode.Front, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false + }; + } + + public static RasterizerState CullBack() + { + return new() + { + FillMode = FillMode.Solid, + CullMode = CullMode.Back, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false + }; + } + + public static RasterizerState CullNone() + { + return new() + { + FillMode = FillMode.Solid, + CullMode = CullMode.None, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false + }; + } + + public static RasterizerState Wireframe() + { + return new() + { + FillMode = FillMode.Wireframe, + CullMode = CullMode.None, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false + }; + } + + public static RasterizerState WireframeCullFront() + { + return new() + { + FillMode = FillMode.Wireframe, + CullMode = CullMode.Front, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false + }; + } + + public static RasterizerState WireframeCullBack() + { + return new() + { + FillMode = FillMode.Wireframe, + CullMode = CullMode.Back, + FrontFace = FrontFace.CounterClockwise, + DepthBias = 0, + DepthBiasClamp = 0.0f, + DepthBiasSlopeScale = 0.0f, + IsDepthClipEnabled = true, + IsScissorEnabled = false + }; + } } diff --git a/sources/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Zenith.NET/Structs/RayTracingGeometry.cs index 18c1f21c..3e8cc7aa 100644 --- a/sources/Zenith.NET/Structs/RayTracingGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingGeometry.cs @@ -9,4 +9,26 @@ public struct RayTracingGeometry public RayTracingAabbGeometry AabbGeometry; public bool IsOpaque; + + public static RayTracingGeometry Triangles(RayTracingTriangleGeometry geometry, bool isOpaque) + { + return new() + { + Type = RayTracingGeometryType.Triangle, + TriangleGeometry = geometry, + AabbGeometry = new(), + IsOpaque = isOpaque + }; + } + + public static RayTracingGeometry Aabbs(RayTracingAabbGeometry geometry, bool isOpaque) + { + return new() + { + Type = RayTracingGeometryType.Aabb, + TriangleGeometry = new(), + AabbGeometry = geometry, + IsOpaque = isOpaque + }; + } } diff --git a/sources/Zenith.NET/Structs/RenderState.cs b/sources/Zenith.NET/Structs/RenderState.cs index ed723be1..f987efa0 100644 --- a/sources/Zenith.NET/Structs/RenderState.cs +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -1,5 +1,3 @@ -using System.Numerics; - namespace Zenith.NET; public struct RenderState @@ -9,8 +7,4 @@ public struct RenderState public DepthStencilState DepthStencilState; public BlendState BlendState; - - public uint StencilReference; - - public Vector4? BlendConstant; } \ No newline at end of file diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 30d2a8dd..8647d081 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -25,4 +25,99 @@ public struct SamplerDesc public float MaxLod; public BorderColor BorderColor; + + public static SamplerDesc LinearWrap() + { + return new() + { + MinFilter = FilterMode.Linear, + MagFilter = FilterMode.Linear, + MipmapFilter = FilterMode.Linear, + AddressU = AddressMode.Wrap, + AddressV = AddressMode.Wrap, + AddressW = AddressMode.Wrap, + CompareOp = CompareOp.Never, + MaxAnisotropy = 1, + LodBias = 0.0f, + MinLod = 0.0f, + MaxLod = float.MaxValue, + BorderColor = BorderColor.TransparentBlack + }; + } + + public static SamplerDesc LinearClamp() + { + return new() + { + MinFilter = FilterMode.Linear, + MagFilter = FilterMode.Linear, + MipmapFilter = FilterMode.Linear, + AddressU = AddressMode.Clamp, + AddressV = AddressMode.Clamp, + AddressW = AddressMode.Clamp, + CompareOp = CompareOp.Never, + MaxAnisotropy = 1, + LodBias = 0.0f, + MinLod = 0.0f, + MaxLod = float.MaxValue, + BorderColor = BorderColor.TransparentBlack + }; + } + + public static SamplerDesc PointWrap() + { + return new() + { + MinFilter = FilterMode.Point, + MagFilter = FilterMode.Point, + MipmapFilter = FilterMode.Point, + AddressU = AddressMode.Wrap, + AddressV = AddressMode.Wrap, + AddressW = AddressMode.Wrap, + CompareOp = CompareOp.Never, + MaxAnisotropy = 1, + LodBias = 0.0f, + MinLod = 0.0f, + MaxLod = float.MaxValue, + BorderColor = BorderColor.TransparentBlack + }; + } + + public static SamplerDesc PointClamp() + { + return new() + { + MinFilter = FilterMode.Point, + MagFilter = FilterMode.Point, + MipmapFilter = FilterMode.Point, + AddressU = AddressMode.Clamp, + AddressV = AddressMode.Clamp, + AddressW = AddressMode.Clamp, + CompareOp = CompareOp.Never, + MaxAnisotropy = 1, + LodBias = 0.0f, + MinLod = 0.0f, + MaxLod = float.MaxValue, + BorderColor = BorderColor.TransparentBlack + }; + } + + public static SamplerDesc Anisotropic(uint maxAnisotropy) + { + return new() + { + MinFilter = FilterMode.Linear, + MagFilter = FilterMode.Linear, + MipmapFilter = FilterMode.Linear, + AddressU = AddressMode.Wrap, + AddressV = AddressMode.Wrap, + AddressW = AddressMode.Wrap, + CompareOp = CompareOp.Never, + MaxAnisotropy = maxAnisotropy, + LodBias = 0.0f, + MinLod = 0.0f, + MaxLod = float.MaxValue, + BorderColor = BorderColor.TransparentBlack + }; + } } diff --git a/sources/Zenith.NET/Structs/ShaderDesc.cs b/sources/Zenith.NET/Structs/ShaderDesc.cs index 7992edb8..3956c0d6 100644 --- a/sources/Zenith.NET/Structs/ShaderDesc.cs +++ b/sources/Zenith.NET/Structs/ShaderDesc.cs @@ -7,4 +7,54 @@ public struct ShaderDesc public string EntryPoint; public ShaderStages Stage; + + public static ShaderDesc Vertex(byte[] bytecode, string entryPoint) + { + return new() + { + Bytecode = bytecode, + EntryPoint = entryPoint, + Stage = ShaderStages.Vertex + }; + } + + public static ShaderDesc Fragment(byte[] bytecode, string entryPoint) + { + return new() + { + Bytecode = bytecode, + EntryPoint = entryPoint, + Stage = ShaderStages.Fragment + }; + } + + public static ShaderDesc Compute(byte[] bytecode, string entryPoint) + { + return new() + { + Bytecode = bytecode, + EntryPoint = entryPoint, + Stage = ShaderStages.Compute + }; + } + + public static ShaderDesc Task(byte[] bytecode, string entryPoint) + { + return new() + { + Bytecode = bytecode, + EntryPoint = entryPoint, + Stage = ShaderStages.Task + }; + } + + public static ShaderDesc Mesh(byte[] bytecode, string entryPoint) + { + return new() + { + Bytecode = bytecode, + EntryPoint = entryPoint, + Stage = ShaderStages.Mesh + }; + } } diff --git a/sources/Zenith.NET/Structs/StencilFaceState.cs b/sources/Zenith.NET/Structs/StencilFaceState.cs index 73bc2f15..d4a5223e 100644 --- a/sources/Zenith.NET/Structs/StencilFaceState.cs +++ b/sources/Zenith.NET/Structs/StencilFaceState.cs @@ -9,4 +9,15 @@ public struct StencilFaceState public StencilOp PassOp; public CompareOp CompareOp; + + public static StencilFaceState Keep() + { + return new() + { + FailOp = StencilOp.Keep, + DepthFailOp = StencilOp.Keep, + PassOp = StencilOp.Keep, + CompareOp = CompareOp.Always + }; + } } diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index 932e9049..4731a3ae 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -19,4 +19,148 @@ public struct TextureDesc public SampleCount SampleCount; public TextureUsages Usages; + + public static TextureDesc Texture1D(PixelFormat format, uint width, uint mipLevels) + { + return new() + { + Type = TextureType.Texture1D, + Format = format, + Width = width, + Height = 1, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc Texture1DArray(PixelFormat format, uint width, uint arrayLayers, uint mipLevels) + { + return new() + { + Type = TextureType.Texture1DArray, + Format = format, + Width = width, + Height = 1, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = arrayLayers, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc Texture2D(PixelFormat format, uint width, uint height, uint mipLevels, SampleCount sampleCount) + { + return new() + { + Type = TextureType.Texture2D, + Format = format, + Width = width, + Height = height, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 1, + SampleCount = sampleCount, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc Texture2DArray(PixelFormat format, uint width, uint height, uint arrayLayers, uint mipLevels, SampleCount sampleCount) + { + return new() + { + Type = TextureType.Texture2DArray, + Format = format, + Width = width, + Height = height, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = arrayLayers, + SampleCount = sampleCount, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc Texture3D(PixelFormat format, uint width, uint height, uint depth, uint mipLevels) + { + return new() + { + Type = TextureType.Texture3D, + Format = format, + Width = width, + Height = height, + Depth = depth, + MipLevels = mipLevels, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc TextureCube(PixelFormat format, uint size, uint mipLevels) + { + return new() + { + Type = TextureType.TextureCube, + Format = format, + Width = size, + Height = size, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 6, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc TextureCubeArray(PixelFormat format, uint size, uint cubeCount, uint mipLevels) + { + return new() + { + Type = TextureType.TextureCubeArray, + Format = format, + Width = size, + Height = size, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 6 * cubeCount, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.CopyDst + }; + } + + public static TextureDesc RenderTarget2D(PixelFormat format, uint width, uint height, uint mipLevels, SampleCount sampleCount) + { + return new() + { + Type = TextureType.Texture2D, + Format = format, + Width = width, + Height = height, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 1, + SampleCount = sampleCount, + Usages = TextureUsages.ColorAttachment | TextureUsages.Sampled + }; + } + + public static TextureDesc DepthStencil2D(PixelFormat format, uint width, uint height, SampleCount sampleCount) + { + return new() + { + Type = TextureType.Texture2D, + Format = format, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = sampleCount, + Usages = TextureUsages.DepthStencil | TextureUsages.Sampled + }; + } } diff --git a/sources/Zenith.NET/Structs/TextureViewDesc.cs b/sources/Zenith.NET/Structs/TextureViewDesc.cs index dc17f493..3437d199 100644 --- a/sources/Zenith.NET/Structs/TextureViewDesc.cs +++ b/sources/Zenith.NET/Structs/TextureViewDesc.cs @@ -9,4 +9,123 @@ public struct TextureViewDesc public PixelFormat Format; public TextureSubresourceRange Range; + + public static TextureViewDesc Texture1D(Texture texture, PixelFormat format, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.Texture1D, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = 0, + LayerCount = 1 + } + }; + } + + public static TextureViewDesc Texture1DArray(Texture texture, PixelFormat format, uint baseArrayLayer, uint layerCount, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.Texture1DArray, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = baseArrayLayer, + LayerCount = layerCount + } + }; + } + + public static TextureViewDesc Texture2D(Texture texture, PixelFormat format, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.Texture2D, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = 0, + LayerCount = 1 + } + }; + } + + public static TextureViewDesc Texture2DArray(Texture texture, PixelFormat format, uint baseArrayLayer, uint layerCount, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.Texture2DArray, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = baseArrayLayer, + LayerCount = layerCount + } + }; + } + + public static TextureViewDesc Texture3D(Texture texture, PixelFormat format, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.Texture3D, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = 0, + LayerCount = 1 + } + }; + } + + public static TextureViewDesc TextureCube(Texture texture, PixelFormat format, uint baseCubeIndex, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.TextureCube, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = baseCubeIndex * 6, + LayerCount = 6 + } + }; + } + + public static TextureViewDesc TextureCubeArray(Texture texture, PixelFormat format, uint baseCubeIndex, uint cubeCount, uint baseMipLevel, uint mipLevelCount) + { + return new() + { + Texture = texture, + Type = TextureType.TextureCubeArray, + Format = format, + Range = new() + { + BaseMipLevel = baseMipLevel, + LevelCount = mipLevelCount, + BaseArrayLayer = baseCubeIndex * 6, + LayerCount = cubeCount * 6 + } + }; + } } From 3666c4c6e42fbfc522ad6ed7acc4743145806cc1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 19:30:29 +0800 Subject: [PATCH 070/466] Rename stencil reference parameter for clarity Renamed the parameter from 'reference' to 'stencilReference' in SetStencilReference and SetStencilReferenceImpl methods to improve code clarity and consistency. No functional changes were made. --- sources/Zenith.NET/CommandBuffer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 20a80913..4ea4026e 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -283,9 +283,9 @@ public void SetPipeline(MeshShadingPipeline pipeline) currentPipeline = pipeline; } - public void SetStencilReference(uint reference) + public void SetStencilReference(uint stencilReference) { - SetStencilReferenceImpl(reference); + SetStencilReferenceImpl(stencilReference); } public void SetBlendConstant(Vector4 blendConstant) @@ -630,7 +630,7 @@ private bool TryGetCurrentPipeline(string commandName, out TPipeline protected abstract void SetPipelineImpl(MeshShadingPipeline pipeline); - protected abstract void SetStencilReferenceImpl(uint reference); + protected abstract void SetStencilReferenceImpl(uint stencilReference); protected abstract void SetBlendConstantImpl(Vector4 blendConstant); From 49cdd34836477d1a6af18ea21504e9b2cff7a260 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 20:13:16 +0800 Subject: [PATCH 071/466] Refactor ValidationLayer into partial classes Split ValidationLayer into partial classes for descriptors and commands. Moved constants and messages to dedicated files. Centralized primitive checks in the main class. Added pre-validation for Buffer.Map and command methods. Improves modularity and maintainability without removing validation logic. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 2 +- sources/Zenith.NET.Metal/MTLBuffer.cs | 2 +- sources/Zenith.NET.Vulkan/VKBuffer.cs | 2 +- sources/Zenith.NET/Buffer.cs | 12 +- sources/Zenith.NET/CommandBuffer.cs | 10 + sources/Zenith.NET/SwapChain.cs | 7 + sources/Zenith.NET/ValidationConstants.cs | 24 + .../Zenith.NET/ValidationLayer.Commands.cs | 527 +++++++ .../Zenith.NET/ValidationLayer.Descriptors.cs | 282 ++++ sources/Zenith.NET/ValidationLayer.cs | 1300 +++-------------- sources/Zenith.NET/ValidationMessages.cs | 84 ++ 11 files changed, 1179 insertions(+), 1073 deletions(-) create mode 100644 sources/Zenith.NET/ValidationConstants.cs create mode 100644 sources/Zenith.NET/ValidationLayer.Commands.cs create mode 100644 sources/Zenith.NET/ValidationLayer.Descriptors.cs create mode 100644 sources/Zenith.NET/ValidationMessages.cs diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index a6956a61..4cf0566c 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -42,7 +42,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc public DXBufferView View { get; } - public override MappedMemory Map() + protected override MappedMemory MapImpl() { void* pointer; Resource.Map(0, (DxRange*)null, &pointer).Success(); diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs index 2dae0e98..9c499c77 100644 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -15,7 +15,7 @@ public MTLBuffer(MTLGraphicsContext context, BufferDesc desc) : base(context, de public MTLHeap Heap { get; } - public override MappedMemory Map() + protected override MappedMemory MapImpl() { return new() { Pointer = Buffer.Contents(), SizeInBytes = Desc.SizeInBytes }; } diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 0ba5915a..3010d266 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -88,7 +88,7 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBufferUsageFlags o public VKBufferView View { get; } - public override MappedMemory Map() + protected override MappedMemory MapImpl() { void* pointer; Context.Vk.MapMemory(Context.Device, DeviceMemory.DeviceMemory, 0, Desc.SizeInBytes, 0, &pointer).Success(); diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index f8184faf..eb8f7c30 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -12,7 +12,15 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public ResourceHandle StorageReadWriteHandle { get; } - public abstract MappedMemory Map(); + public MappedMemory Map() + { + if (Context.ValidationLayer?.ValidateMap(this) is false) + { + return default; + } + + return MapImpl(); + } public abstract void Unmap(); @@ -59,4 +67,6 @@ public void Download(uint offsetInBytes, BufferData data) commandBuffer.Submit().Wait(); } } + + protected abstract MappedMemory MapImpl(); } diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 4ea4026e..1168fca6 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -285,11 +285,21 @@ public void SetPipeline(MeshShadingPipeline pipeline) public void SetStencilReference(uint stencilReference) { + if (Context.ValidationLayer?.ValidateSetStencilReference(currentPipeline) is false) + { + return; + } + SetStencilReferenceImpl(stencilReference); } public void SetBlendConstant(Vector4 blendConstant) { + if (Context.ValidationLayer?.ValidateSetBlendConstant(currentPipeline, blendConstant) is false) + { + return; + } + SetBlendConstantImpl(blendConstant); } diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 34cc8269..3c44aa7b 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -16,6 +16,11 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public void Resize(uint width, uint height) { + if (Context.ValidationLayer?.ValidateResize(width, height) is false) + { + return; + } + desc.Surface.Width = width; desc.Surface.Height = height; @@ -26,6 +31,8 @@ public void Resize(uint width, uint height) public void Refresh(Surface surface) { + Context.ValidationLayer?.ValidateRefresh(surface); + desc.Surface = surface; RefreshImpl(); diff --git a/sources/Zenith.NET/ValidationConstants.cs b/sources/Zenith.NET/ValidationConstants.cs new file mode 100644 index 00000000..e18bda78 --- /dev/null +++ b/sources/Zenith.NET/ValidationConstants.cs @@ -0,0 +1,24 @@ +namespace Zenith.NET; + +internal static class ValidationConstants +{ + public const int CubeMapFaceCount = 6; + + public const int MaxAnisotropy = 16; + + public const int MaxColorAttachments = 8; + + public const uint IndirectDrawArgsSizeInBytes = 16; + + public const uint IndirectDrawIndexedArgsSizeInBytes = 20; + + public const uint IndirectDispatchArgsSizeInBytes = 12; + + public const uint IndirectDispatchMeshArgsSizeInBytes = 12; + + public const int IndexSizeUInt16 = 2; + + public const int IndexSizeUInt32 = 4; + + public const int MaxRayTracingInstanceId = 16777215; +} diff --git a/sources/Zenith.NET/ValidationLayer.Commands.cs b/sources/Zenith.NET/ValidationLayer.Commands.cs new file mode 100644 index 00000000..5205136f --- /dev/null +++ b/sources/Zenith.NET/ValidationLayer.Commands.cs @@ -0,0 +1,527 @@ +using System.Numerics; + +namespace Zenith.NET; + +partial class ValidationLayer +{ + #region Resources + + internal bool ValidateResource(string name, T? resource) where T : GraphicsResource + { + return CheckResource(name, resource); + } + + internal bool ValidateMap(Buffer buffer) + { + if (!CheckResource("Map.buffer", buffer)) + { + return false; + } + + if (buffer.Desc.Access is not (BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly)) + { + ReportError(string.Format(ValidationMessages.MustBeCpuAccessible, "Map.buffer")); + + return false; + } + + return true; + } + + #endregion + + #region SwapChain + + internal bool ValidateResize(uint width, uint height) + { + bool isValid = CheckGreaterThanZero("Resize.width", width); + isValid &= CheckGreaterThanZero("Resize.height", height); + + return isValid; + } + + internal void ValidateRefresh(Surface surface) + { + CheckSurface("Refresh.surface", surface); + } + + #endregion + + #region Transitions + + internal bool ValidateTransition(Texture texture, TextureSubresource subresource, TextureState state) + { + bool isValid = CheckResource("Transition.texture", texture); + + if (isValid) + { + isValid &= CheckTextureSubresource("Transition.subresource", texture, subresource); + } + + isValid &= CheckEnum("Transition.state", state); + + return isValid; + } + + #endregion + + #region Transfer + + internal bool ValidateUpload(Buffer buffer, uint offsetInBytes, BufferData data) + { + bool isValid = CheckResource("Upload.buffer", buffer); + isValid &= CheckBufferData("Upload.data", data); + + if (isValid) + { + isValid &= CheckBufferUsage("Upload.buffer", buffer, BufferUsages.CopyDst); + isValid &= CheckBufferRange("Upload.buffer", buffer, offsetInBytes, data.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateDownload(Buffer buffer, uint offsetInBytes, BufferData data) + { + bool isValid = CheckResource("Download.buffer", buffer); + isValid &= CheckBufferData("Download.data", data); + + if (isValid) + { + isValid &= CheckBufferUsage("Download.buffer", buffer, BufferUsages.CopySrc); + isValid &= CheckBufferRange("Download.buffer", buffer, offsetInBytes, data.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateUpload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + bool isValid = CheckResource("Upload.texture", texture); + isValid &= CheckTextureData("Upload.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); + + if (isValid) + { + isValid &= CheckTextureUsage("Upload.texture", texture, TextureUsages.CopyDst); + isValid &= CheckTextureRange("Upload.texture", texture, subresource, offset, extent); + } + + return isValid; + } + + internal bool ValidateDownload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + bool isValid = CheckResource("Download.texture", texture); + isValid &= CheckTextureData("Download.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); + + if (isValid) + { + isValid &= CheckTextureUsage("Download.texture", texture, TextureUsages.CopySrc); + isValid &= CheckTextureRange("Download.texture", texture, subresource, offset, extent); + } + + return isValid; + } + + internal bool ValidateCopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) + { + bool hasSrc = CheckResource("CopyBuffer.src", src); + bool hasDst = CheckResource("CopyBuffer.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckBufferUsage("CopyBuffer.src", src, BufferUsages.CopySrc); + isValid &= CheckBufferRange("CopyBuffer.src", src, srcOffsetInBytes, sizeInBytes); + } + + if (hasDst) + { + isValid &= CheckBufferUsage("CopyBuffer.dst", dst, BufferUsages.CopyDst); + isValid &= CheckBufferRange("CopyBuffer.dst", dst, dstOffsetInBytes, sizeInBytes); + } + + return isValid; + } + + internal bool ValidateCopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + bool hasSrc = CheckResource("CopyBufferToTexture.src", src); + bool hasDst = CheckResource("CopyBufferToTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckBufferUsage("CopyBufferToTexture.src", src, BufferUsages.CopySrc); + isValid &= CheckTextureDataLayout("CopyBufferToTexture.srcLayout", srcLayout, hasDst ? dst.Desc.Format : PixelFormat.Unknown, dstExtent); + isValid &= CheckBufferRange("CopyBufferToTexture.src", src, srcOffsetInBytes, srcLayout.SizeInBytes); + } + + if (hasDst) + { + isValid &= CheckTextureUsage("CopyBufferToTexture.dst", dst, TextureUsages.CopyDst); + isValid &= CheckTextureRange("CopyBufferToTexture.dst", dst, dstSubresource, dstOffset, dstExtent); + } + + return isValid; + } + + internal bool ValidateCopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) + { + bool hasSrc = CheckResource("CopyTexture.src", src); + bool hasDst = CheckResource("CopyTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureUsage("CopyTexture.src", src, TextureUsages.CopySrc); + isValid &= CheckTextureRange("CopyTexture.src", src, srcSubresource, srcOffset, extent); + } + + if (hasDst) + { + isValid &= CheckTextureUsage("CopyTexture.dst", dst, TextureUsages.CopyDst); + isValid &= CheckTextureRange("CopyTexture.dst", dst, dstSubresource, dstOffset, extent); + } + + if (hasSrc && hasDst) + { + isValid &= CheckSameValue("CopyTexture.Format", src.Desc.Format, dst.Desc.Format); + isValid &= CheckSameValue("CopyTexture.SampleCount", src.Desc.SampleCount, dst.Desc.SampleCount); + } + + return isValid; + } + + internal bool ValidateCopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) + { + bool hasSrc = CheckResource("CopyTextureToBuffer.src", src); + bool hasDst = CheckResource("CopyTextureToBuffer.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureUsage("CopyTextureToBuffer.src", src, TextureUsages.CopySrc); + isValid &= CheckTextureRange("CopyTextureToBuffer.src", src, srcSubresource, srcOffset, srcExtent); + isValid &= CheckTextureDataLayout("CopyTextureToBuffer.dstLayout", dstLayout, src.Desc.Format, srcExtent); + } + + if (hasDst) + { + isValid &= CheckBufferUsage("CopyTextureToBuffer.dst", dst, BufferUsages.CopyDst); + isValid &= CheckBufferRange("CopyTextureToBuffer.dst", dst, dstOffsetInBytes, dstLayout.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + bool hasSrc = CheckResource("ResolveTexture.src", src); + bool hasDst = CheckResource("ResolveTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureSubresource("ResolveTexture.srcSubresource", src, srcSubresource); + + if (src.Desc.SampleCount is SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeMultisampled, "ResolveTexture.src")); + isValid = false; + } + } + + if (hasDst) + { + isValid &= CheckTextureSubresource("ResolveTexture.dstSubresource", dst, dstSubresource); + + if (dst.Desc.SampleCount is not SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeSingleSampled, "ResolveTexture.dst")); + isValid = false; + } + } + + if (hasSrc && hasDst) + { + isValid &= CheckSameValue("ResolveTexture.Format", src.Desc.Format, dst.Desc.Format); + isValid &= CheckSameMipExtent("ResolveTexture", src, srcSubresource, dst, dstSubresource); + } + + return isValid; + } + + #endregion + + #region Render Pass / Viewport + + internal bool ValidateRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + bool isValid = true; + bool hasExtent = false; + uint renderWidth = 0; + uint renderHeight = 0; + + if (colorAttachments.Length > ValidationConstants.MaxColorAttachments) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "BeginRenderPass.colorAttachments.Length", ValidationConstants.MaxColorAttachments)); + isValid = false; + } + + if (colorAttachments.Length is 0 && depthStencilAttachment is null) + { + ReportError(string.Format(ValidationMessages.HasNoAttachments, "BeginRenderPass")); + isValid = false; + } + + for (int index = 0; index < colorAttachments.Length; index++) + { + isValid &= CheckColorAttachment($"BeginRenderPass.colorAttachments[{index}]", colorAttachments[index], ref hasExtent, ref renderWidth, ref renderHeight); + } + + if (depthStencilAttachment is { } attachment) + { + isValid &= CheckDepthStencilAttachment("BeginRenderPass.depthStencilAttachment", attachment, ref hasExtent, ref renderWidth, ref renderHeight); + } + + return isValid; + } + + internal bool ValidateScissors(ReadOnlySpan scissors) + { + bool isValid = true; + + if (scissors.Length is 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetScissors.scissors")); + return false; + } + + for (int index = 0; index < scissors.Length; index++) + { + isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Width", scissors[index].Width); + isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Height", scissors[index].Height); + } + + return isValid; + } + + internal bool ValidateViewports(ReadOnlySpan viewports) + { + bool isValid = true; + + if (viewports.Length is 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetViewports.viewports")); + return false; + } + + for (int index = 0; index < viewports.Length; index++) + { + Viewport viewport = viewports[index]; + + isValid &= CheckFinite($"SetViewports.viewports[{index}].Width", viewport.Width); + isValid &= CheckFinite($"SetViewports.viewports[{index}].Height", viewport.Height); + isValid &= CheckFinite($"SetViewports.viewports[{index}].MinDepth", viewport.MinDepth); + isValid &= CheckFinite($"SetViewports.viewports[{index}].MaxDepth", viewport.MaxDepth); + + if (viewport.Width <= 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Width")); + isValid = false; + } + + if (viewport.Height <= 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Height")); + isValid = false; + } + + if (viewport.MinDepth > viewport.MaxDepth) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"SetViewports.viewports[{index}].MinDepth", $"SetViewports.viewports[{index}].MaxDepth")); + isValid = false; + } + } + + return isValid; + } + + #endregion + + #region Pipeline State + + internal bool ValidateCurrentPipeline(string commandName) + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); + + return false; + } + + internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) where TPipeline : Pipeline + { + if (currentPipeline is null) + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); + } + else + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipelineType, commandName, typeof(TPipeline).Name, currentPipeline.GetType().Name)); + } + + return false; + } + + internal bool ValidateSetStencilReference(Pipeline? currentPipeline) + { + if (currentPipeline is null) + { + return ValidateCurrentPipeline("SetStencilReference"); + } + + if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) + { + return ValidateCurrentPipeline("SetStencilReference", currentPipeline); + } + + return true; + } + + internal bool ValidateSetBlendConstant(Pipeline? currentPipeline, Vector4 blendConstant) + { + bool isValid = true; + + if (currentPipeline is null) + { + isValid = ValidateCurrentPipeline("SetBlendConstant"); + } + else if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) + { + isValid = ValidateCurrentPipeline("SetBlendConstant", currentPipeline); + } + + isValid &= CheckFinite("SetBlendConstant.blendConstant.X", blendConstant.X); + isValid &= CheckFinite("SetBlendConstant.blendConstant.Y", blendConstant.Y); + isValid &= CheckFinite("SetBlendConstant.blendConstant.Z", blendConstant.Z); + isValid &= CheckFinite("SetBlendConstant.blendConstant.W", blendConstant.W); + + return isValid; + } + + internal bool ValidateSetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot, GraphicsPipeline pipeline) + { + bool isValid = CheckResource("SetVertexBuffer.buffer", buffer); + + if (isValid) + { + isValid &= CheckBufferUsage("SetVertexBuffer.buffer", buffer, BufferUsages.Vertex); + isValid &= CheckBufferOffset("SetVertexBuffer.buffer", buffer, offsetInBytes); + } + + if (pipeline.Desc.InputLayouts is not null && slot >= pipeline.Desc.InputLayouts.Length) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "SetVertexBuffer.slot", "GraphicsPipelineDesc.InputLayouts.Length")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) + { + bool isValid = CheckResource("SetIndexBuffer.buffer", buffer); + isValid &= CheckEnum("SetIndexBuffer.indexFormat", indexFormat); + + if (isValid) + { + isValid &= CheckBufferUsage("SetIndexBuffer.buffer", buffer, BufferUsages.Index); + isValid &= CheckBufferOffset("SetIndexBuffer.buffer", buffer, offsetInBytes); + } + + return isValid; + } + + internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout + { + uint sizeInBytes = Context.GraphicsApi switch + { + GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, + GraphicsApi.Metal => T.MetalSizeInBytes, + GraphicsApi.Vulkan => T.VulkanSizeInBytes, + _ => 0 + }; + + if (sizeInBytes is 0) + { + ReportError(string.Format(ValidationMessages.MustHaveNonZeroConstantsSize, typeof(T).Name, Context.GraphicsApi)); + + return false; + } + + return true; + } + + #endregion + + #region Indirect / Queries / Debug + + internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) + { + bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); + + if (isValid) + { + isValid &= CheckBufferUsage($"{commandName}.indirectBuffer", indirectBuffer, BufferUsages.Indirect); + isValid &= CheckBufferRange($"{commandName}.indirectBuffer", indirectBuffer, offsetInBytes, (ulong)argsSizeInBytes * drawOrDispatchCount, allowZeroSize: true); + } + + return isValid; + } + + internal bool ValidateBeginQuery(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("BeginQuery", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "BeginQuery")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateEndQuery(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("EndQuery", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "EndQuery")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateWriteTimestamp(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("WriteTimestamp", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is not QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustBeTimestampQuery, "WriteTimestamp")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateDebugLabel(string commandName, string? label) + { + return CheckStringNotWhitespace($"{commandName}.label", label); + } + + #endregion +} diff --git a/sources/Zenith.NET/ValidationLayer.Descriptors.cs b/sources/Zenith.NET/ValidationLayer.Descriptors.cs new file mode 100644 index 00000000..6a6cc3ac --- /dev/null +++ b/sources/Zenith.NET/ValidationLayer.Descriptors.cs @@ -0,0 +1,282 @@ +namespace Zenith.NET; + +partial class ValidationLayer +{ + internal void ValidateDesc(SwapChainDesc desc) + { + CheckSurface("SwapChainDesc.Surface", desc.Surface); + + CheckEnum("SwapChainDesc.ColorFormat", desc.ColorFormat); + + if (desc.DepthStencilFormat is { } format) + { + CheckEnum("SwapChainDesc.DepthStencilFormat", format); + } + } + + internal void ValidateDesc(BufferDesc desc) + { + CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); + + if (desc.StrideInBytes is 0) + { + ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); + } + + CheckEnum("BufferDesc.Access", desc.Access); + CheckFlags("BufferDesc.Usages", desc.Usages); + + if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) + { + const BufferUsages GpuOnlyUsages = BufferUsages.StorageReadWrite + | BufferUsages.Indirect + | BufferUsages.AccelerationStructure; + + BufferUsages forbiddenUsages = desc.Usages & GpuOnlyUsages; + + if (forbiddenUsages is not BufferUsages.None) + { + ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbiddenUsages, desc.Access)); + } + } + } + + internal void ValidateDesc(BufferViewDesc desc) + { + if (!CheckResource("BufferViewDesc.Buffer", desc.Buffer)) + { + return; + } + + CheckBufferRange("BufferViewDesc", desc.Buffer, desc.OffsetInBytes, desc.SizeInBytes); + + if (desc.StrideInBytes is 0) + { + ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "structured buffer views")); + } + } + + internal void ValidateDesc(TextureDesc desc) + { + CheckEnum("TextureDesc.Type", desc.Type); + CheckEnum("TextureDesc.Format", desc.Format); + + if (desc.Width is 0 || desc.Height is 0 || desc.Depth is 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); + } + + CheckGreaterThanZero("TextureDesc.MipLevels", desc.MipLevels); + CheckGreaterThanZero("TextureDesc.ArrayLayers", desc.ArrayLayers); + + if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) + { + ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); + } + + if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) + { + ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + } + + CheckEnum("TextureDesc.SampleCount", desc.SampleCount); + CheckFlags("TextureDesc.Usages", desc.Usages); + + if (desc.Usages is TextureUsages.None) + { + ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Usages")); + } + } + + internal void ValidateDesc(TextureViewDesc desc) + { + if (!CheckResource("TextureViewDesc.Texture", desc.Texture)) + { + return; + } + + CheckEnum("TextureViewDesc.Type", desc.Type); + CheckEnum("TextureViewDesc.Format", desc.Format); + + if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); + } + + if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) + { + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); + } + + if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); + } + + if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) + { + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); + } + + if (desc.Type is TextureType.TextureCube) + { + if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) + { + ReportError(string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); + } + } + + if (desc.Type is TextureType.TextureCubeArray) + { + if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); + } + } + } + + internal void ValidateDesc(SamplerDesc desc) + { + CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); + CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); + CheckEnum("SamplerDesc.MipmapFilter", desc.MipmapFilter); + CheckEnum("SamplerDesc.AddressU", desc.AddressU); + CheckEnum("SamplerDesc.AddressV", desc.AddressV); + CheckEnum("SamplerDesc.AddressW", desc.AddressW); + CheckEnum("SamplerDesc.CompareOp", desc.CompareOp); + + if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); + } + + if (desc.MinLod > desc.MaxLod) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); + } + + CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); + } + + internal void ValidateDesc(ShaderDesc desc) + { + CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); + CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); + CheckEnum("ShaderDesc.Stage", desc.Stage); + } + + internal void ValidateDesc(GraphicsPipelineDesc desc) + { + CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); + + CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); + CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); + + if (desc.InputLayouts is null) + { + ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); + } + else + { + for (int index = 0; index < desc.InputLayouts.Length; index++) + { + CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{index}]", desc.InputLayouts[index]); + } + } + + CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); + CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); + } + + internal void ValidateDesc(ComputePipelineDesc desc) + { + CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); + } + + internal void ValidateDesc(MeshShadingPipelineDesc desc) + { + CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); + + CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); + CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); + + if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) + { + ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); + } + + CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); + + if (desc.TaskShader is not null) + { + CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); + } + } + + internal void ValidateDesc(QueryHeapDesc desc) + { + CheckEnum("QueryHeapDesc.Type", desc.Type); + CheckGreaterThanZero("QueryHeapDesc.Count", desc.Count); + } + + internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) + { + if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) + { + for (int index = 0; index < desc.Geometries.Length; index++) + { + CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{index}]", desc.Geometries[index]); + } + } + + CheckFlags("BottomLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); + } + + internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) + { + if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) + { + for (int index = 0; index < desc.Instances.Length; index++) + { + CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{index}]", desc.Instances[index]); + } + } + + CheckFlags("TopLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); + } + + internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) + { + if (!oldDesc.BuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + { + ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.BuildFlags", AccelerationStructureBuildFlags.AllowUpdate)); + } + + ValidateDesc(newDesc); + + if (newDesc.Instances is null) + { + return; + } + + if (oldDesc.Instances.Length != newDesc.Instances.Length) + { + ReportError(ValidationMessages.InstanceCountMustRemainSame); + } + } +} diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 15eb0437..13a9489f 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -1,11 +1,8 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Reflection; +using System.Numerics; namespace Zenith.NET; -public abstract class ValidationLayer(GraphicsContext context) : GraphicsResource(context) +public abstract partial class ValidationLayer(GraphicsContext context) : GraphicsResource(context) { private static readonly Dictionary ExpectedSurfaceHandleCount = new() { @@ -22,720 +19,18 @@ protected void Report(MessageSource source, MessageSeverity severity, string mes Context.OnValidationMessage(new(source, severity, message)); } - internal void ValidateDesc(SwapChainDesc desc) - { - CheckSurface("SwapChainDesc.Surface", desc.Surface); - - CheckEnum("SwapChainDesc.ColorFormat", desc.ColorFormat); - - if (desc.DepthStencilFormat is { } format) - { - CheckEnum("SwapChainDesc.DepthStencilFormat", format); - } - } - - internal void ValidateDesc(BufferDesc desc) - { - CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); - - if (desc.StrideInBytes is 0) - { - ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); - } - - CheckEnum("BufferDesc.Access", desc.Access); - CheckFlags("BufferDesc.Usages", desc.Usages); - - if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) - { - const BufferUsages GpuOnlyUsages = BufferUsages.StorageReadWrite - | BufferUsages.Indirect - | BufferUsages.AccelerationStructure; - - BufferUsages forbiddenUsages = desc.Usages & GpuOnlyUsages; - - if (forbiddenUsages is not BufferUsages.None) - { - ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbiddenUsages, desc.Access)); - } - } - } - - internal void ValidateDesc(BufferViewDesc desc) - { - if (!CheckResource("BufferViewDesc.Buffer", desc.Buffer)) - { - return; - } - - CheckBufferRange("BufferViewDesc", desc.Buffer, desc.OffsetInBytes, desc.SizeInBytes); - - if (desc.StrideInBytes is 0) - { - ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "structured buffer views")); - } - } - - internal void ValidateDesc(TextureDesc desc) - { - CheckEnum("TextureDesc.Type", desc.Type); - CheckEnum("TextureDesc.Format", desc.Format); - - if (desc.Width is 0 || desc.Height is 0 || desc.Depth is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); - } - - CheckGreaterThanZero("TextureDesc.MipLevels", desc.MipLevels); - CheckGreaterThanZero("TextureDesc.ArrayLayers", desc.ArrayLayers); - - if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) - { - ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); - } - - if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) - { - ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); - } - - CheckEnum("TextureDesc.SampleCount", desc.SampleCount); - CheckFlags("TextureDesc.Usages", desc.Usages); - - if (desc.Usages is TextureUsages.None) - { - ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Usages")); - } - } - - internal void ValidateDesc(TextureViewDesc desc) - { - if (!CheckResource("TextureViewDesc.Texture", desc.Texture)) - { - return; - } - - CheckEnum("TextureViewDesc.Type", desc.Type); - CheckEnum("TextureViewDesc.Format", desc.Format); - - if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); - } - - if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); - } - - if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); - } - - if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); - } - - if (desc.Type is TextureType.TextureCube) - { - if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) - { - ReportError(string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); - } - } - - if (desc.Type is TextureType.TextureCubeArray) - { - if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); - } - } - } - - internal void ValidateDesc(SamplerDesc desc) - { - CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); - CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); - CheckEnum("SamplerDesc.MipmapFilter", desc.MipmapFilter); - CheckEnum("SamplerDesc.AddressU", desc.AddressU); - CheckEnum("SamplerDesc.AddressV", desc.AddressV); - CheckEnum("SamplerDesc.AddressW", desc.AddressW); - CheckEnum("SamplerDesc.CompareOp", desc.CompareOp); - - if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); - } - - if (desc.MinLod > desc.MaxLod) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); - } - - CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); - } - - internal void ValidateDesc(ShaderDesc desc) - { - CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); - CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); - CheckEnum("ShaderDesc.Stage", desc.Stage); - } - - internal void ValidateDesc(GraphicsPipelineDesc desc) - { - CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); - - CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); - CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); - - if (desc.InputLayouts is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); - } - else - { - for (int index = 0; index < desc.InputLayouts.Length; index++) - { - CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{index}]", desc.InputLayouts[index]); - } - } - - CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); - CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - } - - internal void ValidateDesc(ComputePipelineDesc desc) - { - CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); - } - - internal void ValidateDesc(MeshShadingPipelineDesc desc) - { - CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); - - CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); - CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); - - if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) - { - ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); - } - - CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - - if (desc.TaskShader is not null) - { - CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); - } - } - - internal void ValidateDesc(QueryHeapDesc desc) - { - CheckEnum("QueryHeapDesc.Type", desc.Type); - CheckGreaterThanZero("QueryHeapDesc.Count", desc.Count); - } - - internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) - { - if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) - { - for (int index = 0; index < desc.Geometries.Length; index++) - { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{index}]", desc.Geometries[index]); - } - } - - CheckFlags("BottomLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) - { - if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) - { - for (int index = 0; index < desc.Instances.Length; index++) - { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{index}]", desc.Instances[index]); - } - } - - CheckFlags("TopLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) - { - if (!oldDesc.BuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) - { - ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.BuildFlags", AccelerationStructureBuildFlags.AllowUpdate)); - } - - ValidateDesc(newDesc); - - if (newDesc.Instances is null) - { - return; - } - - if (oldDesc.Instances.Length != newDesc.Instances.Length) - { - ReportError(ValidationMessages.InstanceCountMustRemainSame); - } - } - - internal bool ValidateResource(string name, T? resource) where T : GraphicsResource - { - return CheckResource(name, resource); - } - - internal bool ValidateTransition(Texture texture, TextureSubresource subresource, TextureState state) - { - bool isValid = CheckResource("Transition.texture", texture); - - if (isValid) - { - isValid &= CheckTextureSubresource("Transition.subresource", texture, subresource); - } - - isValid &= CheckEnum("Transition.state", state); - - return isValid; - } - - internal bool ValidateUpload(Buffer buffer, uint offsetInBytes, BufferData data) - { - bool isValid = CheckResource("Upload.buffer", buffer); - isValid &= CheckBufferData("Upload.data", data); - - if (isValid) - { - isValid &= CheckBufferUsage("Upload.buffer", buffer, BufferUsages.CopyDst); - isValid &= CheckBufferRange("Upload.buffer", buffer, offsetInBytes, data.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateDownload(Buffer buffer, uint offsetInBytes, BufferData data) - { - bool isValid = CheckResource("Download.buffer", buffer); - isValid &= CheckBufferData("Download.data", data); - - if (isValid) - { - isValid &= CheckBufferUsage("Download.buffer", buffer, BufferUsages.CopySrc); - isValid &= CheckBufferRange("Download.buffer", buffer, offsetInBytes, data.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateUpload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) - { - bool isValid = CheckResource("Upload.texture", texture); - isValid &= CheckTextureData("Upload.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); - - if (isValid) - { - isValid &= CheckTextureUsage("Upload.texture", texture, TextureUsages.CopyDst); - isValid &= CheckTextureRange("Upload.texture", texture, subresource, offset, extent); - } - - return isValid; - } - - internal bool ValidateDownload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) - { - bool isValid = CheckResource("Download.texture", texture); - isValid &= CheckTextureData("Download.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); - - if (isValid) - { - isValid &= CheckTextureUsage("Download.texture", texture, TextureUsages.CopySrc); - isValid &= CheckTextureRange("Download.texture", texture, subresource, offset, extent); - } - - return isValid; - } - - internal bool ValidateCopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) - { - bool hasSrc = CheckResource("CopyBuffer.src", src); - bool hasDst = CheckResource("CopyBuffer.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckBufferUsage("CopyBuffer.src", src, BufferUsages.CopySrc); - isValid &= CheckBufferRange("CopyBuffer.src", src, srcOffsetInBytes, sizeInBytes); - } - - if (hasDst) - { - isValid &= CheckBufferUsage("CopyBuffer.dst", dst, BufferUsages.CopyDst); - isValid &= CheckBufferRange("CopyBuffer.dst", dst, dstOffsetInBytes, sizeInBytes); - } - - return isValid; - } - - internal bool ValidateCopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) - { - bool hasSrc = CheckResource("CopyBufferToTexture.src", src); - bool hasDst = CheckResource("CopyBufferToTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckBufferUsage("CopyBufferToTexture.src", src, BufferUsages.CopySrc); - isValid &= CheckTextureDataLayout("CopyBufferToTexture.srcLayout", srcLayout, hasDst ? dst.Desc.Format : PixelFormat.Unknown, dstExtent); - isValid &= CheckBufferRange("CopyBufferToTexture.src", src, srcOffsetInBytes, srcLayout.SizeInBytes); - } - - if (hasDst) - { - isValid &= CheckTextureUsage("CopyBufferToTexture.dst", dst, TextureUsages.CopyDst); - isValid &= CheckTextureRange("CopyBufferToTexture.dst", dst, dstSubresource, dstOffset, dstExtent); - } - - return isValid; - } - - internal bool ValidateCopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) - { - bool hasSrc = CheckResource("CopyTexture.src", src); - bool hasDst = CheckResource("CopyTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureUsage("CopyTexture.src", src, TextureUsages.CopySrc); - isValid &= CheckTextureRange("CopyTexture.src", src, srcSubresource, srcOffset, extent); - } - - if (hasDst) - { - isValid &= CheckTextureUsage("CopyTexture.dst", dst, TextureUsages.CopyDst); - isValid &= CheckTextureRange("CopyTexture.dst", dst, dstSubresource, dstOffset, extent); - } - - if (hasSrc && hasDst) - { - isValid &= CheckSameValue("CopyTexture.Format", src.Desc.Format, dst.Desc.Format); - isValid &= CheckSameValue("CopyTexture.SampleCount", src.Desc.SampleCount, dst.Desc.SampleCount); - } - - return isValid; - } - - internal bool ValidateCopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) - { - bool hasSrc = CheckResource("CopyTextureToBuffer.src", src); - bool hasDst = CheckResource("CopyTextureToBuffer.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureUsage("CopyTextureToBuffer.src", src, TextureUsages.CopySrc); - isValid &= CheckTextureRange("CopyTextureToBuffer.src", src, srcSubresource, srcOffset, srcExtent); - isValid &= CheckTextureDataLayout("CopyTextureToBuffer.dstLayout", dstLayout, src.Desc.Format, srcExtent); - } - - if (hasDst) - { - isValid &= CheckBufferUsage("CopyTextureToBuffer.dst", dst, BufferUsages.CopyDst); - isValid &= CheckBufferRange("CopyTextureToBuffer.dst", dst, dstOffsetInBytes, dstLayout.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) - { - bool hasSrc = CheckResource("ResolveTexture.src", src); - bool hasDst = CheckResource("ResolveTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureSubresource("ResolveTexture.srcSubresource", src, srcSubresource); - - if (src.Desc.SampleCount is SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeMultisampled, "ResolveTexture.src")); - isValid = false; - } - } - - if (hasDst) - { - isValid &= CheckTextureSubresource("ResolveTexture.dstSubresource", dst, dstSubresource); - - if (dst.Desc.SampleCount is not SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeSingleSampled, "ResolveTexture.dst")); - isValid = false; - } - } - - if (hasSrc && hasDst) - { - isValid &= CheckSameValue("ResolveTexture.Format", src.Desc.Format, dst.Desc.Format); - isValid &= CheckSameMipExtent("ResolveTexture", src, srcSubresource, dst, dstSubresource); - } - - return isValid; - } - - internal bool ValidateRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) - { - bool isValid = true; - bool hasExtent = false; - uint renderWidth = 0; - uint renderHeight = 0; - - if (colorAttachments.Length > ValidationConstants.MaxColorAttachments) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "BeginRenderPass.colorAttachments.Length", ValidationConstants.MaxColorAttachments)); - isValid = false; - } - - if (colorAttachments.Length is 0 && depthStencilAttachment is null) - { - ReportError(string.Format(ValidationMessages.HasNoAttachments, "BeginRenderPass")); - isValid = false; - } - - for (int index = 0; index < colorAttachments.Length; index++) - { - isValid &= CheckColorAttachment($"BeginRenderPass.colorAttachments[{index}]", colorAttachments[index], ref hasExtent, ref renderWidth, ref renderHeight); - } - - if (depthStencilAttachment is { } attachment) - { - isValid &= CheckDepthStencilAttachment("BeginRenderPass.depthStencilAttachment", attachment, ref hasExtent, ref renderWidth, ref renderHeight); - } - - return isValid; - } - - internal bool ValidateScissors(ReadOnlySpan scissors) - { - bool isValid = true; - - if (scissors.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetScissors.scissors")); - return false; - } - - for (int index = 0; index < scissors.Length; index++) - { - isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Width", scissors[index].Width); - isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Height", scissors[index].Height); - } - - return isValid; - } - - internal bool ValidateViewports(ReadOnlySpan viewports) - { - bool isValid = true; - - if (viewports.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetViewports.viewports")); - return false; - } - - for (int index = 0; index < viewports.Length; index++) - { - Viewport viewport = viewports[index]; - - isValid &= CheckFinite($"SetViewports.viewports[{index}].Width", viewport.Width); - isValid &= CheckFinite($"SetViewports.viewports[{index}].Height", viewport.Height); - isValid &= CheckFinite($"SetViewports.viewports[{index}].MinDepth", viewport.MinDepth); - isValid &= CheckFinite($"SetViewports.viewports[{index}].MaxDepth", viewport.MaxDepth); - - if (viewport.Width <= 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Width")); - isValid = false; - } - - if (viewport.Height <= 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Height")); - isValid = false; - } - - if (viewport.MinDepth > viewport.MaxDepth) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"SetViewports.viewports[{index}].MinDepth", $"SetViewports.viewports[{index}].MaxDepth")); - isValid = false; - } - } - - return isValid; - } - - internal bool ValidateCurrentPipeline(string commandName) - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); - - return false; - } - - internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) where TPipeline : Pipeline - { - if (currentPipeline is null) - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); - } - else - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipelineType, commandName, typeof(TPipeline).Name, currentPipeline.GetType().Name)); - } - - return false; - } - - internal bool ValidateSetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot, GraphicsPipeline pipeline) - { - bool isValid = CheckResource("SetVertexBuffer.buffer", buffer); - - if (isValid) - { - isValid &= CheckBufferUsage("SetVertexBuffer.buffer", buffer, BufferUsages.Vertex); - isValid &= CheckBufferOffset("SetVertexBuffer.buffer", buffer, offsetInBytes); - } - - if (pipeline.Desc.InputLayouts is not null && slot >= pipeline.Desc.InputLayouts.Length) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "SetVertexBuffer.slot", "GraphicsPipelineDesc.InputLayouts.Length")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) - { - bool isValid = CheckResource("SetIndexBuffer.buffer", buffer); - isValid &= CheckEnum("SetIndexBuffer.indexFormat", indexFormat); - - if (isValid) - { - isValid &= CheckBufferUsage("SetIndexBuffer.buffer", buffer, BufferUsages.Index); - isValid &= CheckBufferOffset("SetIndexBuffer.buffer", buffer, offsetInBytes); - } - - return isValid; - } - - internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout - { - bool isLayoutValid = ConstantsLayoutCache.GetOrAdd(typeof(T), CheckConstantsLayout); - - if (!isLayoutValid) - { - ReportError(string.Format(ValidationMessages.HasInvalidConstantsLayout, typeof(T).Name)); - - return false; - } - - uint sizeInBytes = Context.GraphicsApi switch - { - GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, - GraphicsApi.Metal => T.MetalSizeInBytes, - GraphicsApi.Vulkan => T.VulkanSizeInBytes, - _ => 0 - }; - - if (sizeInBytes is 0) - { - ReportError(string.Format(ValidationMessages.MustHaveNonZeroConstantsSize, typeof(T).Name, Context.GraphicsApi)); - - return false; - } - - return true; - } - - internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) - { - bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); - - if (isValid) - { - isValid &= CheckBufferUsage($"{commandName}.indirectBuffer", indirectBuffer, BufferUsages.Indirect); - isValid &= CheckBufferRange($"{commandName}.indirectBuffer", indirectBuffer, offsetInBytes, (ulong)argsSizeInBytes * drawOrDispatchCount, allowZeroSize: true); - } - - return isValid; - } - - internal bool ValidateBeginQuery(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("BeginQuery", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "BeginQuery")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateEndQuery(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("EndQuery", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "EndQuery")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateWriteTimestamp(QueryHeap queryHeap, uint index) + private void ReportError(string message) { - bool isValid = CheckQuery("WriteTimestamp", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is not QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustBeTimestampQuery, "WriteTimestamp")); - isValid = false; - } - - return isValid; + Report(MessageSource.Framework, MessageSeverity.Error, message); } - internal bool ValidateDebugLabel(string commandName, string? label) + private void ReportWarning(string message) { - return CheckStringNotWhitespace($"{commandName}.label", label); + Report(MessageSource.Framework, MessageSeverity.Warning, message); } + #region Primitive Checks + private bool CheckResource(string name, T? resource) where T : GraphicsResource { if (resource is null) @@ -823,6 +118,34 @@ private bool CheckGreaterThanZero(string name, T value) where T : struct, INu return true; } + private bool CheckFinite(string name, float value) + { + if (!float.IsFinite(value)) + { + ReportError(string.Format(ValidationMessages.MustBeFinite, name)); + + return false; + } + + return true; + } + + private bool CheckSameValue(string name, T first, T second) + { + if (!EqualityComparer.Default.Equals(first, second)) + { + ReportError(string.Format(ValidationMessages.MustHaveSameValue, name, first, second)); + + return false; + } + + return true; + } + + #endregion + + #region Buffer Checks + private bool CheckBufferData(string name, BufferData data) { bool isValid = true; @@ -838,6 +161,53 @@ private bool CheckBufferData(string name, BufferData data) return isValid; } + private bool CheckBufferUsage(string name, Buffer buffer, BufferUsages requiredUsage) + { + if (!buffer.Desc.Usages.HasFlag(requiredUsage)) + { + ReportError(string.Format(ValidationMessages.MustHaveUsage, name, requiredUsage)); + + return false; + } + + return true; + } + + private bool CheckBufferOffset(string name, Buffer buffer, uint offsetInBytes) + { + if (offsetInBytes > buffer.Desc.SizeInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.OffsetInBytes", name)); + + return false; + } + + return true; + } + + private bool CheckBufferRange(string name, Buffer buffer, uint offsetInBytes, ulong sizeInBytes, bool allowZeroSize = false) + { + bool isValid = CheckBufferOffset(name, buffer, offsetInBytes); + + if (!allowZeroSize && sizeInBytes is 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.SizeInBytes")); + isValid = false; + } + + if ((ulong)offsetInBytes + sizeInBytes > buffer.Desc.SizeInBytes) + { + ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, name, "the buffer")); + isValid = false; + } + + return isValid; + } + + #endregion + + #region Texture Checks + private bool CheckTextureData(string name, TextureData data, PixelFormat format, Extent3D extent) { bool isValid = true; @@ -899,18 +269,6 @@ private bool CheckTextureDataLayout(string name, TextureDataLayout layout, Pixel return isValid; } - private bool CheckBufferUsage(string name, Buffer buffer, BufferUsages requiredUsage) - { - if (!buffer.Desc.Usages.HasFlag(requiredUsage)) - { - ReportError(string.Format(ValidationMessages.MustHaveUsage, name, requiredUsage)); - - return false; - } - - return true; - } - private bool CheckTextureUsage(string name, Texture texture, TextureUsages requiredUsage) { if (!texture.Desc.Usages.HasFlag(requiredUsage)) @@ -923,37 +281,6 @@ private bool CheckTextureUsage(string name, Texture texture, TextureUsages requi return true; } - private bool CheckBufferOffset(string name, Buffer buffer, uint offsetInBytes) - { - if (offsetInBytes > buffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.OffsetInBytes", name)); - - return false; - } - - return true; - } - - private bool CheckBufferRange(string name, Buffer buffer, uint offsetInBytes, ulong sizeInBytes, bool allowZeroSize = false) - { - bool isValid = CheckBufferOffset(name, buffer, offsetInBytes); - - if (!allowZeroSize && sizeInBytes is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.SizeInBytes")); - isValid = false; - } - - if ((ulong)offsetInBytes + sizeInBytes > buffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, name, "the buffer")); - isValid = false; - } - - return isValid; - } - private bool CheckTextureSubresource(string name, Texture texture, TextureSubresource subresource) { bool isValid = true; @@ -1018,18 +345,6 @@ private bool CheckTextureRange(string name, Texture texture, TextureSubresource return isValid; } - private bool CheckSameValue(string name, T first, T second) - { - if (!EqualityComparer.Default.Equals(first, second)) - { - ReportError(string.Format(ValidationMessages.MustHaveSameValue, name, first, second)); - - return false; - } - - return true; - } - private bool CheckSameMipExtent(string name, Texture first, TextureSubresource firstSubresource, Texture second, TextureSubresource secondSubresource) { if (!CheckTextureSubresource($"{name}.firstSubresource", first, firstSubresource) || !CheckTextureSubresource($"{name}.secondSubresource", second, secondSubresource)) @@ -1048,6 +363,34 @@ private bool CheckSameMipExtent(string name, Texture first, TextureSubresource f return isValid; } + private bool CheckColorFormat(string name, PixelFormat format) + { + if (ZenithHelper.HasDepth(format) || ZenithHelper.HasStencil(format)) + { + ReportError(string.Format(ValidationMessages.MustNotBeDepthStencilFormat, name)); + + return false; + } + + return true; + } + + private bool CheckDepthStencilFormat(string name, PixelFormat format) + { + if (!ZenithHelper.HasDepth(format) && !ZenithHelper.HasStencil(format)) + { + ReportError(string.Format(ValidationMessages.MustBeDepthStencilFormat, name)); + + return false; + } + + return true; + } + + #endregion + + #region Render Pass Checks + private bool CheckColorAttachment(string name, ColorAttachment attachment, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) { bool hasTexture = CheckResource($"{name}.Texture", attachment.Texture); @@ -1123,106 +466,22 @@ private bool CheckDepthStencilAttachment(string name, DepthStencilAttachment att isValid &= CheckEnum($"{name}.StencilStoreOp", attachment.StencilStoreOp); if (attachment.ClearDepth is < 0.0f or > 1.0f) - { - ReportError(string.Format(ValidationMessages.MustBeBetween, $"{name}.ClearDepth", 0.0f, 1.0f)); - isValid = false; - } - - if (hasTexture) - { - isValid &= CheckTextureUsage($"{name}.Texture", attachment.Texture, TextureUsages.DepthStencil); - isValid &= CheckDepthStencilFormat($"{name}.Texture.Desc.Format", attachment.Texture.Desc.Format); - - if (CheckTextureSubresource($"{name}.Subresource", attachment.Texture, attachment.Subresource)) - { - isValid &= CheckRenderPassExtent(name, attachment.Texture, attachment.Subresource, ref hasExtent, ref renderWidth, ref renderHeight); - } - else - { - isValid = false; - } - } - - return isValid; - } - - private bool CheckColorFormat(string name, PixelFormat format) - { - if (ZenithHelper.HasDepth(format) || ZenithHelper.HasStencil(format)) - { - ReportError(string.Format(ValidationMessages.MustNotBeDepthStencilFormat, name)); - - return false; - } - - return true; - } - - private bool CheckDepthStencilFormat(string name, PixelFormat format) - { - if (!ZenithHelper.HasDepth(format) && !ZenithHelper.HasStencil(format)) - { - ReportError(string.Format(ValidationMessages.MustBeDepthStencilFormat, name)); - - return false; - } - - return true; - } - - private bool CheckRenderPassExtent(string name, Texture texture, TextureSubresource subresource, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) - { - ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, subresource.MipLevel, out uint width, out uint height, out _); - - if (!hasExtent) - { - renderWidth = width; - renderHeight = height; - hasExtent = true; - - return true; - } - - bool isValid = true; - - if (width != renderWidth) - { - ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Width", width, renderWidth)); - isValid = false; - } - - if (height != renderHeight) - { - ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Height", height, renderHeight)); - isValid = false; - } - - return isValid; - } - - private bool CheckFinite(string name, float value) - { - if (!float.IsFinite(value)) - { - ReportError(string.Format(ValidationMessages.MustBeFinite, name)); - - return false; - } - - return true; - } - - private bool CheckQuery(string commandName, QueryHeap queryHeap, uint index) - { - bool isValid = CheckResource($"{commandName}.queryHeap", queryHeap); + { + ReportError(string.Format(ValidationMessages.MustBeBetween, $"{name}.ClearDepth", 0.0f, 1.0f)); + isValid = false; + } - if (isValid) + if (hasTexture) { - isValid &= CheckEnum($"{commandName}.queryHeap.Desc.Type", queryHeap.Desc.Type); + isValid &= CheckTextureUsage($"{name}.Texture", attachment.Texture, TextureUsages.DepthStencil); + isValid &= CheckDepthStencilFormat($"{name}.Texture.Desc.Format", attachment.Texture.Desc.Format); - if (index >= queryHeap.Desc.Count) + if (CheckTextureSubresource($"{name}.Subresource", attachment.Texture, attachment.Subresource)) + { + isValid &= CheckRenderPassExtent(name, attachment.Texture, attachment.Subresource, ref hasExtent, ref renderWidth, ref renderHeight); + } + else { - ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{commandName}.index", "QueryHeapDesc.Count")); isValid = false; } } @@ -1230,73 +489,39 @@ private bool CheckQuery(string commandName, QueryHeap queryHeap, uint index) return isValid; } - private void CheckSurface(string name, Surface surface) + private bool CheckRenderPassExtent(string name, Texture texture, TextureSubresource subresource, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) { - if (!CheckEnum($"{name}.Type", surface.Type)) - { - return; - } + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, subresource.MipLevel, out uint width, out uint height, out _); - if (!ExpectedSurfaceHandleCount.TryGetValue(surface.Type, out int expectedHandleCount)) + if (!hasExtent) { - ReportError(string.Format(ValidationMessages.HasUnsupportedSurfaceType, name, surface.Type)); + renderWidth = width; + renderHeight = height; + hasExtent = true; - return; + return true; } - if (surface.NativeHandles is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.NativeHandles")); - - return; - } + bool isValid = true; - if (surface.NativeHandles.Length != expectedHandleCount) + if (width != renderWidth) { - ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.NativeHandles", expectedHandleCount, surface.Type)); - - return; + ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Width", width, renderWidth)); + isValid = false; } - for (int index = 0; index < surface.NativeHandles.Length; index++) + if (height != renderHeight) { - if (surface.NativeHandles[index] is 0) - { - if (expectedHandleCount is 1) - { - ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.NativeHandles[0]", surface.Type)); - } - else - { - ReportError(string.Format(ValidationMessages.MustBeValidHandles, $"{name}.NativeHandles", surface.Type)); - } - - return; - } + ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Height", height, renderHeight)); + isValid = false; } - CheckGreaterThanZero($"{name}.Width", surface.Width); - CheckGreaterThanZero($"{name}.Height", surface.Height); + return isValid; } - private void CheckInputLayout(string name, InputLayout inputLayout) - { - if (CheckArrayNotEmpty($"{name}.InputElements", inputLayout.InputElements)) - { - for (int index = 0; index < inputLayout.InputElements.Length; index++) - { - CheckInputElement($"{name}.InputElements[{index}]", inputLayout.InputElements[index]); - } - } - - CheckGreaterThanZero($"{name}.StrideInBytes", inputLayout.StrideInBytes); - } + #endregion - private void CheckInputElement(string name, InputElement inputElement) - { - CheckEnum($"{name}.Format", inputElement.Format); - CheckEnum($"{name}.Semantic", inputElement.Semantic); - } + #region Pipeline State Checks private void CheckRenderState(string name, RenderState renderState) { @@ -1377,6 +602,96 @@ private void CheckAttachmentFormats(string name, AttachmentFormats attachmentFor } } + private void CheckInputLayout(string name, InputLayout inputLayout) + { + if (CheckArrayNotEmpty($"{name}.InputElements", inputLayout.InputElements)) + { + for (int index = 0; index < inputLayout.InputElements.Length; index++) + { + CheckInputElement($"{name}.InputElements[{index}]", inputLayout.InputElements[index]); + } + } + + CheckGreaterThanZero($"{name}.StrideInBytes", inputLayout.StrideInBytes); + } + + private void CheckInputElement(string name, InputElement inputElement) + { + CheckEnum($"{name}.Format", inputElement.Format); + CheckEnum($"{name}.Semantic", inputElement.Semantic); + } + + #endregion + + #region Surface / Query / Ray Tracing Checks + + private void CheckSurface(string name, Surface surface) + { + if (!CheckEnum($"{name}.Type", surface.Type)) + { + return; + } + + if (!ExpectedSurfaceHandleCount.TryGetValue(surface.Type, out int expectedHandleCount)) + { + ReportError(string.Format(ValidationMessages.HasUnsupportedSurfaceType, name, surface.Type)); + + return; + } + + if (surface.NativeHandles is null) + { + ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.NativeHandles")); + + return; + } + + if (surface.NativeHandles.Length != expectedHandleCount) + { + ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.NativeHandles", expectedHandleCount, surface.Type)); + + return; + } + + for (int index = 0; index < surface.NativeHandles.Length; index++) + { + if (surface.NativeHandles[index] is 0) + { + if (expectedHandleCount is 1) + { + ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.NativeHandles[0]", surface.Type)); + } + else + { + ReportError(string.Format(ValidationMessages.MustBeValidHandles, $"{name}.NativeHandles", surface.Type)); + } + + return; + } + } + + CheckGreaterThanZero($"{name}.Width", surface.Width); + CheckGreaterThanZero($"{name}.Height", surface.Height); + } + + private bool CheckQuery(string commandName, QueryHeap queryHeap, uint index) + { + bool isValid = CheckResource($"{commandName}.queryHeap", queryHeap); + + if (isValid) + { + isValid &= CheckEnum($"{commandName}.queryHeap.Desc.Type", queryHeap.Desc.Type); + + if (index >= queryHeap.Desc.Count) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{commandName}.index", "QueryHeapDesc.Count")); + isValid = false; + } + } + + return isValid; + } + private void CheckRayTracingGeometry(string name, RayTracingGeometry geometry) { if (!CheckEnum($"{name}.Type", geometry.Type)) @@ -1455,158 +770,5 @@ private void CheckRayTracingInstance(string name, RayTracingInstance instance) CheckFlags($"{name}.Flags", instance.Flags); } - private void ReportError(string message) - { - Report(MessageSource.Framework, MessageSeverity.Error, message); - } - - private void ReportWarning(string message) - { - Report(MessageSource.Framework, MessageSeverity.Warning, message); - } - - private static readonly ConcurrentDictionary ConstantsLayoutCache = new(); - - [UnconditionalSuppressMessage("Trimming", "IL2070", - Justification = "Constants layout types are user-defined structs referenced via T; the trimmer preserves their fields and attributes through the static abstract IConstantsLayout contract.")] - private static bool CheckConstantsLayout(Type type) - { - bool isValid = true; - - if (type.GetCustomAttribute() is null) - { - isValid = false; - } - - foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) - { - int bindingAttributeCount = 0; - bool expectsResourceHandle = false; - - if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; } - if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; } - if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; } - if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; expectsResourceHandle = true; } - if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; expectsResourceHandle = true; } - if (field.GetCustomAttribute() is not null) { bindingAttributeCount++; expectsResourceHandle = true; } - - if (bindingAttributeCount > 1) - { - isValid = false; - } - - if (expectsResourceHandle && field.FieldType != typeof(ResourceHandle)) - { - isValid = false; - } - } - - return isValid; - } -} - -internal static class ValidationConstants -{ - public const int CubeMapFaceCount = 6; - - public const int MaxAnisotropy = 16; - - public const int MaxColorAttachments = 8; - - public const uint IndirectDrawArgsSizeInBytes = 16; - - public const uint IndirectDrawIndexedArgsSizeInBytes = 20; - - public const uint IndirectDispatchArgsSizeInBytes = 12; - - public const uint IndirectDispatchMeshArgsSizeInBytes = 12; - - public const int IndexSizeUInt16 = 2; - - public const int IndexSizeUInt32 = 4; - - public const int MaxRayTracingInstanceId = 16777215; -} - -file static class ValidationMessages -{ - public const string MustNotBeNull = "{0} must not be null."; - - public const string MustNotBeZero = "{0} must not be zero."; - - public const string MustHaveExactlyNHandles = "{0} must have exactly {1} handles for {2}."; - - public const string MustBeValidHandle = "{0} must be a valid handle for {1}."; - - public const string MustBeValidHandles = "{0} must be valid handles for {1}."; - - public const string HasUnsupportedSurfaceType = "{0} has unsupported SurfaceType '{1}'."; - - public const string HasInvalidValue = "{0} has an invalid value '{1}'."; - - public const string HasNoAttachments = "{0} has no attachments."; - - public const string MustNotBeDisposed = "{0} must not be disposed."; - - public const string MustBeLessThan = "{0} must be less than {1}."; - - public const string MustNotBeNullOrEmpty = "{0} must not be null or empty."; - - public const string MustNotBeNullOrWhitespace = "{0} must not be null or whitespace."; - - public const string MustBeGreaterThanZero = "{0} must be greater than zero."; - - public const string IsZeroWarning = "{0} is zero, which may be valid for some {1} but could indicate an issue."; - - public const string IsSetToNoneWarning = "{0} is set to None, which may be valid but could indicate an issue."; - - public const string MustBeWithinBounds = "{0} must be greater than zero and within the bounds of {1}."; - - public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; - - public const string MustBeGreaterThanOrEqualTo = "{0} must be greater than or equal to {1}."; - - public const string MustBeEqualTo = "{0} must be equal to {1}."; - - public const string MustBeBetween = "{0} must be between {1} and {2}."; - - public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; - - public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; - - public const string MustBeOneOf = "{0} must be one of: {1}."; - - public const string MustHaveFlag = "{0} must have the flag '{1}' set."; - - public const string MustHaveUsage = "{0} must have usage flag '{1}'."; - - public const string MustHaveSameValue = "{0} must match. First value: '{1}', second value: '{2}'."; - - public const string MustBeWithinResourceBounds = "{0} must be within the bounds of {1}."; - - public const string MustNotBeDepthStencilFormat = "{0} must not use a depth/stencil format."; - - public const string MustBeDepthStencilFormat = "{0} must use a depth/stencil format."; - - public const string MustBeSingleSampled = "{0} must use SampleCount.Count1."; - - public const string MustBeMultisampled = "{0} must use a sample count greater than SampleCount.Count1."; - - public const string MustBeFinite = "{0} must be finite."; - - public const string MustHaveCurrentPipeline = "{0} requires a pipeline to be set."; - - public const string MustHaveCurrentPipelineType = "{0} requires current pipeline type {1}, but current pipeline is {2}."; - - public const string MustNotBeTimestampQuery = "{0} cannot be used with QueryType.Timestamp."; - - public const string MustBeTimestampQuery = "{0} requires QueryType.Timestamp."; - - public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; - - public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; - - public const string HasInvalidConstantsLayout = "{0} is not a valid constants layout. It must be marked with [Constants], each field may carry at most one binding attribute, and any field marked with [Sampled], [Storage], or [Sampler] must be of type ResourceHandle."; - - public const string MustHaveNonZeroConstantsSize = "{0} reports a zero size for {1}; the constants layout has no payload for the current backend."; + #endregion } diff --git a/sources/Zenith.NET/ValidationMessages.cs b/sources/Zenith.NET/ValidationMessages.cs new file mode 100644 index 00000000..b18f9d4f --- /dev/null +++ b/sources/Zenith.NET/ValidationMessages.cs @@ -0,0 +1,84 @@ +namespace Zenith.NET; + +internal static class ValidationMessages +{ + public const string MustNotBeNull = "{0} must not be null."; + + public const string MustNotBeZero = "{0} must not be zero."; + + public const string MustHaveExactlyNHandles = "{0} must have exactly {1} handles for {2}."; + + public const string MustBeValidHandle = "{0} must be a valid handle for {1}."; + + public const string MustBeValidHandles = "{0} must be valid handles for {1}."; + + public const string HasUnsupportedSurfaceType = "{0} has unsupported SurfaceType '{1}'."; + + public const string HasInvalidValue = "{0} has an invalid value '{1}'."; + + public const string HasNoAttachments = "{0} has no attachments."; + + public const string MustNotBeDisposed = "{0} must not be disposed."; + + public const string MustBeLessThan = "{0} must be less than {1}."; + + public const string MustNotBeNullOrEmpty = "{0} must not be null or empty."; + + public const string MustNotBeNullOrWhitespace = "{0} must not be null or whitespace."; + + public const string MustBeGreaterThanZero = "{0} must be greater than zero."; + + public const string IsZeroWarning = "{0} is zero, which may be valid for some {1} but could indicate an issue."; + + public const string IsSetToNoneWarning = "{0} is set to None, which may be valid but could indicate an issue."; + + public const string MustBeWithinBounds = "{0} must be greater than zero and within the bounds of {1}."; + + public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; + + public const string MustBeGreaterThanOrEqualTo = "{0} must be greater than or equal to {1}."; + + public const string MustBeEqualTo = "{0} must be equal to {1}."; + + public const string MustBeBetween = "{0} must be between {1} and {2}."; + + public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; + + public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; + + public const string MustBeOneOf = "{0} must be one of: {1}."; + + public const string MustHaveFlag = "{0} must have the flag '{1}' set."; + + public const string MustHaveUsage = "{0} must have usage flag '{1}'."; + + public const string MustHaveSameValue = "{0} must match. First value: '{1}', second value: '{2}'."; + + public const string MustBeWithinResourceBounds = "{0} must be within the bounds of {1}."; + + public const string MustNotBeDepthStencilFormat = "{0} must not use a depth/stencil format."; + + public const string MustBeDepthStencilFormat = "{0} must use a depth/stencil format."; + + public const string MustBeSingleSampled = "{0} must use SampleCount.Count1."; + + public const string MustBeMultisampled = "{0} must use a sample count greater than SampleCount.Count1."; + + public const string MustBeFinite = "{0} must be finite."; + + public const string MustHaveCurrentPipeline = "{0} requires a pipeline to be set."; + + public const string MustHaveCurrentPipelineType = "{0} requires current pipeline type {1}, but current pipeline is {2}."; + + public const string MustNotBeTimestampQuery = "{0} cannot be used with QueryType.Timestamp."; + + public const string MustBeTimestampQuery = "{0} requires QueryType.Timestamp."; + + public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; + + public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; + + public const string MustHaveNonZeroConstantsSize = "{0} reports a zero size for {1}; the constants layout has no payload for the current backend."; + + public const string MustBeCpuAccessible = "{0} must use BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly to be mappable."; +} From 8828798ccd636bd34c160bdaa7c5cc99647fb319 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 20:17:34 +0800 Subject: [PATCH 072/466] Consolidate validation logic into ValidationLayer.cs Moved all descriptor and command validation methods from ValidationLayer.Commands.cs and ValidationLayer.Descriptors.cs into ValidationLayer.cs. Removed the partial class declaration and organized methods into regions for clarity. No functional changes; this is a refactor for improved code organization and maintainability. --- .../Zenith.NET/ValidationLayer.Commands.cs | 527 ------------ .../Zenith.NET/ValidationLayer.Descriptors.cs | 282 ------ sources/Zenith.NET/ValidationLayer.cs | 805 +++++++++++++++++- 3 files changed, 804 insertions(+), 810 deletions(-) delete mode 100644 sources/Zenith.NET/ValidationLayer.Commands.cs delete mode 100644 sources/Zenith.NET/ValidationLayer.Descriptors.cs diff --git a/sources/Zenith.NET/ValidationLayer.Commands.cs b/sources/Zenith.NET/ValidationLayer.Commands.cs deleted file mode 100644 index 5205136f..00000000 --- a/sources/Zenith.NET/ValidationLayer.Commands.cs +++ /dev/null @@ -1,527 +0,0 @@ -using System.Numerics; - -namespace Zenith.NET; - -partial class ValidationLayer -{ - #region Resources - - internal bool ValidateResource(string name, T? resource) where T : GraphicsResource - { - return CheckResource(name, resource); - } - - internal bool ValidateMap(Buffer buffer) - { - if (!CheckResource("Map.buffer", buffer)) - { - return false; - } - - if (buffer.Desc.Access is not (BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly)) - { - ReportError(string.Format(ValidationMessages.MustBeCpuAccessible, "Map.buffer")); - - return false; - } - - return true; - } - - #endregion - - #region SwapChain - - internal bool ValidateResize(uint width, uint height) - { - bool isValid = CheckGreaterThanZero("Resize.width", width); - isValid &= CheckGreaterThanZero("Resize.height", height); - - return isValid; - } - - internal void ValidateRefresh(Surface surface) - { - CheckSurface("Refresh.surface", surface); - } - - #endregion - - #region Transitions - - internal bool ValidateTransition(Texture texture, TextureSubresource subresource, TextureState state) - { - bool isValid = CheckResource("Transition.texture", texture); - - if (isValid) - { - isValid &= CheckTextureSubresource("Transition.subresource", texture, subresource); - } - - isValid &= CheckEnum("Transition.state", state); - - return isValid; - } - - #endregion - - #region Transfer - - internal bool ValidateUpload(Buffer buffer, uint offsetInBytes, BufferData data) - { - bool isValid = CheckResource("Upload.buffer", buffer); - isValid &= CheckBufferData("Upload.data", data); - - if (isValid) - { - isValid &= CheckBufferUsage("Upload.buffer", buffer, BufferUsages.CopyDst); - isValid &= CheckBufferRange("Upload.buffer", buffer, offsetInBytes, data.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateDownload(Buffer buffer, uint offsetInBytes, BufferData data) - { - bool isValid = CheckResource("Download.buffer", buffer); - isValid &= CheckBufferData("Download.data", data); - - if (isValid) - { - isValid &= CheckBufferUsage("Download.buffer", buffer, BufferUsages.CopySrc); - isValid &= CheckBufferRange("Download.buffer", buffer, offsetInBytes, data.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateUpload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) - { - bool isValid = CheckResource("Upload.texture", texture); - isValid &= CheckTextureData("Upload.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); - - if (isValid) - { - isValid &= CheckTextureUsage("Upload.texture", texture, TextureUsages.CopyDst); - isValid &= CheckTextureRange("Upload.texture", texture, subresource, offset, extent); - } - - return isValid; - } - - internal bool ValidateDownload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) - { - bool isValid = CheckResource("Download.texture", texture); - isValid &= CheckTextureData("Download.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); - - if (isValid) - { - isValid &= CheckTextureUsage("Download.texture", texture, TextureUsages.CopySrc); - isValid &= CheckTextureRange("Download.texture", texture, subresource, offset, extent); - } - - return isValid; - } - - internal bool ValidateCopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) - { - bool hasSrc = CheckResource("CopyBuffer.src", src); - bool hasDst = CheckResource("CopyBuffer.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckBufferUsage("CopyBuffer.src", src, BufferUsages.CopySrc); - isValid &= CheckBufferRange("CopyBuffer.src", src, srcOffsetInBytes, sizeInBytes); - } - - if (hasDst) - { - isValid &= CheckBufferUsage("CopyBuffer.dst", dst, BufferUsages.CopyDst); - isValid &= CheckBufferRange("CopyBuffer.dst", dst, dstOffsetInBytes, sizeInBytes); - } - - return isValid; - } - - internal bool ValidateCopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) - { - bool hasSrc = CheckResource("CopyBufferToTexture.src", src); - bool hasDst = CheckResource("CopyBufferToTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckBufferUsage("CopyBufferToTexture.src", src, BufferUsages.CopySrc); - isValid &= CheckTextureDataLayout("CopyBufferToTexture.srcLayout", srcLayout, hasDst ? dst.Desc.Format : PixelFormat.Unknown, dstExtent); - isValid &= CheckBufferRange("CopyBufferToTexture.src", src, srcOffsetInBytes, srcLayout.SizeInBytes); - } - - if (hasDst) - { - isValid &= CheckTextureUsage("CopyBufferToTexture.dst", dst, TextureUsages.CopyDst); - isValid &= CheckTextureRange("CopyBufferToTexture.dst", dst, dstSubresource, dstOffset, dstExtent); - } - - return isValid; - } - - internal bool ValidateCopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) - { - bool hasSrc = CheckResource("CopyTexture.src", src); - bool hasDst = CheckResource("CopyTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureUsage("CopyTexture.src", src, TextureUsages.CopySrc); - isValid &= CheckTextureRange("CopyTexture.src", src, srcSubresource, srcOffset, extent); - } - - if (hasDst) - { - isValid &= CheckTextureUsage("CopyTexture.dst", dst, TextureUsages.CopyDst); - isValid &= CheckTextureRange("CopyTexture.dst", dst, dstSubresource, dstOffset, extent); - } - - if (hasSrc && hasDst) - { - isValid &= CheckSameValue("CopyTexture.Format", src.Desc.Format, dst.Desc.Format); - isValid &= CheckSameValue("CopyTexture.SampleCount", src.Desc.SampleCount, dst.Desc.SampleCount); - } - - return isValid; - } - - internal bool ValidateCopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) - { - bool hasSrc = CheckResource("CopyTextureToBuffer.src", src); - bool hasDst = CheckResource("CopyTextureToBuffer.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureUsage("CopyTextureToBuffer.src", src, TextureUsages.CopySrc); - isValid &= CheckTextureRange("CopyTextureToBuffer.src", src, srcSubresource, srcOffset, srcExtent); - isValid &= CheckTextureDataLayout("CopyTextureToBuffer.dstLayout", dstLayout, src.Desc.Format, srcExtent); - } - - if (hasDst) - { - isValid &= CheckBufferUsage("CopyTextureToBuffer.dst", dst, BufferUsages.CopyDst); - isValid &= CheckBufferRange("CopyTextureToBuffer.dst", dst, dstOffsetInBytes, dstLayout.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) - { - bool hasSrc = CheckResource("ResolveTexture.src", src); - bool hasDst = CheckResource("ResolveTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureSubresource("ResolveTexture.srcSubresource", src, srcSubresource); - - if (src.Desc.SampleCount is SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeMultisampled, "ResolveTexture.src")); - isValid = false; - } - } - - if (hasDst) - { - isValid &= CheckTextureSubresource("ResolveTexture.dstSubresource", dst, dstSubresource); - - if (dst.Desc.SampleCount is not SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeSingleSampled, "ResolveTexture.dst")); - isValid = false; - } - } - - if (hasSrc && hasDst) - { - isValid &= CheckSameValue("ResolveTexture.Format", src.Desc.Format, dst.Desc.Format); - isValid &= CheckSameMipExtent("ResolveTexture", src, srcSubresource, dst, dstSubresource); - } - - return isValid; - } - - #endregion - - #region Render Pass / Viewport - - internal bool ValidateRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) - { - bool isValid = true; - bool hasExtent = false; - uint renderWidth = 0; - uint renderHeight = 0; - - if (colorAttachments.Length > ValidationConstants.MaxColorAttachments) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "BeginRenderPass.colorAttachments.Length", ValidationConstants.MaxColorAttachments)); - isValid = false; - } - - if (colorAttachments.Length is 0 && depthStencilAttachment is null) - { - ReportError(string.Format(ValidationMessages.HasNoAttachments, "BeginRenderPass")); - isValid = false; - } - - for (int index = 0; index < colorAttachments.Length; index++) - { - isValid &= CheckColorAttachment($"BeginRenderPass.colorAttachments[{index}]", colorAttachments[index], ref hasExtent, ref renderWidth, ref renderHeight); - } - - if (depthStencilAttachment is { } attachment) - { - isValid &= CheckDepthStencilAttachment("BeginRenderPass.depthStencilAttachment", attachment, ref hasExtent, ref renderWidth, ref renderHeight); - } - - return isValid; - } - - internal bool ValidateScissors(ReadOnlySpan scissors) - { - bool isValid = true; - - if (scissors.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetScissors.scissors")); - return false; - } - - for (int index = 0; index < scissors.Length; index++) - { - isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Width", scissors[index].Width); - isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Height", scissors[index].Height); - } - - return isValid; - } - - internal bool ValidateViewports(ReadOnlySpan viewports) - { - bool isValid = true; - - if (viewports.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetViewports.viewports")); - return false; - } - - for (int index = 0; index < viewports.Length; index++) - { - Viewport viewport = viewports[index]; - - isValid &= CheckFinite($"SetViewports.viewports[{index}].Width", viewport.Width); - isValid &= CheckFinite($"SetViewports.viewports[{index}].Height", viewport.Height); - isValid &= CheckFinite($"SetViewports.viewports[{index}].MinDepth", viewport.MinDepth); - isValid &= CheckFinite($"SetViewports.viewports[{index}].MaxDepth", viewport.MaxDepth); - - if (viewport.Width <= 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Width")); - isValid = false; - } - - if (viewport.Height <= 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Height")); - isValid = false; - } - - if (viewport.MinDepth > viewport.MaxDepth) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"SetViewports.viewports[{index}].MinDepth", $"SetViewports.viewports[{index}].MaxDepth")); - isValid = false; - } - } - - return isValid; - } - - #endregion - - #region Pipeline State - - internal bool ValidateCurrentPipeline(string commandName) - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); - - return false; - } - - internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) where TPipeline : Pipeline - { - if (currentPipeline is null) - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); - } - else - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipelineType, commandName, typeof(TPipeline).Name, currentPipeline.GetType().Name)); - } - - return false; - } - - internal bool ValidateSetStencilReference(Pipeline? currentPipeline) - { - if (currentPipeline is null) - { - return ValidateCurrentPipeline("SetStencilReference"); - } - - if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) - { - return ValidateCurrentPipeline("SetStencilReference", currentPipeline); - } - - return true; - } - - internal bool ValidateSetBlendConstant(Pipeline? currentPipeline, Vector4 blendConstant) - { - bool isValid = true; - - if (currentPipeline is null) - { - isValid = ValidateCurrentPipeline("SetBlendConstant"); - } - else if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) - { - isValid = ValidateCurrentPipeline("SetBlendConstant", currentPipeline); - } - - isValid &= CheckFinite("SetBlendConstant.blendConstant.X", blendConstant.X); - isValid &= CheckFinite("SetBlendConstant.blendConstant.Y", blendConstant.Y); - isValid &= CheckFinite("SetBlendConstant.blendConstant.Z", blendConstant.Z); - isValid &= CheckFinite("SetBlendConstant.blendConstant.W", blendConstant.W); - - return isValid; - } - - internal bool ValidateSetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot, GraphicsPipeline pipeline) - { - bool isValid = CheckResource("SetVertexBuffer.buffer", buffer); - - if (isValid) - { - isValid &= CheckBufferUsage("SetVertexBuffer.buffer", buffer, BufferUsages.Vertex); - isValid &= CheckBufferOffset("SetVertexBuffer.buffer", buffer, offsetInBytes); - } - - if (pipeline.Desc.InputLayouts is not null && slot >= pipeline.Desc.InputLayouts.Length) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "SetVertexBuffer.slot", "GraphicsPipelineDesc.InputLayouts.Length")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) - { - bool isValid = CheckResource("SetIndexBuffer.buffer", buffer); - isValid &= CheckEnum("SetIndexBuffer.indexFormat", indexFormat); - - if (isValid) - { - isValid &= CheckBufferUsage("SetIndexBuffer.buffer", buffer, BufferUsages.Index); - isValid &= CheckBufferOffset("SetIndexBuffer.buffer", buffer, offsetInBytes); - } - - return isValid; - } - - internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout - { - uint sizeInBytes = Context.GraphicsApi switch - { - GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, - GraphicsApi.Metal => T.MetalSizeInBytes, - GraphicsApi.Vulkan => T.VulkanSizeInBytes, - _ => 0 - }; - - if (sizeInBytes is 0) - { - ReportError(string.Format(ValidationMessages.MustHaveNonZeroConstantsSize, typeof(T).Name, Context.GraphicsApi)); - - return false; - } - - return true; - } - - #endregion - - #region Indirect / Queries / Debug - - internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) - { - bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); - - if (isValid) - { - isValid &= CheckBufferUsage($"{commandName}.indirectBuffer", indirectBuffer, BufferUsages.Indirect); - isValid &= CheckBufferRange($"{commandName}.indirectBuffer", indirectBuffer, offsetInBytes, (ulong)argsSizeInBytes * drawOrDispatchCount, allowZeroSize: true); - } - - return isValid; - } - - internal bool ValidateBeginQuery(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("BeginQuery", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "BeginQuery")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateEndQuery(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("EndQuery", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "EndQuery")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateWriteTimestamp(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("WriteTimestamp", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is not QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustBeTimestampQuery, "WriteTimestamp")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateDebugLabel(string commandName, string? label) - { - return CheckStringNotWhitespace($"{commandName}.label", label); - } - - #endregion -} diff --git a/sources/Zenith.NET/ValidationLayer.Descriptors.cs b/sources/Zenith.NET/ValidationLayer.Descriptors.cs deleted file mode 100644 index 6a6cc3ac..00000000 --- a/sources/Zenith.NET/ValidationLayer.Descriptors.cs +++ /dev/null @@ -1,282 +0,0 @@ -namespace Zenith.NET; - -partial class ValidationLayer -{ - internal void ValidateDesc(SwapChainDesc desc) - { - CheckSurface("SwapChainDesc.Surface", desc.Surface); - - CheckEnum("SwapChainDesc.ColorFormat", desc.ColorFormat); - - if (desc.DepthStencilFormat is { } format) - { - CheckEnum("SwapChainDesc.DepthStencilFormat", format); - } - } - - internal void ValidateDesc(BufferDesc desc) - { - CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); - - if (desc.StrideInBytes is 0) - { - ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); - } - - CheckEnum("BufferDesc.Access", desc.Access); - CheckFlags("BufferDesc.Usages", desc.Usages); - - if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) - { - const BufferUsages GpuOnlyUsages = BufferUsages.StorageReadWrite - | BufferUsages.Indirect - | BufferUsages.AccelerationStructure; - - BufferUsages forbiddenUsages = desc.Usages & GpuOnlyUsages; - - if (forbiddenUsages is not BufferUsages.None) - { - ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbiddenUsages, desc.Access)); - } - } - } - - internal void ValidateDesc(BufferViewDesc desc) - { - if (!CheckResource("BufferViewDesc.Buffer", desc.Buffer)) - { - return; - } - - CheckBufferRange("BufferViewDesc", desc.Buffer, desc.OffsetInBytes, desc.SizeInBytes); - - if (desc.StrideInBytes is 0) - { - ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "structured buffer views")); - } - } - - internal void ValidateDesc(TextureDesc desc) - { - CheckEnum("TextureDesc.Type", desc.Type); - CheckEnum("TextureDesc.Format", desc.Format); - - if (desc.Width is 0 || desc.Height is 0 || desc.Depth is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); - } - - CheckGreaterThanZero("TextureDesc.MipLevels", desc.MipLevels); - CheckGreaterThanZero("TextureDesc.ArrayLayers", desc.ArrayLayers); - - if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) - { - ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); - } - - if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) - { - ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); - } - - CheckEnum("TextureDesc.SampleCount", desc.SampleCount); - CheckFlags("TextureDesc.Usages", desc.Usages); - - if (desc.Usages is TextureUsages.None) - { - ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Usages")); - } - } - - internal void ValidateDesc(TextureViewDesc desc) - { - if (!CheckResource("TextureViewDesc.Texture", desc.Texture)) - { - return; - } - - CheckEnum("TextureViewDesc.Type", desc.Type); - CheckEnum("TextureViewDesc.Format", desc.Format); - - if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); - } - - if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); - } - - if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); - } - - if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); - } - - if (desc.Type is TextureType.TextureCube) - { - if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) - { - ReportError(string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); - } - } - - if (desc.Type is TextureType.TextureCubeArray) - { - if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); - } - } - } - - internal void ValidateDesc(SamplerDesc desc) - { - CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); - CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); - CheckEnum("SamplerDesc.MipmapFilter", desc.MipmapFilter); - CheckEnum("SamplerDesc.AddressU", desc.AddressU); - CheckEnum("SamplerDesc.AddressV", desc.AddressV); - CheckEnum("SamplerDesc.AddressW", desc.AddressW); - CheckEnum("SamplerDesc.CompareOp", desc.CompareOp); - - if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); - } - - if (desc.MinLod > desc.MaxLod) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); - } - - CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); - } - - internal void ValidateDesc(ShaderDesc desc) - { - CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); - CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); - CheckEnum("ShaderDesc.Stage", desc.Stage); - } - - internal void ValidateDesc(GraphicsPipelineDesc desc) - { - CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); - - CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); - CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); - - if (desc.InputLayouts is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); - } - else - { - for (int index = 0; index < desc.InputLayouts.Length; index++) - { - CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{index}]", desc.InputLayouts[index]); - } - } - - CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); - CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - } - - internal void ValidateDesc(ComputePipelineDesc desc) - { - CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); - } - - internal void ValidateDesc(MeshShadingPipelineDesc desc) - { - CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); - - CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); - CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); - - if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) - { - ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); - } - - CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - - if (desc.TaskShader is not null) - { - CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); - } - } - - internal void ValidateDesc(QueryHeapDesc desc) - { - CheckEnum("QueryHeapDesc.Type", desc.Type); - CheckGreaterThanZero("QueryHeapDesc.Count", desc.Count); - } - - internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) - { - if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) - { - for (int index = 0; index < desc.Geometries.Length; index++) - { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{index}]", desc.Geometries[index]); - } - } - - CheckFlags("BottomLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) - { - if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) - { - for (int index = 0; index < desc.Instances.Length; index++) - { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{index}]", desc.Instances[index]); - } - } - - CheckFlags("TopLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) - { - if (!oldDesc.BuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) - { - ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.BuildFlags", AccelerationStructureBuildFlags.AllowUpdate)); - } - - ValidateDesc(newDesc); - - if (newDesc.Instances is null) - { - return; - } - - if (oldDesc.Instances.Length != newDesc.Instances.Length) - { - ReportError(ValidationMessages.InstanceCountMustRemainSame); - } - } -} diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 13a9489f..59cf1673 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -2,7 +2,7 @@ namespace Zenith.NET; -public abstract partial class ValidationLayer(GraphicsContext context) : GraphicsResource(context) +public abstract class ValidationLayer(GraphicsContext context) : GraphicsResource(context) { private static readonly Dictionary ExpectedSurfaceHandleCount = new() { @@ -29,6 +29,809 @@ private void ReportWarning(string message) Report(MessageSource.Framework, MessageSeverity.Warning, message); } + #region Descriptor Validation + + internal void ValidateDesc(SwapChainDesc desc) + { + CheckSurface("SwapChainDesc.Surface", desc.Surface); + + CheckEnum("SwapChainDesc.ColorFormat", desc.ColorFormat); + + if (desc.DepthStencilFormat is { } format) + { + CheckEnum("SwapChainDesc.DepthStencilFormat", format); + } + } + + internal void ValidateDesc(BufferDesc desc) + { + CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); + + if (desc.StrideInBytes is 0) + { + ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); + } + + CheckEnum("BufferDesc.Access", desc.Access); + CheckFlags("BufferDesc.Usages", desc.Usages); + + if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) + { + const BufferUsages GpuOnlyUsages = BufferUsages.StorageReadWrite + | BufferUsages.Indirect + | BufferUsages.AccelerationStructure; + + BufferUsages forbiddenUsages = desc.Usages & GpuOnlyUsages; + + if (forbiddenUsages is not BufferUsages.None) + { + ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbiddenUsages, desc.Access)); + } + } + } + + internal void ValidateDesc(BufferViewDesc desc) + { + if (!CheckResource("BufferViewDesc.Buffer", desc.Buffer)) + { + return; + } + + CheckBufferRange("BufferViewDesc", desc.Buffer, desc.OffsetInBytes, desc.SizeInBytes); + + if (desc.StrideInBytes is 0) + { + ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "structured buffer views")); + } + } + + internal void ValidateDesc(TextureDesc desc) + { + CheckEnum("TextureDesc.Type", desc.Type); + CheckEnum("TextureDesc.Format", desc.Format); + + if (desc.Width is 0 || desc.Height is 0 || desc.Depth is 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); + } + + CheckGreaterThanZero("TextureDesc.MipLevels", desc.MipLevels); + CheckGreaterThanZero("TextureDesc.ArrayLayers", desc.ArrayLayers); + + if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) + { + ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); + } + + if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) + { + ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); + } + + CheckEnum("TextureDesc.SampleCount", desc.SampleCount); + CheckFlags("TextureDesc.Usages", desc.Usages); + + if (desc.Usages is TextureUsages.None) + { + ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Usages")); + } + } + + internal void ValidateDesc(TextureViewDesc desc) + { + if (!CheckResource("TextureViewDesc.Texture", desc.Texture)) + { + return; + } + + CheckEnum("TextureViewDesc.Type", desc.Type); + CheckEnum("TextureViewDesc.Format", desc.Format); + + if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); + } + + if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) + { + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); + } + + if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); + } + + if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) + { + ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); + } + + if (desc.Type is TextureType.TextureCube) + { + if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) + { + ReportError(string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); + } + } + + if (desc.Type is TextureType.TextureCubeArray) + { + if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); + } + + if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) + { + ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); + } + } + } + + internal void ValidateDesc(SamplerDesc desc) + { + CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); + CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); + CheckEnum("SamplerDesc.MipmapFilter", desc.MipmapFilter); + CheckEnum("SamplerDesc.AddressU", desc.AddressU); + CheckEnum("SamplerDesc.AddressV", desc.AddressV); + CheckEnum("SamplerDesc.AddressW", desc.AddressW); + CheckEnum("SamplerDesc.CompareOp", desc.CompareOp); + + if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); + } + + if (desc.MinLod > desc.MaxLod) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); + } + + CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); + } + + internal void ValidateDesc(ShaderDesc desc) + { + CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); + CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); + CheckEnum("ShaderDesc.Stage", desc.Stage); + } + + internal void ValidateDesc(GraphicsPipelineDesc desc) + { + CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); + + CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); + CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); + + if (desc.InputLayouts is null) + { + ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); + } + else + { + for (int index = 0; index < desc.InputLayouts.Length; index++) + { + CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{index}]", desc.InputLayouts[index]); + } + } + + CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); + CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); + } + + internal void ValidateDesc(ComputePipelineDesc desc) + { + CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); + } + + internal void ValidateDesc(MeshShadingPipelineDesc desc) + { + CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); + + CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); + CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); + + if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) + { + ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); + } + + CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); + + if (desc.TaskShader is not null) + { + CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); + } + } + + internal void ValidateDesc(QueryHeapDesc desc) + { + CheckEnum("QueryHeapDesc.Type", desc.Type); + CheckGreaterThanZero("QueryHeapDesc.Count", desc.Count); + } + + internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) + { + if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) + { + for (int index = 0; index < desc.Geometries.Length; index++) + { + CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{index}]", desc.Geometries[index]); + } + } + + CheckFlags("BottomLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); + } + + internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) + { + if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) + { + for (int index = 0; index < desc.Instances.Length; index++) + { + CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{index}]", desc.Instances[index]); + } + } + + CheckFlags("TopLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); + } + + internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) + { + if (!oldDesc.BuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + { + ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.BuildFlags", AccelerationStructureBuildFlags.AllowUpdate)); + } + + ValidateDesc(newDesc); + + if (newDesc.Instances is null) + { + return; + } + + if (oldDesc.Instances.Length != newDesc.Instances.Length) + { + ReportError(ValidationMessages.InstanceCountMustRemainSame); + } + } + + #endregion + + #region Resource Validation + + internal bool ValidateResource(string name, T? resource) where T : GraphicsResource + { + return CheckResource(name, resource); + } + + internal bool ValidateMap(Buffer buffer) + { + if (!CheckResource("Map.buffer", buffer)) + { + return false; + } + + if (buffer.Desc.Access is not (BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly)) + { + ReportError(string.Format(ValidationMessages.MustBeCpuAccessible, "Map.buffer")); + + return false; + } + + return true; + } + + #endregion + + #region SwapChain Validation + + internal bool ValidateResize(uint width, uint height) + { + bool isValid = CheckGreaterThanZero("Resize.width", width); + isValid &= CheckGreaterThanZero("Resize.height", height); + + return isValid; + } + + internal void ValidateRefresh(Surface surface) + { + CheckSurface("Refresh.surface", surface); + } + + #endregion + + #region Transition Validation + + internal bool ValidateTransition(Texture texture, TextureSubresource subresource, TextureState state) + { + bool isValid = CheckResource("Transition.texture", texture); + + if (isValid) + { + isValid &= CheckTextureSubresource("Transition.subresource", texture, subresource); + } + + isValid &= CheckEnum("Transition.state", state); + + return isValid; + } + + #endregion + + #region Transfer Validation + + internal bool ValidateUpload(Buffer buffer, uint offsetInBytes, BufferData data) + { + bool isValid = CheckResource("Upload.buffer", buffer); + isValid &= CheckBufferData("Upload.data", data); + + if (isValid) + { + isValid &= CheckBufferUsage("Upload.buffer", buffer, BufferUsages.CopyDst); + isValid &= CheckBufferRange("Upload.buffer", buffer, offsetInBytes, data.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateDownload(Buffer buffer, uint offsetInBytes, BufferData data) + { + bool isValid = CheckResource("Download.buffer", buffer); + isValid &= CheckBufferData("Download.data", data); + + if (isValid) + { + isValid &= CheckBufferUsage("Download.buffer", buffer, BufferUsages.CopySrc); + isValid &= CheckBufferRange("Download.buffer", buffer, offsetInBytes, data.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateUpload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + bool isValid = CheckResource("Upload.texture", texture); + isValid &= CheckTextureData("Upload.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); + + if (isValid) + { + isValid &= CheckTextureUsage("Upload.texture", texture, TextureUsages.CopyDst); + isValid &= CheckTextureRange("Upload.texture", texture, subresource, offset, extent); + } + + return isValid; + } + + internal bool ValidateDownload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + bool isValid = CheckResource("Download.texture", texture); + isValid &= CheckTextureData("Download.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); + + if (isValid) + { + isValid &= CheckTextureUsage("Download.texture", texture, TextureUsages.CopySrc); + isValid &= CheckTextureRange("Download.texture", texture, subresource, offset, extent); + } + + return isValid; + } + + internal bool ValidateCopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) + { + bool hasSrc = CheckResource("CopyBuffer.src", src); + bool hasDst = CheckResource("CopyBuffer.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckBufferUsage("CopyBuffer.src", src, BufferUsages.CopySrc); + isValid &= CheckBufferRange("CopyBuffer.src", src, srcOffsetInBytes, sizeInBytes); + } + + if (hasDst) + { + isValid &= CheckBufferUsage("CopyBuffer.dst", dst, BufferUsages.CopyDst); + isValid &= CheckBufferRange("CopyBuffer.dst", dst, dstOffsetInBytes, sizeInBytes); + } + + return isValid; + } + + internal bool ValidateCopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + bool hasSrc = CheckResource("CopyBufferToTexture.src", src); + bool hasDst = CheckResource("CopyBufferToTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckBufferUsage("CopyBufferToTexture.src", src, BufferUsages.CopySrc); + isValid &= CheckTextureDataLayout("CopyBufferToTexture.srcLayout", srcLayout, hasDst ? dst.Desc.Format : PixelFormat.Unknown, dstExtent); + isValid &= CheckBufferRange("CopyBufferToTexture.src", src, srcOffsetInBytes, srcLayout.SizeInBytes); + } + + if (hasDst) + { + isValid &= CheckTextureUsage("CopyBufferToTexture.dst", dst, TextureUsages.CopyDst); + isValid &= CheckTextureRange("CopyBufferToTexture.dst", dst, dstSubresource, dstOffset, dstExtent); + } + + return isValid; + } + + internal bool ValidateCopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) + { + bool hasSrc = CheckResource("CopyTexture.src", src); + bool hasDst = CheckResource("CopyTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureUsage("CopyTexture.src", src, TextureUsages.CopySrc); + isValid &= CheckTextureRange("CopyTexture.src", src, srcSubresource, srcOffset, extent); + } + + if (hasDst) + { + isValid &= CheckTextureUsage("CopyTexture.dst", dst, TextureUsages.CopyDst); + isValid &= CheckTextureRange("CopyTexture.dst", dst, dstSubresource, dstOffset, extent); + } + + if (hasSrc && hasDst) + { + isValid &= CheckSameValue("CopyTexture.Format", src.Desc.Format, dst.Desc.Format); + isValid &= CheckSameValue("CopyTexture.SampleCount", src.Desc.SampleCount, dst.Desc.SampleCount); + } + + return isValid; + } + + internal bool ValidateCopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) + { + bool hasSrc = CheckResource("CopyTextureToBuffer.src", src); + bool hasDst = CheckResource("CopyTextureToBuffer.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureUsage("CopyTextureToBuffer.src", src, TextureUsages.CopySrc); + isValid &= CheckTextureRange("CopyTextureToBuffer.src", src, srcSubresource, srcOffset, srcExtent); + isValid &= CheckTextureDataLayout("CopyTextureToBuffer.dstLayout", dstLayout, src.Desc.Format, srcExtent); + } + + if (hasDst) + { + isValid &= CheckBufferUsage("CopyTextureToBuffer.dst", dst, BufferUsages.CopyDst); + isValid &= CheckBufferRange("CopyTextureToBuffer.dst", dst, dstOffsetInBytes, dstLayout.SizeInBytes); + } + + return isValid; + } + + internal bool ValidateResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + bool hasSrc = CheckResource("ResolveTexture.src", src); + bool hasDst = CheckResource("ResolveTexture.dst", dst); + bool isValid = hasSrc && hasDst; + + if (hasSrc) + { + isValid &= CheckTextureSubresource("ResolveTexture.srcSubresource", src, srcSubresource); + + if (src.Desc.SampleCount is SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeMultisampled, "ResolveTexture.src")); + isValid = false; + } + } + + if (hasDst) + { + isValid &= CheckTextureSubresource("ResolveTexture.dstSubresource", dst, dstSubresource); + + if (dst.Desc.SampleCount is not SampleCount.Count1) + { + ReportError(string.Format(ValidationMessages.MustBeSingleSampled, "ResolveTexture.dst")); + isValid = false; + } + } + + if (hasSrc && hasDst) + { + isValid &= CheckSameValue("ResolveTexture.Format", src.Desc.Format, dst.Desc.Format); + isValid &= CheckSameMipExtent("ResolveTexture", src, srcSubresource, dst, dstSubresource); + } + + return isValid; + } + + #endregion + + #region Render Pass / Viewport Validation + + internal bool ValidateRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + bool isValid = true; + bool hasExtent = false; + uint renderWidth = 0; + uint renderHeight = 0; + + if (colorAttachments.Length > ValidationConstants.MaxColorAttachments) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "BeginRenderPass.colorAttachments.Length", ValidationConstants.MaxColorAttachments)); + isValid = false; + } + + if (colorAttachments.Length is 0 && depthStencilAttachment is null) + { + ReportError(string.Format(ValidationMessages.HasNoAttachments, "BeginRenderPass")); + isValid = false; + } + + for (int index = 0; index < colorAttachments.Length; index++) + { + isValid &= CheckColorAttachment($"BeginRenderPass.colorAttachments[{index}]", colorAttachments[index], ref hasExtent, ref renderWidth, ref renderHeight); + } + + if (depthStencilAttachment is { } attachment) + { + isValid &= CheckDepthStencilAttachment("BeginRenderPass.depthStencilAttachment", attachment, ref hasExtent, ref renderWidth, ref renderHeight); + } + + return isValid; + } + + internal bool ValidateScissors(ReadOnlySpan scissors) + { + bool isValid = true; + + if (scissors.Length is 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetScissors.scissors")); + return false; + } + + for (int index = 0; index < scissors.Length; index++) + { + isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Width", scissors[index].Width); + isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Height", scissors[index].Height); + } + + return isValid; + } + + internal bool ValidateViewports(ReadOnlySpan viewports) + { + bool isValid = true; + + if (viewports.Length is 0) + { + ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetViewports.viewports")); + return false; + } + + for (int index = 0; index < viewports.Length; index++) + { + Viewport viewport = viewports[index]; + + isValid &= CheckFinite($"SetViewports.viewports[{index}].Width", viewport.Width); + isValid &= CheckFinite($"SetViewports.viewports[{index}].Height", viewport.Height); + isValid &= CheckFinite($"SetViewports.viewports[{index}].MinDepth", viewport.MinDepth); + isValid &= CheckFinite($"SetViewports.viewports[{index}].MaxDepth", viewport.MaxDepth); + + if (viewport.Width <= 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Width")); + isValid = false; + } + + if (viewport.Height <= 0) + { + ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Height")); + isValid = false; + } + + if (viewport.MinDepth > viewport.MaxDepth) + { + ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"SetViewports.viewports[{index}].MinDepth", $"SetViewports.viewports[{index}].MaxDepth")); + isValid = false; + } + } + + return isValid; + } + + #endregion + + #region Pipeline State Validation + + internal bool ValidateCurrentPipeline(string commandName) + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); + + return false; + } + + internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) where TPipeline : Pipeline + { + if (currentPipeline is null) + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); + } + else + { + ReportError(string.Format(ValidationMessages.MustHaveCurrentPipelineType, commandName, typeof(TPipeline).Name, currentPipeline.GetType().Name)); + } + + return false; + } + + internal bool ValidateSetStencilReference(Pipeline? currentPipeline) + { + if (currentPipeline is null) + { + return ValidateCurrentPipeline("SetStencilReference"); + } + + if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) + { + return ValidateCurrentPipeline("SetStencilReference", currentPipeline); + } + + return true; + } + + internal bool ValidateSetBlendConstant(Pipeline? currentPipeline, Vector4 blendConstant) + { + bool isValid = true; + + if (currentPipeline is null) + { + isValid = ValidateCurrentPipeline("SetBlendConstant"); + } + else if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) + { + isValid = ValidateCurrentPipeline("SetBlendConstant", currentPipeline); + } + + isValid &= CheckFinite("SetBlendConstant.blendConstant.X", blendConstant.X); + isValid &= CheckFinite("SetBlendConstant.blendConstant.Y", blendConstant.Y); + isValid &= CheckFinite("SetBlendConstant.blendConstant.Z", blendConstant.Z); + isValid &= CheckFinite("SetBlendConstant.blendConstant.W", blendConstant.W); + + return isValid; + } + + internal bool ValidateSetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot, GraphicsPipeline pipeline) + { + bool isValid = CheckResource("SetVertexBuffer.buffer", buffer); + + if (isValid) + { + isValid &= CheckBufferUsage("SetVertexBuffer.buffer", buffer, BufferUsages.Vertex); + isValid &= CheckBufferOffset("SetVertexBuffer.buffer", buffer, offsetInBytes); + } + + if (pipeline.Desc.InputLayouts is not null && slot >= pipeline.Desc.InputLayouts.Length) + { + ReportError(string.Format(ValidationMessages.MustBeLessThan, "SetVertexBuffer.slot", "GraphicsPipelineDesc.InputLayouts.Length")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) + { + bool isValid = CheckResource("SetIndexBuffer.buffer", buffer); + isValid &= CheckEnum("SetIndexBuffer.indexFormat", indexFormat); + + if (isValid) + { + isValid &= CheckBufferUsage("SetIndexBuffer.buffer", buffer, BufferUsages.Index); + isValid &= CheckBufferOffset("SetIndexBuffer.buffer", buffer, offsetInBytes); + } + + return isValid; + } + + internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout + { + uint sizeInBytes = Context.GraphicsApi switch + { + GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, + GraphicsApi.Metal => T.MetalSizeInBytes, + GraphicsApi.Vulkan => T.VulkanSizeInBytes, + _ => 0 + }; + + if (sizeInBytes is 0) + { + ReportError(string.Format(ValidationMessages.MustHaveNonZeroConstantsSize, typeof(T).Name, Context.GraphicsApi)); + + return false; + } + + return true; + } + + #endregion + + #region Indirect / Query / Debug Validation + + internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) + { + bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); + + if (isValid) + { + isValid &= CheckBufferUsage($"{commandName}.indirectBuffer", indirectBuffer, BufferUsages.Indirect); + isValid &= CheckBufferRange($"{commandName}.indirectBuffer", indirectBuffer, offsetInBytes, (ulong)argsSizeInBytes * drawOrDispatchCount, allowZeroSize: true); + } + + return isValid; + } + + internal bool ValidateBeginQuery(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("BeginQuery", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "BeginQuery")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateEndQuery(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("EndQuery", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "EndQuery")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateWriteTimestamp(QueryHeap queryHeap, uint index) + { + bool isValid = CheckQuery("WriteTimestamp", queryHeap, index); + + if (isValid && queryHeap.Desc.Type is not QueryType.Timestamp) + { + ReportError(string.Format(ValidationMessages.MustBeTimestampQuery, "WriteTimestamp")); + isValid = false; + } + + return isValid; + } + + internal bool ValidateDebugLabel(string commandName, string? label) + { + return CheckStringNotWhitespace($"{commandName}.label", label); + } + + #endregion + #region Primitive Checks private bool CheckResource(string name, T? resource) where T : GraphicsResource From a6a960d18a0ed7b82a54d51939f3bff6ac34ee60 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 20:23:50 +0800 Subject: [PATCH 073/466] Refactor validation logic and relocate constants/messages Refactor indirect validation to use specific methods for clarity and type safety. Simplify ValidateSetConstants signature. Move ValidationConstants and ValidationMessages into ValidationLayer.cs as file-scoped static classes and remove their original files. No functional changes to validation logic. --- sources/Zenith.NET/CommandBuffer.cs | 10 +- sources/Zenith.NET/ValidationConstants.cs | 24 ---- sources/Zenith.NET/ValidationLayer.cs | 130 +++++++++++++++++++++- sources/Zenith.NET/ValidationMessages.cs | 84 -------------- 4 files changed, 133 insertions(+), 115 deletions(-) delete mode 100644 sources/Zenith.NET/ValidationConstants.cs delete mode 100644 sources/Zenith.NET/ValidationMessages.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 1168fca6..ba235945 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -340,7 +340,7 @@ public void SetConstants(T data) where T : unmanaged, IConstantsLayout return; } - if (Context.ValidationLayer?.ValidateSetConstants(data) is false) + if (Context.ValidationLayer?.ValidateSetConstants() is false) { return; } @@ -365,7 +365,7 @@ public void DrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCou return; } - if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DrawIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawArgsSizeInBytes, drawCount) is false) + if (Context.ValidationLayer?.ValidateDrawIndirect(indirectBuffer, offsetInBytes, drawCount) is false) { return; } @@ -390,7 +390,7 @@ public void DrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint return; } - if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DrawIndexedIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawIndexedArgsSizeInBytes, drawCount) is false) + if (Context.ValidationLayer?.ValidateDrawIndexedIndirect(indirectBuffer, offsetInBytes, drawCount) is false) { return; } @@ -415,7 +415,7 @@ public void DispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) return; } - if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DispatchIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchArgsSizeInBytes, 1) is false) + if (Context.ValidationLayer?.ValidateDispatchIndirect(indirectBuffer, offsetInBytes) is false) { return; } @@ -440,7 +440,7 @@ public void DispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint return; } - if (Context.ValidationLayer?.ValidateIndirectBuffer(nameof(DispatchMeshIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchMeshArgsSizeInBytes, dispatchCount) is false) + if (Context.ValidationLayer?.ValidateDispatchMeshIndirect(indirectBuffer, offsetInBytes, dispatchCount) is false) { return; } diff --git a/sources/Zenith.NET/ValidationConstants.cs b/sources/Zenith.NET/ValidationConstants.cs deleted file mode 100644 index e18bda78..00000000 --- a/sources/Zenith.NET/ValidationConstants.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Zenith.NET; - -internal static class ValidationConstants -{ - public const int CubeMapFaceCount = 6; - - public const int MaxAnisotropy = 16; - - public const int MaxColorAttachments = 8; - - public const uint IndirectDrawArgsSizeInBytes = 16; - - public const uint IndirectDrawIndexedArgsSizeInBytes = 20; - - public const uint IndirectDispatchArgsSizeInBytes = 12; - - public const uint IndirectDispatchMeshArgsSizeInBytes = 12; - - public const int IndexSizeUInt16 = 2; - - public const int IndexSizeUInt32 = 4; - - public const int MaxRayTracingInstanceId = 16777215; -} diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 59cf1673..5c28c6a8 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -749,7 +749,7 @@ internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFor return isValid; } - internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLayout + internal bool ValidateSetConstants() where T : unmanaged, IConstantsLayout { uint sizeInBytes = Context.GraphicsApi switch { @@ -773,7 +773,27 @@ internal bool ValidateSetConstants(T data) where T : unmanaged, IConstantsLay #region Indirect / Query / Debug Validation - internal bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) + internal bool ValidateDrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + return ValidateIndirectBuffer(nameof(CommandBuffer.DrawIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawArgsSizeInBytes, drawCount); + } + + internal bool ValidateDrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + return ValidateIndirectBuffer(nameof(CommandBuffer.DrawIndexedIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawIndexedArgsSizeInBytes, drawCount); + } + + internal bool ValidateDispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) + { + return ValidateIndirectBuffer(nameof(CommandBuffer.DispatchIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchArgsSizeInBytes, 1); + } + + internal bool ValidateDispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) + { + return ValidateIndirectBuffer(nameof(CommandBuffer.DispatchMeshIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchMeshArgsSizeInBytes, dispatchCount); + } + + private bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) { bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); @@ -1575,3 +1595,109 @@ private void CheckRayTracingInstance(string name, RayTracingInstance instance) #endregion } + +file static class ValidationConstants +{ + public const int CubeMapFaceCount = 6; + + public const int MaxAnisotropy = 16; + + public const int MaxColorAttachments = 8; + + public const uint IndirectDrawArgsSizeInBytes = 16; + + public const uint IndirectDrawIndexedArgsSizeInBytes = 20; + + public const uint IndirectDispatchArgsSizeInBytes = 12; + + public const uint IndirectDispatchMeshArgsSizeInBytes = 12; + + public const int IndexSizeUInt16 = 2; + + public const int IndexSizeUInt32 = 4; + + public const int MaxRayTracingInstanceId = 16777215; +} + +file static class ValidationMessages +{ + public const string MustNotBeNull = "{0} must not be null."; + + public const string MustNotBeZero = "{0} must not be zero."; + + public const string MustHaveExactlyNHandles = "{0} must have exactly {1} handles for {2}."; + + public const string MustBeValidHandle = "{0} must be a valid handle for {1}."; + + public const string MustBeValidHandles = "{0} must be valid handles for {1}."; + + public const string HasUnsupportedSurfaceType = "{0} has unsupported SurfaceType '{1}'."; + + public const string HasInvalidValue = "{0} has an invalid value '{1}'."; + + public const string HasNoAttachments = "{0} has no attachments."; + + public const string MustNotBeDisposed = "{0} must not be disposed."; + + public const string MustBeLessThan = "{0} must be less than {1}."; + + public const string MustNotBeNullOrEmpty = "{0} must not be null or empty."; + + public const string MustNotBeNullOrWhitespace = "{0} must not be null or whitespace."; + + public const string MustBeGreaterThanZero = "{0} must be greater than zero."; + + public const string IsZeroWarning = "{0} is zero, which may be valid for some {1} but could indicate an issue."; + + public const string IsSetToNoneWarning = "{0} is set to None, which may be valid but could indicate an issue."; + + public const string MustBeWithinBounds = "{0} must be greater than zero and within the bounds of {1}."; + + public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; + + public const string MustBeGreaterThanOrEqualTo = "{0} must be greater than or equal to {1}."; + + public const string MustBeEqualTo = "{0} must be equal to {1}."; + + public const string MustBeBetween = "{0} must be between {1} and {2}."; + + public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; + + public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; + + public const string MustBeOneOf = "{0} must be one of: {1}."; + + public const string MustHaveFlag = "{0} must have the flag '{1}' set."; + + public const string MustHaveUsage = "{0} must have usage flag '{1}'."; + + public const string MustHaveSameValue = "{0} must match. First value: '{1}', second value: '{2}'."; + + public const string MustBeWithinResourceBounds = "{0} must be within the bounds of {1}."; + + public const string MustNotBeDepthStencilFormat = "{0} must not use a depth/stencil format."; + + public const string MustBeDepthStencilFormat = "{0} must use a depth/stencil format."; + + public const string MustBeSingleSampled = "{0} must use SampleCount.Count1."; + + public const string MustBeMultisampled = "{0} must use a sample count greater than SampleCount.Count1."; + + public const string MustBeFinite = "{0} must be finite."; + + public const string MustHaveCurrentPipeline = "{0} requires a pipeline to be set."; + + public const string MustHaveCurrentPipelineType = "{0} requires current pipeline type {1}, but current pipeline is {2}."; + + public const string MustNotBeTimestampQuery = "{0} cannot be used with QueryType.Timestamp."; + + public const string MustBeTimestampQuery = "{0} requires QueryType.Timestamp."; + + public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; + + public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; + + public const string MustHaveNonZeroConstantsSize = "{0} reports a zero size for {1}; the constants layout has no payload for the current backend."; + + public const string MustBeCpuAccessible = "{0} must use BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly to be mappable."; +} diff --git a/sources/Zenith.NET/ValidationMessages.cs b/sources/Zenith.NET/ValidationMessages.cs deleted file mode 100644 index b18f9d4f..00000000 --- a/sources/Zenith.NET/ValidationMessages.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace Zenith.NET; - -internal static class ValidationMessages -{ - public const string MustNotBeNull = "{0} must not be null."; - - public const string MustNotBeZero = "{0} must not be zero."; - - public const string MustHaveExactlyNHandles = "{0} must have exactly {1} handles for {2}."; - - public const string MustBeValidHandle = "{0} must be a valid handle for {1}."; - - public const string MustBeValidHandles = "{0} must be valid handles for {1}."; - - public const string HasUnsupportedSurfaceType = "{0} has unsupported SurfaceType '{1}'."; - - public const string HasInvalidValue = "{0} has an invalid value '{1}'."; - - public const string HasNoAttachments = "{0} has no attachments."; - - public const string MustNotBeDisposed = "{0} must not be disposed."; - - public const string MustBeLessThan = "{0} must be less than {1}."; - - public const string MustNotBeNullOrEmpty = "{0} must not be null or empty."; - - public const string MustNotBeNullOrWhitespace = "{0} must not be null or whitespace."; - - public const string MustBeGreaterThanZero = "{0} must be greater than zero."; - - public const string IsZeroWarning = "{0} is zero, which may be valid for some {1} but could indicate an issue."; - - public const string IsSetToNoneWarning = "{0} is set to None, which may be valid but could indicate an issue."; - - public const string MustBeWithinBounds = "{0} must be greater than zero and within the bounds of {1}."; - - public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; - - public const string MustBeGreaterThanOrEqualTo = "{0} must be greater than or equal to {1}."; - - public const string MustBeEqualTo = "{0} must be equal to {1}."; - - public const string MustBeBetween = "{0} must be between {1} and {2}."; - - public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; - - public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; - - public const string MustBeOneOf = "{0} must be one of: {1}."; - - public const string MustHaveFlag = "{0} must have the flag '{1}' set."; - - public const string MustHaveUsage = "{0} must have usage flag '{1}'."; - - public const string MustHaveSameValue = "{0} must match. First value: '{1}', second value: '{2}'."; - - public const string MustBeWithinResourceBounds = "{0} must be within the bounds of {1}."; - - public const string MustNotBeDepthStencilFormat = "{0} must not use a depth/stencil format."; - - public const string MustBeDepthStencilFormat = "{0} must use a depth/stencil format."; - - public const string MustBeSingleSampled = "{0} must use SampleCount.Count1."; - - public const string MustBeMultisampled = "{0} must use a sample count greater than SampleCount.Count1."; - - public const string MustBeFinite = "{0} must be finite."; - - public const string MustHaveCurrentPipeline = "{0} requires a pipeline to be set."; - - public const string MustHaveCurrentPipelineType = "{0} requires current pipeline type {1}, but current pipeline is {2}."; - - public const string MustNotBeTimestampQuery = "{0} cannot be used with QueryType.Timestamp."; - - public const string MustBeTimestampQuery = "{0} requires QueryType.Timestamp."; - - public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; - - public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; - - public const string MustHaveNonZeroConstantsSize = "{0} reports a zero size for {1}; the constants layout has no payload for the current backend."; - - public const string MustBeCpuAccessible = "{0} must use BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly to be mappable."; -} From 7f4d07d6076e6b747404d5d9c712fe03f3a3ab42 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 20:37:26 +0800 Subject: [PATCH 074/466] Refactor enums and struct layouts for graphics types Reassigned enum bit positions in BufferUsages, TextureUsages, and RayTracingInstanceFlags for consistency. Reordered and added states in TextureState and TextureType. Updated ColorAttachment field order and static constructors. Moved RenderState to the end of pipeline desc structs. Reorganized RayTracingTriangleGeometry fields and added index-related members. --- sources/Zenith.NET/Enums/BufferUsages.cs | 6 ++--- .../Enums/RayTracingInstanceFlags.cs | 4 +-- sources/Zenith.NET/Enums/TextureState.cs | 12 ++++----- sources/Zenith.NET/Enums/TextureType.cs | 8 +++--- sources/Zenith.NET/Enums/TextureUsages.cs | 8 +++--- sources/Zenith.NET/Structs/ColorAttachment.cs | 26 +++++++++---------- .../Structs/GraphicsPipelineDesc.cs | 4 +-- .../Structs/MeshShadingPipelineDesc.cs | 4 +-- .../Structs/RayTracingTriangleGeometry.cs | 8 +++--- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs index 4623127f..182c104f 100644 --- a/sources/Zenith.NET/Enums/BufferUsages.cs +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -9,11 +9,11 @@ public enum BufferUsages CopyDst = 1 << 1, - Vertex = 1 << 2, + Uniform = 1 << 2, - Index = 1 << 3, + Vertex = 1 << 3, - Uniform = 1 << 4, + Index = 1 << 4, StorageReadOnly = 1 << 5, diff --git a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs index 50d9429b..c4af153a 100644 --- a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs +++ b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs @@ -5,9 +5,9 @@ public enum RayTracingInstanceFlags { None = 0, - CullDisable = 1 << 0, + FrontCounterClockwise = 1 << 0, - FrontCounterClockwise = 1 << 1, + CullDisable = 1 << 1, ForceOpaque = 1 << 2, diff --git a/sources/Zenith.NET/Enums/TextureState.cs b/sources/Zenith.NET/Enums/TextureState.cs index 56b5eba7..10675cd2 100644 --- a/sources/Zenith.NET/Enums/TextureState.cs +++ b/sources/Zenith.NET/Enums/TextureState.cs @@ -2,19 +2,19 @@ public enum TextureState { - ColorAttachment, - - DepthStencil, + CopySrc, - DepthStencilReadOnly, + CopyDst, Sampled, Storage, - CopySrc, + ColorAttachment, - CopyDst, + DepthStencil, + + DepthStencilReadOnly, ResolveSrc, diff --git a/sources/Zenith.NET/Enums/TextureType.cs b/sources/Zenith.NET/Enums/TextureType.cs index 412f421c..a434d786 100644 --- a/sources/Zenith.NET/Enums/TextureType.cs +++ b/sources/Zenith.NET/Enums/TextureType.cs @@ -4,15 +4,15 @@ public enum TextureType { Texture1D, - Texture1DArray, - Texture2D, - Texture2DArray, - Texture3D, TextureCube, + Texture1DArray, + + Texture2DArray, + TextureCubeArray } diff --git a/sources/Zenith.NET/Enums/TextureUsages.cs b/sources/Zenith.NET/Enums/TextureUsages.cs index e91450a3..2da4da15 100644 --- a/sources/Zenith.NET/Enums/TextureUsages.cs +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -9,11 +9,11 @@ public enum TextureUsages CopyDst = 1 << 1, - ColorAttachment = 1 << 2, + Sampled = 1 << 2, - DepthStencil = 1 << 3, + Storage = 1 << 3, - Sampled = 1 << 4, + ColorAttachment = 1 << 4, - Storage = 1 << 5 + DepthStencil = 1 << 5 } diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 2cfe6fc5..6587b50c 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -8,27 +8,27 @@ public struct ColorAttachment public TextureSubresource Subresource; - public Texture? ResolveTexture; - - public TextureSubresource ResolveSubresource; - public LoadOp LoadOp; public StoreOp StoreOp; public Vector4 ClearColor; + public Texture? ResolveTexture; + + public TextureSubresource ResolveSubresource; + public static ColorAttachment Clear(Texture texture, Vector4 clearColor) { return new() { Texture = texture, Subresource = new(), - ResolveTexture = null, - ResolveSubresource = new(), LoadOp = LoadOp.Clear, StoreOp = StoreOp.Store, - ClearColor = clearColor + ClearColor = clearColor, + ResolveTexture = null, + ResolveSubresource = new() }; } @@ -38,11 +38,11 @@ public static ColorAttachment Load(Texture texture) { Texture = texture, Subresource = new(), - ResolveTexture = null, - ResolveSubresource = new(), LoadOp = LoadOp.Load, StoreOp = StoreOp.Store, - ClearColor = Vector4.Zero + ClearColor = Vector4.Zero, + ResolveTexture = null, + ResolveSubresource = new() }; } @@ -52,11 +52,11 @@ public static ColorAttachment DontCare(Texture texture) { Texture = texture, Subresource = new(), - ResolveTexture = null, - ResolveSubresource = new(), LoadOp = LoadOp.DontCare, StoreOp = StoreOp.Store, - ClearColor = Vector4.Zero + ClearColor = Vector4.Zero, + ResolveTexture = null, + ResolveSubresource = new() }; } } diff --git a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 2b772528..4f940fbc 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -2,8 +2,6 @@ public struct GraphicsPipelineDesc { - public RenderState RenderState; - public Shader VertexShader; public Shader FragmentShader; @@ -13,4 +11,6 @@ public struct GraphicsPipelineDesc public PrimitiveTopology PrimitiveTopology; public AttachmentFormats AttachmentFormats; + + public RenderState RenderState; } diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index 2d2c53f3..ab7d59bf 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -2,8 +2,6 @@ public struct MeshShadingPipelineDesc { - public RenderState RenderState; - public Shader? TaskShader; public Shader MeshShader; @@ -13,4 +11,6 @@ public struct MeshShadingPipelineDesc public PrimitiveTopology PrimitiveTopology; public AttachmentFormats AttachmentFormats; + + public RenderState RenderState; } diff --git a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index 193317f1..4ff380ef 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -6,10 +6,6 @@ public struct RayTracingTriangleGeometry { public Buffer VertexBuffer; - public Buffer? IndexBuffer; - - public Matrix4x4 Transform; - public PixelFormat VertexFormat; public uint VertexCount; @@ -18,9 +14,13 @@ public struct RayTracingTriangleGeometry public uint VertexOffsetInBytes; + public Buffer? IndexBuffer; + public IndexFormat IndexFormat; public uint IndexCount; public uint IndexOffsetInBytes; + + public Matrix4x4 Transform; } \ No newline at end of file From 4fe889276ee13dcc6935b68965a2b5467c4ba13c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 20:56:50 +0800 Subject: [PATCH 075/466] Update Uno.WinUI to version 6.5.237 Upgraded the Uno.WinUI NuGet package from 6.5.229 to 6.5.237 in Directory.Packages.props to include the latest features and fixes. --- sources/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index bd73f9f6..ec3a0ee3 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -21,7 +21,7 @@ - + \ No newline at end of file From ae9cc50736400feaa882354a232e758b5571a4ae Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 20 May 2026 21:07:12 +0800 Subject: [PATCH 076/466] Refactor Capabilities and Constants; add UTF-8 BOM Renamed Capabilities properties for clarity. Refactored Constants.Buffer for simpler lease management. Added UTF-8 BOM to several source files. --- sources/Zenith.NET/Capabilities.cs | 4 +- sources/Zenith.NET/Constants.cs | 37 ++++++++----------- sources/Zenith.NET/Enums/BlendOp.cs | 2 +- sources/Zenith.NET/Enums/BufferAccess.cs | 2 +- sources/Zenith.NET/Enums/CompareOp.cs | 2 +- sources/Zenith.NET/Enums/LoadOp.cs | 2 +- sources/Zenith.NET/Enums/StencilOp.cs | 2 +- sources/Zenith.NET/Enums/StoreOp.cs | 2 +- .../Structs/ColorAttachmentBlendState.cs | 2 +- .../Structs/RayTracingAabbGeometry.cs | 2 +- .../Structs/RayTracingTriangleGeometry.cs | 2 +- sources/Zenith.NET/Structs/RenderState.cs | 2 +- .../Zenith.NET/ValidationMessageEventArgs.cs | 2 +- 13 files changed, 28 insertions(+), 35 deletions(-) diff --git a/sources/Zenith.NET/Capabilities.cs b/sources/Zenith.NET/Capabilities.cs index 2fb03389..4c5f396f 100644 --- a/sources/Zenith.NET/Capabilities.cs +++ b/sources/Zenith.NET/Capabilities.cs @@ -4,7 +4,7 @@ public abstract class Capabilities { public abstract string DeviceName { get; } - public abstract bool SupportsRayTracing { get; } + public abstract bool RayTracing { get; } - public abstract bool SupportsMeshShading { get; } + public abstract bool MeshShading { get; } } diff --git a/sources/Zenith.NET/Constants.cs b/sources/Zenith.NET/Constants.cs index 620d60b7..04ffcc4b 100644 --- a/sources/Zenith.NET/Constants.cs +++ b/sources/Zenith.NET/Constants.cs @@ -10,35 +10,28 @@ internal class Constants(GraphicsContext context) : DisposableObject public Buffer Buffer(CommandBuffer commandBuffer, T data) where T : unmanaged, IConstantsLayout { + using Lock.Scope _ = @lock.EnterScope(); + uint sizeInBytes = SizeInBytes(); - Lease lease; - using (Lock.Scope _ = @lock.EnterScope()) + if (!borrowed.TryGetValue(commandBuffer, out List? leases)) { - if (!borrowed.TryGetValue(commandBuffer, out List? leases)) - { - borrowed[commandBuffer] = leases = []; - } + borrowed[commandBuffer] = leases = []; + } - Lease? availableLease = available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes); - if (availableLease is not null && available.Remove(availableLease)) - { - lease = availableLease; - } - else + if (!(available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) + { + lease = new(context.CreateBuffer(new() { - lease = new(context.CreateBuffer(new() - { - SizeInBytes = sizeInBytes, - StrideInBytes = 1, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Uniform - })); - } - - leases.Add(lease); + SizeInBytes = sizeInBytes, + StrideInBytes = 1, + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Uniform + })); } + leases.Add(lease); + Write(data, lease.Buffer); return lease.Buffer; diff --git a/sources/Zenith.NET/Enums/BlendOp.cs b/sources/Zenith.NET/Enums/BlendOp.cs index dabe79a8..133edfbb 100644 --- a/sources/Zenith.NET/Enums/BlendOp.cs +++ b/sources/Zenith.NET/Enums/BlendOp.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public enum BlendOp { diff --git a/sources/Zenith.NET/Enums/BufferAccess.cs b/sources/Zenith.NET/Enums/BufferAccess.cs index ee111387..58925a00 100644 --- a/sources/Zenith.NET/Enums/BufferAccess.cs +++ b/sources/Zenith.NET/Enums/BufferAccess.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public enum BufferAccess { diff --git a/sources/Zenith.NET/Enums/CompareOp.cs b/sources/Zenith.NET/Enums/CompareOp.cs index 8c28d024..f24edc29 100644 --- a/sources/Zenith.NET/Enums/CompareOp.cs +++ b/sources/Zenith.NET/Enums/CompareOp.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public enum CompareOp { diff --git a/sources/Zenith.NET/Enums/LoadOp.cs b/sources/Zenith.NET/Enums/LoadOp.cs index 9d3ba99e..ccf14d19 100644 --- a/sources/Zenith.NET/Enums/LoadOp.cs +++ b/sources/Zenith.NET/Enums/LoadOp.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public enum LoadOp { diff --git a/sources/Zenith.NET/Enums/StencilOp.cs b/sources/Zenith.NET/Enums/StencilOp.cs index 605979e3..1b9ff58d 100644 --- a/sources/Zenith.NET/Enums/StencilOp.cs +++ b/sources/Zenith.NET/Enums/StencilOp.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public enum StencilOp { diff --git a/sources/Zenith.NET/Enums/StoreOp.cs b/sources/Zenith.NET/Enums/StoreOp.cs index fc74145f..55a8eb45 100644 --- a/sources/Zenith.NET/Enums/StoreOp.cs +++ b/sources/Zenith.NET/Enums/StoreOp.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public enum StoreOp { diff --git a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs index ecfc0a61..6eff8d3b 100644 --- a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public struct ColorAttachmentBlendState { diff --git a/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs index c2481f52..db7966aa 100644 --- a/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public struct RayTracingAabbGeometry { diff --git a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index 4ff380ef..bc2f35bb 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; namespace Zenith.NET; diff --git a/sources/Zenith.NET/Structs/RenderState.cs b/sources/Zenith.NET/Structs/RenderState.cs index f987efa0..89b68199 100644 --- a/sources/Zenith.NET/Structs/RenderState.cs +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public struct RenderState { diff --git a/sources/Zenith.NET/ValidationMessageEventArgs.cs b/sources/Zenith.NET/ValidationMessageEventArgs.cs index 060eb03b..7f795e76 100644 --- a/sources/Zenith.NET/ValidationMessageEventArgs.cs +++ b/sources/Zenith.NET/ValidationMessageEventArgs.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public class ValidationMessageEventArgs(MessageSource source, MessageSeverity severity, string message) : EventArgs { From fe0789e01f80563d14a1bcd26a2f12af6d35e8f8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 14:00:09 +0800 Subject: [PATCH 077/466] Refactor usages order, rename MipmapFilter, update validation - Standardize bitwise OR order for buffer/texture usages. - Rename SamplerDesc.MipmapFilter to MipFilter throughout. - Refactor pipeline validation logic in ValidationLayer for clarity and remove redundant checks. - No changes to runtime behavior; improves code consistency and readability. --- sources/Zenith.NET/Structs/BufferDesc.cs | 12 ++++++------ sources/Zenith.NET/Structs/SamplerDesc.cs | 12 ++++++------ sources/Zenith.NET/Structs/TextureDesc.cs | 18 +++++++++--------- sources/Zenith.NET/ValidationLayer.cs | 16 +++++++--------- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index 62de9e8e..3eade879 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -17,7 +17,7 @@ public static BufferDesc Vertex(uint sizeInBytes, uint strideInBytes) SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Vertex | BufferUsages.CopyDst + Usages = BufferUsages.CopyDst | BufferUsages.Vertex }; } @@ -28,7 +28,7 @@ public static BufferDesc Index(uint sizeInBytes, uint strideInBytes) SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Index | BufferUsages.CopyDst + Usages = BufferUsages.CopyDst | BufferUsages.Index }; } @@ -39,7 +39,7 @@ public static BufferDesc Uniform(uint sizeInBytes) SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Uniform | BufferUsages.CopyDst + Usages = BufferUsages.CopyDst | BufferUsages.Uniform }; } @@ -50,7 +50,7 @@ public static BufferDesc StorageReadOnly(uint sizeInBytes, uint strideInBytes) SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst + Usages = BufferUsages.CopyDst | BufferUsages.StorageReadOnly }; } @@ -61,7 +61,7 @@ public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadWrite | BufferUsages.CopyDst + Usages = BufferUsages.CopyDst | BufferUsages.StorageReadWrite }; } @@ -72,7 +72,7 @@ public static BufferDesc Indirect(uint sizeInBytes) SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Indirect | BufferUsages.CopyDst + Usages = BufferUsages.CopyDst | BufferUsages.Indirect }; } diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 8647d081..073ffb35 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -6,7 +6,7 @@ public struct SamplerDesc public FilterMode MagFilter; - public FilterMode MipmapFilter; + public FilterMode MipFilter; public AddressMode AddressU; @@ -32,7 +32,7 @@ public static SamplerDesc LinearWrap() { MinFilter = FilterMode.Linear, MagFilter = FilterMode.Linear, - MipmapFilter = FilterMode.Linear, + MipFilter = FilterMode.Linear, AddressU = AddressMode.Wrap, AddressV = AddressMode.Wrap, AddressW = AddressMode.Wrap, @@ -51,7 +51,7 @@ public static SamplerDesc LinearClamp() { MinFilter = FilterMode.Linear, MagFilter = FilterMode.Linear, - MipmapFilter = FilterMode.Linear, + MipFilter = FilterMode.Linear, AddressU = AddressMode.Clamp, AddressV = AddressMode.Clamp, AddressW = AddressMode.Clamp, @@ -70,7 +70,7 @@ public static SamplerDesc PointWrap() { MinFilter = FilterMode.Point, MagFilter = FilterMode.Point, - MipmapFilter = FilterMode.Point, + MipFilter = FilterMode.Point, AddressU = AddressMode.Wrap, AddressV = AddressMode.Wrap, AddressW = AddressMode.Wrap, @@ -89,7 +89,7 @@ public static SamplerDesc PointClamp() { MinFilter = FilterMode.Point, MagFilter = FilterMode.Point, - MipmapFilter = FilterMode.Point, + MipFilter = FilterMode.Point, AddressU = AddressMode.Clamp, AddressV = AddressMode.Clamp, AddressW = AddressMode.Clamp, @@ -108,7 +108,7 @@ public static SamplerDesc Anisotropic(uint maxAnisotropy) { MinFilter = FilterMode.Linear, MagFilter = FilterMode.Linear, - MipmapFilter = FilterMode.Linear, + MipFilter = FilterMode.Linear, AddressU = AddressMode.Wrap, AddressV = AddressMode.Wrap, AddressW = AddressMode.Wrap, diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index 4731a3ae..add3d42a 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -32,7 +32,7 @@ public static TextureDesc Texture1D(PixelFormat format, uint width, uint mipLeve MipLevels = mipLevels, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -48,7 +48,7 @@ public static TextureDesc Texture1DArray(PixelFormat format, uint width, uint ar MipLevels = mipLevels, ArrayLayers = arrayLayers, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -64,7 +64,7 @@ public static TextureDesc Texture2D(PixelFormat format, uint width, uint height, MipLevels = mipLevels, ArrayLayers = 1, SampleCount = sampleCount, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -80,7 +80,7 @@ public static TextureDesc Texture2DArray(PixelFormat format, uint width, uint he MipLevels = mipLevels, ArrayLayers = arrayLayers, SampleCount = sampleCount, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -96,7 +96,7 @@ public static TextureDesc Texture3D(PixelFormat format, uint width, uint height, MipLevels = mipLevels, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -112,7 +112,7 @@ public static TextureDesc TextureCube(PixelFormat format, uint size, uint mipLev MipLevels = mipLevels, ArrayLayers = 6, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -128,7 +128,7 @@ public static TextureDesc TextureCubeArray(PixelFormat format, uint size, uint c MipLevels = mipLevels, ArrayLayers = 6 * cubeCount, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.CopyDst | TextureUsages.Sampled }; } @@ -144,7 +144,7 @@ public static TextureDesc RenderTarget2D(PixelFormat format, uint width, uint he MipLevels = mipLevels, ArrayLayers = 1, SampleCount = sampleCount, - Usages = TextureUsages.ColorAttachment | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.ColorAttachment }; } @@ -160,7 +160,7 @@ public static TextureDesc DepthStencil2D(PixelFormat format, uint width, uint he MipLevels = 1, ArrayLayers = 1, SampleCount = sampleCount, - Usages = TextureUsages.DepthStencil | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.DepthStencil }; } } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 5c28c6a8..5eed999d 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -183,7 +183,7 @@ internal void ValidateDesc(SamplerDesc desc) { CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); - CheckEnum("SamplerDesc.MipmapFilter", desc.MipmapFilter); + CheckEnum("SamplerDesc.MipFilter", desc.MipFilter); CheckEnum("SamplerDesc.AddressU", desc.AddressU); CheckEnum("SamplerDesc.AddressV", desc.AddressV); CheckEnum("SamplerDesc.AddressW", desc.AddressW); @@ -211,8 +211,6 @@ internal void ValidateDesc(ShaderDesc desc) internal void ValidateDesc(GraphicsPipelineDesc desc) { - CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); - CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); @@ -230,6 +228,7 @@ internal void ValidateDesc(GraphicsPipelineDesc desc) CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); + CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); } internal void ValidateDesc(ComputePipelineDesc desc) @@ -239,7 +238,10 @@ internal void ValidateDesc(ComputePipelineDesc desc) internal void ValidateDesc(MeshShadingPipelineDesc desc) { - CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); + if (desc.TaskShader is not null) + { + CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); + } CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); @@ -250,11 +252,7 @@ internal void ValidateDesc(MeshShadingPipelineDesc desc) } CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - - if (desc.TaskShader is not null) - { - CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); - } + CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); } internal void ValidateDesc(QueryHeapDesc desc) From 6fda8cafe47564389766716f5749e40e9f0b9364 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 17:32:33 +0800 Subject: [PATCH 078/466] Make resource handle properties abstract in core classes Converted resource handle properties in Buffer, BufferView, Sampler, Texture, and TextureView from auto-implemented to abstract. This enforces implementation in derived classes and removes default property definitions. --- sources/Zenith.NET/Buffer.cs | 6 +++--- sources/Zenith.NET/BufferView.cs | 6 +++--- sources/Zenith.NET/Sampler.cs | 2 +- sources/Zenith.NET/Texture.cs | 4 ++-- sources/Zenith.NET/TextureView.cs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index eb8f7c30..d760962d 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -6,11 +6,11 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public ref readonly BufferDesc Desc => ref desc; - public ResourceHandle UniformHandle { get; } + public abstract ResourceHandle UniformHandle { get; } - public ResourceHandle StorageReadOnlyHandle { get; } + public abstract ResourceHandle StorageReadOnlyHandle { get; } - public ResourceHandle StorageReadWriteHandle { get; } + public abstract ResourceHandle StorageReadWriteHandle { get; } public MappedMemory Map() { diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs index 4f7eaa3c..b75e1de4 100644 --- a/sources/Zenith.NET/BufferView.cs +++ b/sources/Zenith.NET/BufferView.cs @@ -6,9 +6,9 @@ public abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : public ref readonly BufferViewDesc Desc => ref desc; - public ResourceHandle UniformHandle { get; } + public abstract ResourceHandle UniformHandle { get; } - public ResourceHandle StorageReadOnlyHandle { get; } + public abstract ResourceHandle StorageReadOnlyHandle { get; } - public ResourceHandle StorageReadWriteHandle { get; } + public abstract ResourceHandle StorageReadWriteHandle { get; } } diff --git a/sources/Zenith.NET/Sampler.cs b/sources/Zenith.NET/Sampler.cs index 5ad1d477..4ff48e79 100644 --- a/sources/Zenith.NET/Sampler.cs +++ b/sources/Zenith.NET/Sampler.cs @@ -6,5 +6,5 @@ public abstract class Sampler(GraphicsContext context, SamplerDesc desc) : Graph public ref readonly SamplerDesc Desc => ref desc; - public ResourceHandle Handle { get; } + public abstract ResourceHandle Handle { get; } } diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index 6889dd06..b0df7b16 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -6,9 +6,9 @@ public abstract class Texture(GraphicsContext context, TextureDesc desc) : Graph public ref readonly TextureDesc Desc => ref desc; - public ResourceHandle SampledHandle { get; } + public abstract ResourceHandle SampledHandle { get; } - public ResourceHandle StorageHandle { get; } + public abstract ResourceHandle StorageHandle { get; } public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { diff --git a/sources/Zenith.NET/TextureView.cs b/sources/Zenith.NET/TextureView.cs index 6588f449..2ea722f4 100644 --- a/sources/Zenith.NET/TextureView.cs +++ b/sources/Zenith.NET/TextureView.cs @@ -6,7 +6,7 @@ public abstract class TextureView(GraphicsContext context, TextureViewDesc desc) public ref readonly TextureViewDesc Desc => ref desc; - public ResourceHandle SampledHandle { get; } + public abstract ResourceHandle SampledHandle { get; } - public ResourceHandle StorageHandle { get; } + public abstract ResourceHandle StorageHandle { get; } } From a41093e5ac78bbba31da6b2ff1e689951eb64080 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 17:52:02 +0800 Subject: [PATCH 079/466] Stub out DirectX 12 backend and remove DX12-specific code Replaced all DXGraphicsContext methods with NotImplementedException, effectively disabling the DirectX 12 backend. Removed all DirectX 12-specific implementation files and cleared related global usings. This prepares the codebase for future refactoring or reimplementation of the DirectX 12 backend. --- .../DXBottomLevelAccelerationStructure.cs | 139 ---- sources/Zenith.NET.DirectX12/DXBuffer.cs | 71 -- sources/Zenith.NET.DirectX12/DXBufferView.cs | 83 --- .../Zenith.NET.DirectX12/DXCapabilities.cs | 30 - .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 614 ----------------- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 36 - .../Zenith.NET.DirectX12/DXComputePipeline.cs | 35 - .../DXDescriptorAllocator.cs | 35 - .../Zenith.NET.DirectX12/DXDescriptorPool.cs | 79 --- .../Zenith.NET.DirectX12/DXDescriptorTable.cs | 54 -- .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 24 - sources/Zenith.NET.DirectX12/DXFence.cs | 44 -- sources/Zenith.NET.DirectX12/DXFormats.cs | 622 ------------------ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 181 +---- .../DXGraphicsPipeline.cs | 150 ----- sources/Zenith.NET.DirectX12/DXHeap.cs | 33 - .../DXMeshShadingPipeline.cs | 144 ---- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 53 -- .../Zenith.NET.DirectX12/DXResourceTable.cs | 222 ------- sources/Zenith.NET.DirectX12/DXSampler.cs | 49 -- sources/Zenith.NET.DirectX12/DXShader.cs | 23 - sources/Zenith.NET.DirectX12/DXSwapChain.cs | 134 ---- .../DXSwapChainTargets.cs | 90 --- sources/Zenith.NET.DirectX12/DXTexture.cs | 236 ------- sources/Zenith.NET.DirectX12/DXTextureView.cs | 200 ------ .../DXTopLevelAccelerationStructure.cs | 153 ----- .../Zenith.NET.DirectX12/DXValidationLayer.cs | 37 -- sources/Zenith.NET.DirectX12/Extensions.cs | 234 ------- sources/Zenith.NET.DirectX12/Usings.cs | 15 +- 29 files changed, 23 insertions(+), 3797 deletions(-) delete mode 100644 sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXBuffer.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXBufferView.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXCapabilities.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXCommandBuffer.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXCommandQueue.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXComputePipeline.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXDescriptorPool.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXDescriptorTable.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXDescriptorToken.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXFence.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXFormats.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXHeap.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXQueryHeap.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXResourceTable.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXSampler.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXShader.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXSwapChain.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXTexture.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXTextureView.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs delete mode 100644 sources/Zenith.NET.DirectX12/DXValidationLayer.cs diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs deleted file mode 100644 index eab0f893..00000000 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; -using Silk.NET.Maths; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXBottomLevelAccelerationStructure : BottomLevelAccelerationStructure -{ - public DXBottomLevelAccelerationStructure(DXGraphicsContext context, BottomLevelAccelerationStructureDesc desc, DXCommandBuffer commandBuffer) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - uint geometryCount = (uint)desc.Geometries.Length; - - TransformBuffer = new(context, new() - { - SizeInBytes = (uint)(sizeof(Matrix3X4) * geometryCount), - StrideInBytes = (uint)sizeof(Matrix3X4), - Flags = BufferUsageFlags.MapWrite - }); - - MappedMemory mappedMemory = TransformBuffer.Map(); - - Matrix3X4[] transforms = desc.Geometries.Select(static item => DXFormats.DirectX12(item.Triangles.Transform)).ToArray(); - - if (geometryCount > 0) - { - unsafe - { - transforms.AsSpan().CopyTo(new Span>((void*)mappedMemory.Pointer, (int)geometryCount)); - } - } - - TransformBuffer.Unmap(); - - RaytracingGeometryDesc* geometries = (RaytracingGeometryDesc*)ZenithMarshal.Allocate(scope, geometryCount); - for (uint i = 0; i < geometryCount; i++) - { - RayTracingGeometry geometry = desc.Geometries[i]; - - geometries[i] = new() - { - Type = DXFormats.DirectX12(geometry.Type), - Flags = DXFormats.DirectX12(geometry.Flags), - Anonymous = new - ( - triangles: geometry.Type is RayTracingGeometryType.Triangles ? new() - { - Transform3x4 = TransformBuffer.GPUVirtualAddress + (uint)(sizeof(Matrix3X4) * i), - IndexFormat = geometry.Triangles.IndexBuffer is not null ? DXFormats.DirectX12(geometry.Triangles.IndexFormat) : Format.FormatUnknown, - VertexFormat = DXFormats.DirectX12(geometry.Triangles.VertexFormat), - IndexCount = geometry.Triangles.IndexCount, - VertexCount = geometry.Triangles.VertexCount, - IndexBuffer = geometry.Triangles.IndexBuffer is not null ? geometry.Triangles.IndexBuffer.DirectX12().GPUVirtualAddress + geometry.Triangles.IndexOffsetInBytes : 0, - VertexBuffer = new() - { - StartAddress = geometry.Triangles.VertexBuffer.DirectX12().GPUVirtualAddress + geometry.Triangles.VertexOffsetInBytes, - StrideInBytes = geometry.Triangles.VertexStrideInBytes - } - } : null, - aABBs: geometry.Type is RayTracingGeometryType.AABBs ? new() - { - AABBCount = geometry.AABBs.Count, - AABBs = new() - { - StartAddress = geometry.AABBs.Buffer.DirectX12().GPUVirtualAddress + geometry.AABBs.OffsetInBytes, - StrideInBytes = geometry.AABBs.StrideInBytes - } - } : null - ) - }; - } - - BuildRaytracingAccelerationStructureInputs inputs = new() - { - Type = RaytracingAccelerationStructureType.BottomLevel, - Flags = DXFormats.DirectX12(desc.Flags), - NumDescs = geometryCount, - PGeometryDescs = geometries - }; - - RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); - - context.Device5?.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - - AccelerationStructureBuffer = new(context, new() - { - SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, - StrideInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, - Flags = BufferUsageFlags.AccelerationStructure - }); - - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, - StrideInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, - Flags = BufferUsageFlags.UnorderedAccess - }); - - BuildRaytracingAccelerationStructureDesc buildDesc = new() - { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - Inputs = inputs, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress - }; - - commandBuffer.GraphicsCommandList4.BuildRaytracingAccelerationStructure(&buildDesc, 0, (RaytracingAccelerationStructurePostbuildInfoDesc*)null); - - ResourceBarrier barrier = new() - { - Type = ResourceBarrierType.Uav, - UAV = new() - { - PResource = AccelerationStructureBuffer.Resource - } - }; - - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public DXBuffer TransformBuffer { get; } - - public DXBuffer AccelerationStructureBuffer { get; } - - public DXBuffer ScratchBuffer { get; } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - TransformBuffer.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs deleted file mode 100644 index 4cf0566c..00000000 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXBuffer : Buffer -{ - public ComPtr Resource; - - public ulong GPUVirtualAddress; - - public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc) - { - ResourceDesc resourceDesc = new() - { - Dimension = ResourceDimension.Buffer, - Width = ZenithHelper.Align(desc.SizeInBytes, 256u), - Height = 1, - DepthOrArraySize = 1, - MipLevels = 1, - SampleDesc = new(1, 0), - Layout = TextureLayout.LayoutRowMajor, - Flags = DXFormats.DirectX12(desc.Flags).Flags - }; - - Heap = new(context, resourceDesc, DXFormats.DirectX12(desc.Flags).Type, HeapFlags.AllowOnlyBuffers); - - context.Device.CreatePlacedResource(Heap.Heap, 0, &resourceDesc, DXFormats.DirectX12(desc.Flags).States, null, out Resource).Success(); - - GPUVirtualAddress = Resource.GetGPUVirtualAddress(); - - View = new(context, new() - { - Buffer = this, - OffsetInBytes = 0, - SizeInBytes = desc.SizeInBytes, - StrideInBytes = desc.StrideInBytes - }); - } - - public DXHeap Heap { get; } - - public DXBufferView View { get; } - - protected override MappedMemory MapImpl() - { - void* pointer; - Resource.Map(0, (DxRange*)null, &pointer).Success(); - - return new() { Pointer = (nint)pointer, SizeInBytes = Desc.SizeInBytes }; - } - - public override void Unmap() - { - Resource.Unmap(0, (DxRange*)null); - } - - protected override void SetResourceName(string name) - { - Resource.SetName(name).Success(); - } - - protected override void Destroy() - { - View.Dispose(); - - Resource.Dispose(); - - Heap.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs deleted file mode 100644 index 9f0ba1fb..00000000 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXBufferView(DXGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) -{ - private DXDescriptorToken? cbvToken; - private DXDescriptorToken? srvToken; - private DXDescriptorToken? uavToken; - - public CpuDescriptorHandle CbvHandle => (cbvToken ??= CreateCbvToken()).Handle; - - public CpuDescriptorHandle SrvHandle => (srvToken ??= CreateSrvToken()).Handle; - - public CpuDescriptorHandle UavHandle => (uavToken ??= CreateUavToken()).Handle; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - uavToken?.Dispose(); - srvToken?.Dispose(); - cbvToken?.Dispose(); - } - - private DXDescriptorToken CreateCbvToken() - { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); - - ConstantBufferViewDesc viewDesc = new() - { - BufferLocation = Desc.Buffer.DirectX12().GPUVirtualAddress + Desc.OffsetInBytes, - SizeInBytes = ZenithHelper.Align(Desc.SizeInBytes, 256u) - }; - - context.Device.CreateConstantBufferView(&viewDesc, token.Handle); - - return token; - } - - private DXDescriptorToken CreateSrvToken() - { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); - - ShaderResourceViewDesc viewDesc = new() - { - ViewDimension = SrvDimension.Buffer, - Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, - Buffer = new() - { - FirstElement = Desc.OffsetInBytes / Desc.StrideInBytes, - NumElements = Desc.SizeInBytes / Desc.StrideInBytes, - StructureByteStride = Desc.StrideInBytes - } - }; - - context.Device.CreateShaderResourceView(Desc.Buffer.DirectX12().Resource, &viewDesc, token.Handle); - - return token; - } - - private DXDescriptorToken CreateUavToken() - { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); - - UnorderedAccessViewDesc viewDesc = new() - { - ViewDimension = UavDimension.Buffer, - Buffer = new() - { - FirstElement = Desc.OffsetInBytes / Desc.StrideInBytes, - NumElements = Desc.SizeInBytes / Desc.StrideInBytes, - StructureByteStride = Desc.StrideInBytes - } - }; - - context.Device.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, (ID3D12Resource*)null, &viewDesc, token.Handle); - - return token; - } -} diff --git a/sources/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Zenith.NET.DirectX12/DXCapabilities.cs deleted file mode 100644 index 9326aa66..00000000 --- a/sources/Zenith.NET.DirectX12/DXCapabilities.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; -using Feature = Silk.NET.Direct3D12.Feature; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXCapabilities : Capabilities -{ - public DXCapabilities(DXGraphicsContext context) - { - AdapterDesc desc; - context.Adapter4.GetDesc(&desc).Success(); - - FeatureDataD3D12Options5 options5 = new(); - context.Device.CheckFeatureSupport(Feature.D3D12Options5, &options5, (uint)sizeof(FeatureDataD3D12Options5)).Success(); - - FeatureDataD3D12Options7 options7 = new(); - context.Device.CheckFeatureSupport(Feature.D3D12Options7, &options7, (uint)sizeof(FeatureDataD3D12Options7)).Success(); - - DeviceName = ZenithMarshal.StringFromPointer((nint)desc.Description, StringEncoding.UTF16); - RayTracingSupported = options5.RaytracingTier >= RaytracingTier.Tier11; - MeshShadingSupported = options7.MeshShaderTier is not MeshShaderTier.TierNotSupported; - } - - public override string DeviceName { get; } - - public override bool RayTracingSupported { get; } - - public override bool MeshShadingSupported { get; } -} diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs deleted file mode 100644 index a2f7ed23..00000000 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ /dev/null @@ -1,614 +0,0 @@ -using System.Numerics; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; -using Silk.NET.Maths; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXCommandBuffer : CommandBuffer -{ - private const uint TextureDataPitchAlignment = 256; - - private const uint TextureDataPlacementAlignment = 512; - - private readonly DXDescriptorTable? cbvSrvUavTable; - private readonly DXDescriptorTable? samplerTable; - private DXDescriptorToken[] currentRenderPassTokens = []; - - public ComPtr CommandAllocator; - - public ComPtr CommandList; - - public ComPtr GraphicsCommandList4; - - public ComPtr? GraphicsCommandList6; - - public DXCommandBuffer(DXGraphicsContext context, DXCommandQueue queue) : base(context, queue) - { - if (queue.Type is not CommandQueueType.Copy) - { - cbvSrvUavTable = new(context, DescriptorHeapType.CbvSrvUav, 2048); - samplerTable = new(context, DescriptorHeapType.Sampler, 1024); - } - - context.Device.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), out CommandAllocator).Success(); - - context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, (ComPtr)null, out CommandList).Success(); - - CommandList.QueryInterface(out GraphicsCommandList4).Success(); - - if (CommandList.QueryInterface(out ComPtr graphicsCommandList6).IsSuccess()) - { - GraphicsCommandList6 = graphicsCommandList6; - } - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) - { - GraphicsCommandList4.CopyBufferRegion(dest.DirectX12().Resource, destOffsetInBytes, src.DirectX12().Resource, srcOffsetInBytes, sizeInBytes); - } - - protected override void CopyBufferToTextureImpl(Buffer src, - uint srcOffsetInBytes, - TextureDataLayout srcLayout, - Texture dest, - TextureSubresource destSubresource, - Offset3D destOffset, - Extent3D destExtent) - { - DXBuffer dxSrc = src.DirectX12(); - DXTexture dxDest = dest.DirectX12(); - - (uint blockWidth, uint blockHeight, uint blocksWide, _) = ZenithHelper.BlockLayout(dxDest.Desc.Format, destExtent.Width, destExtent.Height); - - uint offsetX = ZenithHelper.Align(destOffset.X, blockWidth); - uint offsetY = ZenithHelper.Align(destOffset.Y, blockHeight); - uint extentWidth = ZenithHelper.Align(destExtent.Width, blockWidth); - uint extentHeight = ZenithHelper.Align(destExtent.Height, blockHeight); - - for (uint i = 0; i < destExtent.Depth; i++) - { - TextureCopyLocation srcLocation = new() - { - PResource = dxSrc.Resource, - Type = TextureCopyType.PlacedFootprint, - PlacedFootprint = new() - { - Offset = srcOffsetInBytes + (srcLayout.SlicePitchInBytes * i), - Footprint = new() - { - Format = DXFormats.DirectX12(dxDest.Desc.Format), - Width = extentWidth, - Height = extentHeight, - Depth = 1, - RowPitch = srcLayout.RowPitchInBytes - } - } - }; - - Box srcBox = new(0, 0, 0, extentWidth, extentHeight, 1); - - TextureCopyLocation destLocation = new() - { - PResource = dxDest.Resource, - Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = dxDest.SubresourceIndex(destSubresource) - }; - - GraphicsCommandList4.CopyTextureRegion(&destLocation, offsetX, offsetY, destOffset.Z + i, &srcLocation, &srcBox); - } - } - - protected override void CopyTextureImpl(Texture src, - TextureSubresource srcSubresource, - Offset3D srcOffset, - Texture dest, - TextureSubresource destSubresource, - Offset3D destOffset, - Extent3D extent) - { - DXTexture dxSrc = src.DirectX12(); - DXTexture dxDest = dest.DirectX12(); - - TextureCopyLocation srcLocation = new() - { - PResource = dxSrc.Resource, - Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = dxSrc.SubresourceIndex(srcSubresource) - }; - - Box srcBox = new(srcOffset.X, srcOffset.Y, srcOffset.Z, srcOffset.X + extent.Width, srcOffset.Y + extent.Height, srcOffset.Z + extent.Depth); - - TextureCopyLocation destLocation = new() - { - PResource = dxDest.Resource, - Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = dxDest.SubresourceIndex(destSubresource) - }; - - GraphicsCommandList4.CopyTextureRegion(&destLocation, destOffset.X, destOffset.Y, destOffset.Z, &srcLocation, &srcBox); - } - - protected override void CopyTextureToBufferImpl(Texture src, - TextureSubresource srcSubresource, - Offset3D srcOffset, - Extent3D srcExtent, - Buffer dest, - uint destOffsetInBytes, - TextureDataLayout destLayout) - { - DXTexture dxSrc = src.DirectX12(); - DXBuffer dxDest = dest.DirectX12(); - - (uint blockWidth, uint blockHeight, uint blocksWide, _) = ZenithHelper.BlockLayout(dxSrc.Desc.Format, srcExtent.Width, srcExtent.Height); - - uint offsetX = ZenithHelper.Align(srcOffset.X, blockWidth); - uint offsetY = ZenithHelper.Align(srcOffset.Y, blockHeight); - uint extentWidth = ZenithHelper.Align(srcExtent.Width, blockWidth); - uint extentHeight = ZenithHelper.Align(srcExtent.Height, blockHeight); - - TextureCopyLocation srcLocation = new() - { - PResource = dxSrc.Resource, - Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = dxSrc.SubresourceIndex(srcSubresource) - }; - - Box srcBox = new(offsetX, offsetY, srcOffset.Z, offsetX + extentWidth, offsetY + extentHeight, srcOffset.Z + srcExtent.Depth); - - for (uint i = 0; i < srcExtent.Depth; i++) - { - Box sliceBox = new(offsetX, offsetY, srcOffset.Z + i, offsetX + extentWidth, offsetY + extentHeight, srcOffset.Z + i + 1); - - TextureCopyLocation destLocation = new() - { - PResource = dxDest.Resource, - Type = TextureCopyType.PlacedFootprint, - PlacedFootprint = new() - { - Offset = destOffsetInBytes + (destLayout.SlicePitchInBytes * i), - Footprint = new() - { - Format = DXFormats.DirectX12(dxSrc.Desc.Format), - Width = extentWidth, - Height = extentHeight, - Depth = 1, - RowPitch = destLayout.RowPitchInBytes - } - } - }; - - GraphicsCommandList4.CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &sliceBox); - } - } - - protected override TextureDataLayout GetTextureCopyLayout(PixelFormat format, Extent3D extent) - { - uint rowPitchInBytes = ZenithHelper.Align(ZenithHelper.RowPitchInBytes(format, extent.Width, extent.Height), TextureDataPitchAlignment); - (_, _, _, uint blocksHigh) = ZenithHelper.BlockLayout(format, extent.Width, extent.Height); - - uint slicePitchInBytes = ZenithHelper.Align(rowPitchInBytes * blocksHigh, TextureDataPlacementAlignment); - - return new() - { - SizeInBytes = slicePitchInBytes * extent.Depth, - RowPitchInBytes = rowPitchInBytes, - SlicePitchInBytes = slicePitchInBytes - }; - } - - protected override void ResolveTextureImpl(Texture src, - TextureSubresource srcSubresource, - Texture dest, - TextureSubresource destSubresource) - { - DXTexture dxSrc = src.DirectX12(); - DXTexture dxDest = dest.DirectX12(); - - GraphicsCommandList4.ResolveSubresource(dxDest.Resource, dxDest.SubresourceIndex(destSubresource), dxSrc.Resource, dxSrc.SubresourceIndex(srcSubresource), DXFormats.DirectX12(dxDest.Desc.Format)); - } - - protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) - { - return new DXBottomLevelAccelerationStructure(Context, desc, this); - } - - protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) - { - return new DXTopLevelAccelerationStructure(Context, desc, this); - } - - protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) - { - accelerationStructure.DirectX12().Update(this, newDesc); - } - - protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment) - { - ReleaseRenderPassTokens(); - - using ZenithMarshal.Scope scope = new(); - - uint colorAttachmentCount = (uint)colorAttachments.Length; - bool hasDepthStencilAttachment = depthStencilAttachment is not null; - - RenderPassRenderTargetDesc* renderTargets = (RenderPassRenderTargetDesc*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - RenderPassDepthStencilDesc* depthStencil = hasDepthStencilAttachment ? (RenderPassDepthStencilDesc*)ZenithMarshal.Allocate(scope, 1) : null; - - currentRenderPassTokens = new DXDescriptorToken[colorAttachmentCount + (hasDepthStencilAttachment ? 1 : 0)]; - - for (uint i = 0; i < colorAttachmentCount; i++) - { - ColorAttachment attachment = colorAttachments[(int)i]; - - renderTargets[i] = new() - { - CpuDescriptor = (currentRenderPassTokens[i] = attachment.Texture.DirectX12().CreateRtvToken(attachment.Subresource)).Handle, - BeginningAccess = new() - { - Type = RenderPassBeginningAccessType.Preserve - }, - EndingAccess = new() - { - Type = attachment.StoreOp is StoreOp.Store ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.Discard - } - }; - - ref RenderPassRenderTargetDesc renderTarget = ref renderTargets[i]; - - switch (attachment.LoadOp) - { - case LoadOp.Clear: - renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Clear; - renderTarget.BeginningAccess.Clear.ClearValue.Format = DXFormats.DirectX12(attachment.Texture.Desc.Format); - - fixed (float* colorPtr = renderTarget.BeginningAccess.Clear.ClearValue.Anonymous.Color) - { - attachment.ClearColor.CopyTo(new Span(colorPtr, 4)); - } - break; - - case LoadOp.DontCare: - renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Discard; - break; - } - } - - if (hasDepthStencilAttachment) - { - DepthStencilAttachment attachment = depthStencilAttachment!.Value; - PixelFormat format = attachment.Texture.Desc.Format; - bool hasDepth = ZenithHelper.HasDepth(format); - bool hasStencil = ZenithHelper.HasStencil(format); - - depthStencil[0] = new() - { - CpuDescriptor = (currentRenderPassTokens[colorAttachmentCount] = attachment.Texture.DirectX12().CreateDsvToken(attachment.Subresource)).Handle, - DepthBeginningAccess = new() - { - Type = hasDepth ? RenderPassBeginningAccessType.Preserve : RenderPassBeginningAccessType.NoAccess - }, - StencilBeginningAccess = new() - { - Type = hasStencil ? RenderPassBeginningAccessType.Preserve : RenderPassBeginningAccessType.NoAccess - }, - DepthEndingAccess = new() - { - Type = hasDepth ? attachment.DepthStoreOp is StoreOp.Store ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.Discard : RenderPassEndingAccessType.NoAccess - }, - StencilEndingAccess = new() - { - Type = hasStencil ? attachment.StencilStoreOp is StoreOp.Store ? RenderPassEndingAccessType.Preserve : RenderPassEndingAccessType.Discard : RenderPassEndingAccessType.NoAccess - } - }; - - ref RenderPassDepthStencilDesc depthStencilDesc = ref depthStencil[0]; - - if (depthStencilDesc.DepthBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) - { - switch (attachment.DepthLoadOp) - { - case LoadOp.Clear: - depthStencilDesc.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Clear; - depthStencilDesc.DepthBeginningAccess.Clear.ClearValue.Format = DXFormats.DirectX12(format); - depthStencilDesc.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth = attachment.ClearDepth; - break; - - case LoadOp.DontCare: - depthStencilDesc.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Discard; - break; - } - } - - if (depthStencilDesc.StencilBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) - { - switch (attachment.StencilLoadOp) - { - case LoadOp.Clear: - depthStencilDesc.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Clear; - depthStencilDesc.StencilBeginningAccess.Clear.ClearValue.Format = DXFormats.DirectX12(format); - depthStencilDesc.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = attachment.ClearStencil; - break; - - case LoadOp.DontCare: - depthStencilDesc.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Discard; - break; - } - } - } - - GraphicsCommandList4.BeginRenderPass(colorAttachmentCount, renderTargets, depthStencil, RenderPassFlags.None); - } - - protected override void EndRenderPassImpl() - { - GraphicsCommandList4.EndRenderPass(); - - ReleaseRenderPassTokens(); - } - - protected override void SetScissorsImpl(ReadOnlySpan scissors) - { - Span> dxScissors = scissors.Length <= 8 ? stackalloc Box2D[scissors.Length] : new Box2D[scissors.Length]; - - for (int i = 0; i < scissors.Length; i++) - { - Scissor scissor = scissors[i]; - - dxScissors[i] = new(new(scissor.X, scissor.Y), new((int)(scissor.X + scissor.Width), (int)(scissor.Y + scissor.Height))); - } - - GraphicsCommandList4.RSSetScissorRects((uint)dxScissors.Length, ref dxScissors[0]); - } - - protected override void SetViewportsImpl(ReadOnlySpan viewports) - { - Span dxViewports = viewports.Length <= 8 ? stackalloc DxViewport[viewports.Length] : new DxViewport[viewports.Length]; - - for (int i = 0; i < viewports.Length; i++) - { - Viewport viewport = viewports[i]; - - dxViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); - } - - GraphicsCommandList4.RSSetViewports((uint)dxViewports.Length, ref dxViewports[0]); - } - - protected override void SetPipelineImpl(GraphicsPipeline pipeline) - { - DXGraphicsPipeline dxPipeline = pipeline.DirectX12(); - - GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); - GraphicsCommandList4.SetGraphicsRootSignature(dxPipeline.RootSignature); - - GraphicsCommandList4.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).PrimitiveTopology); - } - - protected override void SetPipelineImpl(ComputePipeline pipeline) - { - DXComputePipeline dxPipeline = pipeline.DirectX12(); - - GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); - GraphicsCommandList4.SetComputeRootSignature(dxPipeline.RootSignature); - } - - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) - { - DXMeshShadingPipeline dxPipeline = pipeline.DirectX12(); - - GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); - GraphicsCommandList4.SetGraphicsRootSignature(dxPipeline.RootSignature); - } - - protected override void SetStencilReferenceImpl(uint reference) - { - GraphicsCommandList4.OMSetStencilRef(reference); - } - - protected override void SetBlendConstantImpl(Vector4 blendConstant) - { - float* blendFactor = stackalloc float[4] - { - blendConstant.X, - blendConstant.Y, - blendConstant.Z, - blendConstant.W - }; - - GraphicsCommandList4.OMSetBlendFactor(blendFactor); - } - - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) - { - VertexBufferView view = new() - { - BufferLocation = buffer.DirectX12().GPUVirtualAddress + offsetInBytes, - SizeInBytes = buffer.Desc.SizeInBytes - offsetInBytes, - StrideInBytes = pipeline.Desc.InputLayouts[index].StrideInBytes - }; - - GraphicsCommandList4.IASetVertexBuffers(index, 1, &view); - } - - protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format) - { - IndexBufferView view = new() - { - BufferLocation = buffer.DirectX12().GPUVirtualAddress + offsetInBytes, - SizeInBytes = buffer.Desc.SizeInBytes - offsetInBytes, - Format = DXFormats.DirectX12(format) - }; - - GraphicsCommandList4.IASetIndexBuffer(&view); - } - - protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) - { - if (cbvSrvUavTable is null || samplerTable is null) - { - return; - } - - resourceTable.DirectX12().Bind(this, cbvSrvUavTable, samplerTable, pipeline is GraphicsPipeline or MeshShadingPipeline); - } - - protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) - { - GraphicsCommandList4.DrawInstanced(vertexCount, instanceCount, firstVertex, firstInstance); - } - - protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - GraphicsCommandList4.ExecuteIndirect(Context.DrawSignature, drawCount, indirectBuffer.DirectX12().Resource, offsetInBytes, (ID3D12Resource*)null, 0); - } - - protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) - { - GraphicsCommandList4.DrawIndexedInstanced(indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); - } - - protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - GraphicsCommandList4.ExecuteIndirect(Context.DrawIndexedSignature, drawCount, indirectBuffer.DirectX12().Resource, offsetInBytes, (ID3D12Resource*)null, 0); - } - - protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) - { - GraphicsCommandList4.Dispatch(groupCountX, groupCountY, groupCountZ); - } - - protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) - { - GraphicsCommandList4.ExecuteIndirect(Context.DispatchSignature, 1, indirectBuffer.DirectX12().Resource, offsetInBytes, (ID3D12Resource*)null, 0); - } - - protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) - { - GraphicsCommandList6?.DispatchMesh(groupCountX, groupCountY, groupCountZ); - } - - protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) - { - GraphicsCommandList6?.ExecuteIndirect(Context.DispatchMeshSignature, dispatchCount, indirectBuffer.DirectX12().Resource, offsetInBytes, (ID3D12Resource*)null, 0); - } - - protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) - { - GraphicsCommandList4.BeginQuery(queryHeap.DirectX12().QueryHeap, DXFormats.DirectX12(queryHeap.Desc.Type).Type, index); - } - - protected override void EndQueryImpl(QueryHeap queryHeap, uint index) - { - DXQueryHeap dxQueryHeap = queryHeap.DirectX12(); - - GraphicsCommandList4.EndQuery(dxQueryHeap.QueryHeap, DXFormats.DirectX12(dxQueryHeap.Desc.Type).Type, index); - - GraphicsCommandList4.ResolveQueryData(dxQueryHeap.QueryHeap, DXFormats.DirectX12(dxQueryHeap.Desc.Type).Type, index, 1, dxQueryHeap.Buffer.Resource, sizeof(ulong) * index); - } - - protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) - { - DXQueryHeap dxQueryHeap = queryHeap.DirectX12(); - - GraphicsCommandList4.EndQuery(dxQueryHeap.QueryHeap, DxQueryType.Timestamp, index); - - GraphicsCommandList4.ResolveQueryData(dxQueryHeap.QueryHeap, DxQueryType.Timestamp, index, 1, dxQueryHeap.Buffer.Resource, sizeof(ulong) * index); - } - - protected override void BeginDebugEventImpl(string label) - { - using ZenithMarshal.Scope scope = new(); - - uint size = PixHelpers.CalculateEventSize(label); - - ulong* buffer = (ulong*)ZenithMarshal.Allocate(scope, size); - - PixHelpers.FormatEventToBuffer(buffer, PixHelpers.Event, 0, label); - - GraphicsCommandList4.BeginEvent(PixHelpers.Version, buffer, size); - } - - protected override void EndDebugEventImpl() - { - GraphicsCommandList4.EndEvent(); - } - - protected override void InsertDebugMarkerImpl(string label) - { - using ZenithMarshal.Scope scope = new(); - - uint size = PixHelpers.CalculateEventSize(label); - - ulong* buffer = (ulong*)ZenithMarshal.Allocate(scope, size); - - PixHelpers.FormatEventToBuffer(buffer, PixHelpers.Marker, 0, label); - - GraphicsCommandList4.SetMarker(PixHelpers.Version, buffer, size); - } - - protected override void BeginImpl() - { - if (cbvSrvUavTable is null || samplerTable is null) - { - return; - } - - ComPtr[] descriptorHeaps = [cbvSrvUavTable.Heap, samplerTable.Heap]; - - fixed (ID3D12DescriptorHeap** ppDescriptorHeaps = descriptorHeaps[0]) - { - GraphicsCommandList4.SetDescriptorHeaps((uint)descriptorHeaps.Length, ppDescriptorHeaps); - } - } - - protected override void EndImpl() - { - GraphicsCommandList4.Close().Success(); - } - - protected override void ResetImpl() - { - ReleaseRenderPassTokens(); - - cbvSrvUavTable?.Reset(); - samplerTable?.Reset(); - - CommandAllocator.Reset().Success(); - GraphicsCommandList4.Reset(CommandAllocator, (ID3D12PipelineState*)null).Success(); - } - - protected override void SetResourceName(string name) - { - CommandList.SetName(name).Success(); - } - - protected override void Destroy() - { - ReleaseRenderPassTokens(); - - base.Destroy(); - - GraphicsCommandList6?.Dispose(); - GraphicsCommandList4.Dispose(); - CommandList.Dispose(); - CommandAllocator.Dispose(); - - samplerTable?.Dispose(); - cbvSrvUavTable?.Dispose(); - } - - private void ReleaseRenderPassTokens() - { - foreach (DXDescriptorToken token in currentRenderPassTokens) - { - token.Dispose(); - } - - currentRenderPassTokens = []; - } -} diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs deleted file mode 100644 index a8029f29..00000000 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr queue) : CommandQueue(context, type) -{ - private readonly DXFence fence = new(context); - - protected override CommandBuffer CreateCommandBuffer() - { - return new DXCommandBuffer(context, this); - } - - protected override void WaitIdleImpl() - { - fence.Wait(queue); - } - - protected override void SubmitImpl(CommandBuffer commandBuffer) - { - queue.ExecuteCommandLists(1, commandBuffer.DirectX12().CommandList.GetAddressOf()); - } - - protected override void SetResourceName(string name) - { - queue.SetName(name).Success(); - } - - protected override void Destroy() - { - base.Destroy(); - - fence.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs deleted file mode 100644 index 40834ced..00000000 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXComputePipeline : ComputePipeline -{ - public ComPtr RootSignature; - - public ComPtr PipelineState; - - public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - ComputePipelineStateDesc computePipelineStateDesc = new() - { - PRootSignature = RootSignature = desc.ResourceBindings.RootSignature(context), - CS = desc.Compute.DirectX12().GetShaderBytecode(scope) - }; - - context.Device.CreateComputePipelineState(&computePipelineStateDesc, out PipelineState).Success(); - } - - protected override void SetResourceName(string name) - { - PipelineState.SetName(name).Success(); - } - - protected override void Destroy() - { - PipelineState.Dispose(); - RootSignature.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs b/sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs deleted file mode 100644 index 34bee9e3..00000000 --- a/sources/Zenith.NET.DirectX12/DXDescriptorAllocator.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal class DXDescriptorAllocator(DXGraphicsContext context, DescriptorHeapType type) : GraphicsResource(context) -{ - private readonly Lock @lock = new(); - private readonly List available = []; - - public DXDescriptorToken Allocate(uint length) - { - using Lock.Scope _ = @lock.EnterScope(); - - CpuDescriptorHandle handle = default; - if (available.FirstOrDefault(item => item.TryAllocate(length, out handle)) is not DXDescriptorPool pool && (pool = new(context, type)).TryAllocate(length, out handle)) - { - available.Add(pool); - } - - return new() { Pool = pool, Handle = handle, Length = length }; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - foreach (DXDescriptorPool pool in available) - { - pool.Dispose(); - } - available.Clear(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorPool.cs b/sources/Zenith.NET.DirectX12/DXDescriptorPool.cs deleted file mode 100644 index d30ea79e..00000000 --- a/sources/Zenith.NET.DirectX12/DXDescriptorPool.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXDescriptorPool : GraphicsResource -{ - private const uint DescriptorCount = 512; - - private readonly bool[] slots = new bool[DescriptorCount]; - - public ComPtr Heap; - - public DXDescriptorPool(DXGraphicsContext context, DescriptorHeapType type) : base(context) - { - DescriptorHeapDesc desc = new() - { - Type = type, - NumDescriptors = DescriptorCount - }; - - context.Device.CreateDescriptorHeap(&desc, out Heap).Success(); - - DescriptorSize = context.Device.GetDescriptorHandleIncrementSize(type); - } - - public uint DescriptorSize { get; } - - public bool TryAllocate(uint length, out CpuDescriptorHandle handle) - { - handle = default; - - for (uint i = 0; i <= DescriptorCount - length; i++) - { - bool available = true; - - for (uint j = 0; j < length; j++) - { - if (slots[i + j]) - { - available = false; - - break; - } - } - - if (available) - { - for (uint j = 0; j < length; j++) - { - slots[i + j] = true; - } - - handle = new(Heap.GetCPUDescriptorHandleForHeapStart().Ptr + (DescriptorSize * i)); - - return true; - } - } - - return false; - } - - public void Free(CpuDescriptorHandle handle, uint length) - { - for (uint i = (uint)((handle.Ptr - Heap.GetCPUDescriptorHandleForHeapStart().Ptr) / DescriptorSize); i < length; i++) - { - slots[i] = false; - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Heap.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorTable.cs b/sources/Zenith.NET.DirectX12/DXDescriptorTable.cs deleted file mode 100644 index 6cc156f1..00000000 --- a/sources/Zenith.NET.DirectX12/DXDescriptorTable.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXDescriptorTable : GraphicsResource -{ - public ComPtr Heap; - - private uint currentIndex; - - public DXDescriptorTable(DXGraphicsContext context, DescriptorHeapType type, uint count) : base(context) - { - DescriptorHeapDesc desc = new() - { - Type = Type = type, - NumDescriptors = count, - Flags = DescriptorHeapFlags.ShaderVisible - }; - - context.Device.CreateDescriptorHeap(&desc, out Heap).Success(); - - DescriptorSize = context.Device.GetDescriptorHandleIncrementSize(type); - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public DescriptorHeapType Type { get; } - - public uint DescriptorSize { get; } - - public GpuDescriptorHandle GpuCurrentHandle => new(Heap.GetGPUDescriptorHandleForHeapStart().Ptr + (DescriptorSize * currentIndex)); - - public void Write(DXDescriptorToken token) - { - Context.Device.CopyDescriptorsSimple(token.Length, new(Heap.GetCPUDescriptorHandleForHeapStart().Ptr + (DescriptorSize * currentIndex)), token.Handle, Type); - - currentIndex += token.Length; - } - - public void Reset() - { - currentIndex = 0; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Heap.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs deleted file mode 100644 index 75dad3a5..00000000 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal record struct DXDescriptorToken : IDisposable -{ - public DXDescriptorPool Pool; - - public CpuDescriptorHandle Handle; - - public uint Length; - - public readonly CpuDescriptorHandle this[uint index] => index >= Length ? default : new(Handle.Ptr + (Pool.DescriptorSize * index)); - - public readonly void Dispose() - { - if (Length is 0) - { - return; - } - - Pool.Free(Handle, Length); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXFence.cs b/sources/Zenith.NET.DirectX12/DXFence.cs deleted file mode 100644 index 83122669..00000000 --- a/sources/Zenith.NET.DirectX12/DXFence.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXFence : GraphicsResource -{ - private readonly ManualResetEvent @event = new(false); - - public ComPtr Fence; - - private ulong currentFenceValue; - - public DXFence(DXGraphicsContext context) : base(context) - { - context.Device.CreateFence(0, FenceFlags.None, out Fence).Success(); - } - - public void Wait(ComPtr queue) - { - currentFenceValue++; - - queue.Signal(Fence, currentFenceValue).Success(); - - if (Fence.GetCompletedValue() < currentFenceValue) - { - Fence.SetEventOnCompletion(currentFenceValue, (void*)@event.SafeWaitHandle.DangerousGetHandle()).Success(); - - @event.WaitOne(); - @event.Reset(); - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Fence.Dispose(); - - @event.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs deleted file mode 100644 index 9b891a7e..00000000 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ /dev/null @@ -1,622 +0,0 @@ -using System.Numerics; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; -using Silk.NET.Maths; - -namespace Zenith.NET.DirectX12; - -internal static unsafe class DXFormats -{ - public static (ResourceFlags Flags, ResourceStates States, HeapType Type) DirectX12(BufferUsageFlags bufferUsageFlags) - { - ResourceFlags flags = ResourceFlags.None; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.AccelerationStructure) || bufferUsageFlags.HasFlag(BufferUsageFlags.UnorderedAccess)) - { - flags |= ResourceFlags.AllowUnorderedAccess; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.AccelerationStructure)) - { - flags |= ResourceFlags.RaytracingAccelerationStructure; - } - } - - ResourceStates states = ResourceStates.Common; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Vertex) || bufferUsageFlags.HasFlag(BufferUsageFlags.Constant)) - { - states |= ResourceStates.VertexAndConstantBuffer; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Index)) - { - states |= ResourceStates.IndexBuffer; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Indirect)) - { - states |= ResourceStates.IndirectArgument; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.AccelerationStructure)) - { - states |= ResourceStates.RaytracingAccelerationStructure; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.ShaderResource)) - { - states |= ResourceStates.AllShaderResource; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.UnorderedAccess)) - { - states |= ResourceStates.UnorderedAccess; - } - - HeapType type = HeapType.Default; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead)) - { - states = ResourceStates.CopyDest; - - type = HeapType.Readback; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - states = ResourceStates.GenericRead; - - type = HeapType.Upload; - } - - return (flags, states, type); - } - - public static ResourceDimension DirectX12(TextureType textureType) - { - return textureType switch - { - TextureType.Texture1D or - TextureType.Texture1DArray => ResourceDimension.Texture1D, - - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ResourceDimension.Texture2D, - - TextureType.Texture3D => ResourceDimension.Texture3D, - - _ => ResourceDimension.Texture1D - }; - } - - public static Format DirectX12(PixelFormat pixelFormat) - { - return pixelFormat switch - { - PixelFormat.R8UNorm => Format.FormatR8Unorm, - PixelFormat.R8SNorm => Format.FormatR8SNorm, - PixelFormat.R8UInt => Format.FormatR8Uint, - PixelFormat.R8SInt => Format.FormatR8Sint, - - PixelFormat.R16UNorm => Format.FormatR16Unorm, - PixelFormat.R16SNorm => Format.FormatR16SNorm, - PixelFormat.R16UInt => Format.FormatR16Uint, - PixelFormat.R16SInt => Format.FormatR16Sint, - PixelFormat.R16Float => Format.FormatR16Float, - - PixelFormat.R32UInt => Format.FormatR32Uint, - PixelFormat.R32SInt => Format.FormatR32Sint, - PixelFormat.R32Float => Format.FormatR32Float, - - PixelFormat.R8G8UNorm => Format.FormatR8G8Unorm, - PixelFormat.R8G8SNorm => Format.FormatR8G8SNorm, - PixelFormat.R8G8UInt => Format.FormatR8G8Uint, - PixelFormat.R8G8SInt => Format.FormatR8G8Sint, - - PixelFormat.R16G16UNorm => Format.FormatR16G16Unorm, - PixelFormat.R16G16SNorm => Format.FormatR16G16SNorm, - PixelFormat.R16G16UInt => Format.FormatR16G16Uint, - PixelFormat.R16G16SInt => Format.FormatR16G16Sint, - PixelFormat.R16G16Float => Format.FormatR16G16Float, - - PixelFormat.R32G32UInt => Format.FormatR32G32Uint, - PixelFormat.R32G32SInt => Format.FormatR32G32Sint, - PixelFormat.R32G32Float => Format.FormatR32G32Float, - - PixelFormat.R32G32B32UInt => Format.FormatR32G32B32Uint, - PixelFormat.R32G32B32SInt => Format.FormatR32G32B32Sint, - PixelFormat.R32G32B32Float => Format.FormatR32G32B32Float, - - PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, - PixelFormat.R8G8B8A8SNorm => Format.FormatR8G8B8A8SNorm, - PixelFormat.R8G8B8A8UInt => Format.FormatR8G8B8A8Uint, - PixelFormat.R8G8B8A8SInt => Format.FormatR8G8B8A8Sint, - PixelFormat.R8G8B8A8SRgb => Format.FormatR8G8B8A8UnormSrgb, - - PixelFormat.R16G16B16A16UNorm => Format.FormatR16G16B16A16Unorm, - PixelFormat.R16G16B16A16SNorm => Format.FormatR16G16B16A16SNorm, - PixelFormat.R16G16B16A16UInt => Format.FormatR16G16B16A16Uint, - PixelFormat.R16G16B16A16SInt => Format.FormatR16G16B16A16Sint, - PixelFormat.R16G16B16A16Float => Format.FormatR16G16B16A16Float, - - PixelFormat.R32G32B32A32UInt => Format.FormatR32G32B32A32Uint, - PixelFormat.R32G32B32A32SInt => Format.FormatR32G32B32A32Sint, - PixelFormat.R32G32B32A32Float => Format.FormatR32G32B32A32Float, - - PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - PixelFormat.B8G8R8A8SRgb => Format.FormatB8G8R8A8UnormSrgb, - - PixelFormat.D16UNorm => Format.FormatD16Unorm, - PixelFormat.D24UNormS8UInt => Format.FormatD24UnormS8Uint, - PixelFormat.D32Float => Format.FormatD32Float, - PixelFormat.D32FloatS8UInt => Format.FormatD32FloatS8X24Uint, - - PixelFormat.BC4UNorm => Format.FormatBC4Unorm, - PixelFormat.BC4SNorm => Format.FormatBC4SNorm, - - PixelFormat.BC5UNorm => Format.FormatBC5Unorm, - PixelFormat.BC5SNorm => Format.FormatBC5SNorm, - - PixelFormat.BC6HUFloat => Format.FormatBC6HUF16, - PixelFormat.BC6HSFloat => Format.FormatBC6HSF16, - - PixelFormat.BC7UNorm => Format.FormatBC7Unorm, - PixelFormat.BC7SRgb => Format.FormatBC7UnormSrgb, - - _ => Format.FormatUnknown - }; - } - - public static SampleDesc DirectX12(SampleCount sampleCount) - { - return sampleCount switch - { - SampleCount.Count1 => new() { Count = 1 }, - SampleCount.Count2 => new() { Count = 2 }, - SampleCount.Count4 => new() { Count = 4 }, - SampleCount.Count8 => new() { Count = 8 }, - SampleCount.Count16 => new() { Count = 16 }, - SampleCount.Count32 => new() { Count = 32 }, - _ => new() { Count = 1 } - }; - } - - public static (ResourceFlags Flags, ResourceStates States) DirectX12(TextureUsageFlags textureUsageFlags) - { - ResourceFlags flags = ResourceFlags.None; - - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget)) - { - flags |= ResourceFlags.AllowRenderTarget; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) - { - flags |= ResourceFlags.AllowDepthStencil; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.UnorderedAccess)) - { - flags |= ResourceFlags.AllowUnorderedAccess; - } - - ResourceStates states = ResourceStates.Common; - - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget)) - { - states |= ResourceStates.RenderTarget; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) - { - states |= ResourceStates.DepthWrite; - } - - return (flags, states); - } - - public static (DxFilter Filter, DxComparisonFunc ComparisonFunc) DirectX12(Filter filter, ComparisonFunc comparisonFunc) - { - bool isComparison = comparisonFunc is not ComparisonFunc.Never and not ComparisonFunc.Always; - - return - ( - filter switch - { - Filter.MinPointMagPointMipPoint => isComparison ? DxFilter.ComparisonMinMagMipPoint : DxFilter.MinMagMipPoint, - Filter.MinPointMagPointMipLinear => isComparison ? DxFilter.ComparisonMinMagPointMipLinear : DxFilter.MinMagPointMipLinear, - Filter.MinPointMagLinearMipPoint => isComparison ? DxFilter.ComparisonMinPointMagLinearMipPoint : DxFilter.MinPointMagLinearMipPoint, - Filter.MinPointMagLinearMipLinear => isComparison ? DxFilter.ComparisonMinPointMagMipLinear : DxFilter.MinPointMagMipLinear, - Filter.MinLinearMagPointMipPoint => isComparison ? DxFilter.ComparisonMinLinearMagMipPoint : DxFilter.MinLinearMagMipPoint, - Filter.MinLinearMagPointMipLinear => isComparison ? DxFilter.ComparisonMinLinearMagPointMipLinear : DxFilter.MinLinearMagPointMipLinear, - Filter.MinLinearMagLinearMipPoint => isComparison ? DxFilter.ComparisonMinMagLinearMipPoint : DxFilter.MinMagLinearMipPoint, - Filter.MinLinearMagLinearMipLinear => isComparison ? DxFilter.ComparisonMinMagMipLinear : DxFilter.MinMagMipLinear, - Filter.Anisotropic => isComparison ? DxFilter.ComparisonAnisotropic : DxFilter.Anisotropic, - _ => DxFilter.MinMagMipPoint - }, - comparisonFunc switch - { - ComparisonFunc.Never => DxComparisonFunc.Never, - ComparisonFunc.Less => DxComparisonFunc.Less, - ComparisonFunc.Equal => DxComparisonFunc.Equal, - ComparisonFunc.LessEqual => DxComparisonFunc.LessEqual, - ComparisonFunc.Greater => DxComparisonFunc.Greater, - ComparisonFunc.NotEqual => DxComparisonFunc.NotEqual, - ComparisonFunc.GreaterEqual => DxComparisonFunc.GreaterEqual, - ComparisonFunc.Always => DxComparisonFunc.Always, - _ => DxComparisonFunc.None - } - ); - } - - public static TextureAddressMode DirectX12(AddressMode addressMode) - { - return addressMode switch - { - AddressMode.Wrap => TextureAddressMode.Wrap, - AddressMode.Mirror => TextureAddressMode.Mirror, - AddressMode.Clamp => TextureAddressMode.Clamp, - AddressMode.Border => TextureAddressMode.Border, - _ => TextureAddressMode.Wrap - }; - } - - public static DescriptorRangeType DirectX12(ResourceType resourceType) - { - return resourceType switch - { - ResourceType.ConstantBuffer => DescriptorRangeType.Cbv, - - ResourceType.StructuredBuffer or - ResourceType.Texture or - ResourceType.AccelerationStructure => DescriptorRangeType.Srv, - - ResourceType.StructuredBufferReadWrite or - ResourceType.TextureReadWrite => DescriptorRangeType.Uav, - - ResourceType.Sampler => DescriptorRangeType.Sampler, - - _ => DescriptorRangeType.Srv - }; - } - - public static (PrimitiveTopologyType PrimitiveTopologyType, D3DPrimitiveTopology PrimitiveTopology) DirectX12(PrimitiveTopology primitiveTopology) - { - return - ( - primitiveTopology switch - { - PrimitiveTopology.PointList => PrimitiveTopologyType.Point, - - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, - - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, - - _ => PrimitiveTopologyType.Undefined - }, - primitiveTopology switch - { - PrimitiveTopology.PointList => D3DPrimitiveTopology.D3DPrimitiveTopologyPointlist, - PrimitiveTopology.LineList => D3DPrimitiveTopology.D3DPrimitiveTopologyLinelist, - PrimitiveTopology.LineStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyLinestrip, - PrimitiveTopology.TriangleList => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglelist, - PrimitiveTopology.TriangleStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglestrip, - _ => D3DPrimitiveTopology.D3DPrimitiveTopologyUndefined - } - ); - } - - public static DxFillMode DirectX12(FillMode fillMode) - { - return fillMode switch - { - FillMode.Solid => DxFillMode.Solid, - FillMode.Wireframe => DxFillMode.Wireframe, - _ => DxFillMode.None - }; - } - - public static DxCullMode DirectX12(CullMode cullMode) - { - return cullMode switch - { - CullMode.None => DxCullMode.None, - CullMode.Front => DxCullMode.Front, - CullMode.Back => DxCullMode.Back, - _ => DxCullMode.None - }; - } - - public static DxStencilOp DirectX12(StencilOp stencilOp) - { - return stencilOp switch - { - StencilOp.Keep => DxStencilOp.Keep, - StencilOp.Zero => DxStencilOp.Zero, - StencilOp.Replace => DxStencilOp.Replace, - StencilOp.IncrementAndClamp => DxStencilOp.IncrSat, - StencilOp.DecrementAndClamp => DxStencilOp.DecrSat, - StencilOp.Invert => DxStencilOp.Invert, - StencilOp.IncrementAndWrap => DxStencilOp.Incr, - StencilOp.DecrementAndWrap => DxStencilOp.Decr, - _ => DxStencilOp.Keep - }; - } - - public static DxBlend DirectX12(Blend blend) - { - return blend switch - { - Blend.Zero => DxBlend.Zero, - Blend.One => DxBlend.One, - Blend.SrcAlpha => DxBlend.SrcAlpha, - Blend.InverseSrcAlpha => DxBlend.InvSrcAlpha, - Blend.DestAlpha => DxBlend.DestAlpha, - Blend.InverseDestAlpha => DxBlend.InvDestAlpha, - Blend.SrcColor => DxBlend.SrcColor, - Blend.InverseSrcColor => DxBlend.InvSrcColor, - Blend.DestColor => DxBlend.DestColor, - Blend.InverseDestColor => DxBlend.InvDestColor, - Blend.BlendFactor => DxBlend.BlendFactor, - Blend.InverseBlendFactor => DxBlend.InvBlendFactor, - _ => DxBlend.Zero - }; - } - - public static DxBlendOp DirectX12(BlendOp blendOp) - { - return blendOp switch - { - BlendOp.Add => DxBlendOp.Add, - BlendOp.Subtract => DxBlendOp.Subtract, - BlendOp.ReverseSubtract => DxBlendOp.RevSubtract, - BlendOp.Min => DxBlendOp.Min, - BlendOp.Max => DxBlendOp.Max, - _ => DxBlendOp.Add - }; - } - - public static ColorWriteEnable DirectX12(ColorWriteMask writeMask) - { - ColorWriteEnable result = ColorWriteEnable.None; - - if (writeMask.HasFlag(ColorWriteMask.Red)) - { - result |= ColorWriteEnable.Red; - } - - if (writeMask.HasFlag(ColorWriteMask.Green)) - { - result |= ColorWriteEnable.Green; - } - - if (writeMask.HasFlag(ColorWriteMask.Blue)) - { - result |= ColorWriteEnable.Blue; - } - - if (writeMask.HasFlag(ColorWriteMask.Alpha)) - { - result |= ColorWriteEnable.Alpha; - } - - return result; - } - - public static Format DirectX12(ElementFormat elementFormat) - { - return elementFormat switch - { - ElementFormat.UByte1 => Format.FormatR8Uint, - ElementFormat.UByte2 => Format.FormatR8G8Uint, - ElementFormat.UByte4 => Format.FormatR8G8B8A8Uint, - - ElementFormat.Byte1 => Format.FormatR8Sint, - ElementFormat.Byte2 => Format.FormatR8G8Sint, - ElementFormat.Byte4 => Format.FormatR8G8B8A8Sint, - - ElementFormat.UByte1Normalized => Format.FormatR8Unorm, - ElementFormat.UByte2Normalized => Format.FormatR8G8Unorm, - ElementFormat.UByte4Normalized => Format.FormatR8G8B8A8Unorm, - - ElementFormat.Byte1Normalized => Format.FormatR8SNorm, - ElementFormat.Byte2Normalized => Format.FormatR8G8SNorm, - ElementFormat.Byte4Normalized => Format.FormatR8G8B8A8SNorm, - - ElementFormat.UShort1 => Format.FormatR16Uint, - ElementFormat.UShort2 => Format.FormatR16G16Uint, - ElementFormat.UShort4 => Format.FormatR16G16B16A16Uint, - - ElementFormat.Short1 => Format.FormatR16Sint, - ElementFormat.Short2 => Format.FormatR16G16Sint, - ElementFormat.Short4 => Format.FormatR16G16B16A16Sint, - - ElementFormat.UShort1Normalized => Format.FormatR16Unorm, - ElementFormat.UShort2Normalized => Format.FormatR16G16Unorm, - ElementFormat.UShort4Normalized => Format.FormatR16G16B16A16Unorm, - - ElementFormat.Short1Normalized => Format.FormatR16SNorm, - ElementFormat.Short2Normalized => Format.FormatR16G16SNorm, - ElementFormat.Short4Normalized => Format.FormatR16G16B16A16SNorm, - - ElementFormat.Half1 => Format.FormatR16Float, - ElementFormat.Half2 => Format.FormatR16G16Float, - ElementFormat.Half4 => Format.FormatR16G16B16A16Float, - - ElementFormat.Float1 => Format.FormatR32Float, - ElementFormat.Float2 => Format.FormatR32G32Float, - ElementFormat.Float3 => Format.FormatR32G32B32Float, - ElementFormat.Float4 => Format.FormatR32G32B32A32Float, - - ElementFormat.UInt1 => Format.FormatR32Uint, - ElementFormat.UInt2 => Format.FormatR32G32Uint, - ElementFormat.UInt3 => Format.FormatR32G32B32Uint, - ElementFormat.UInt4 => Format.FormatR32G32B32A32Uint, - - ElementFormat.Int1 => Format.FormatR32Sint, - ElementFormat.Int2 => Format.FormatR32G32Sint, - ElementFormat.Int3 => Format.FormatR32G32B32Sint, - ElementFormat.Int4 => Format.FormatR32G32B32A32Sint, - - _ => Format.FormatUnknown - }; - } - - public static CommandListType DirectX12(CommandQueueType commandQueueType) - { - return commandQueueType switch - { - CommandQueueType.Graphics => CommandListType.Direct, - CommandQueueType.Compute => CommandListType.Compute, - CommandQueueType.Copy => CommandListType.Copy, - _ => CommandListType.None - }; - } - - public static Format DirectX12(IndexFormat indexFormat) - { - return indexFormat switch - { - IndexFormat.UInt16 => Format.FormatR16Uint, - IndexFormat.UInt32 => Format.FormatR32Uint, - _ => Format.FormatUnknown - }; - } - - public static (QueryHeapType HeapType, DxQueryType Type) DirectX12(QueryType queryType) - { - return - ( - queryType switch - { - QueryType.Occlusion or - QueryType.BinaryOcclusion => QueryHeapType.Occlusion, - - QueryType.Timestamp => QueryHeapType.Timestamp, - - _ => QueryHeapType.Occlusion - }, - queryType switch - { - QueryType.Occlusion => DxQueryType.Occlusion, - QueryType.BinaryOcclusion => DxQueryType.BinaryOcclusion, - QueryType.Timestamp => DxQueryType.Timestamp, - _ => DxQueryType.Occlusion - } - ); - } - - public static Matrix3X4 DirectX12(Matrix4x4 matrix4x4) - { - Matrix3X4 result; - - float* pResult = (float*)&result; - - pResult[0] = matrix4x4.M11; - pResult[1] = matrix4x4.M21; - pResult[2] = matrix4x4.M31; - pResult[3] = matrix4x4.M41; - - pResult[4] = matrix4x4.M12; - pResult[5] = matrix4x4.M22; - pResult[6] = matrix4x4.M32; - pResult[7] = matrix4x4.M42; - - pResult[8] = matrix4x4.M13; - pResult[9] = matrix4x4.M23; - pResult[10] = matrix4x4.M33; - pResult[11] = matrix4x4.M43; - - return result; - } - - public static RaytracingGeometryType DirectX12(RayTracingGeometryType rayTracingGeometryType) - { - return rayTracingGeometryType switch - { - RayTracingGeometryType.Triangles => RaytracingGeometryType.Triangles, - RayTracingGeometryType.AABBs => RaytracingGeometryType.ProceduralPrimitiveAabbs, - _ => RaytracingGeometryType.Triangles - }; - } - - public static RaytracingGeometryFlags DirectX12(RayTracingGeometryFlags rayTracingGeometryFlags) - { - RaytracingGeometryFlags result = RaytracingGeometryFlags.None; - - if (rayTracingGeometryFlags.HasFlag(RayTracingGeometryFlags.Opaque)) - { - result |= RaytracingGeometryFlags.Opaque; - } - - return result; - } - - public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) - { - RaytracingAccelerationStructureBuildFlags result = RaytracingAccelerationStructureBuildFlags.None; - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) - { - result |= RaytracingAccelerationStructureBuildFlags.AllowUpdate; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) - { - result |= RaytracingAccelerationStructureBuildFlags.AllowCompaction; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) - { - result |= RaytracingAccelerationStructureBuildFlags.PreferFastTrace; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) - { - result |= RaytracingAccelerationStructureBuildFlags.PreferFastBuild; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) - { - result |= RaytracingAccelerationStructureBuildFlags.MinimizeMemory; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PerformUpdate)) - { - result |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; - } - - return result; - } - - public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) - { - RaytracingInstanceFlags result = RaytracingInstanceFlags.None; - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleCullDisable)) - { - result |= RaytracingInstanceFlags.TriangleCullDisable; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleFrontCounterClockwise)) - { - result |= RaytracingInstanceFlags.TriangleFrontCounterclockwise; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) - { - result |= RaytracingInstanceFlags.ForceOpaque; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNoOpaque)) - { - result |= RaytracingInstanceFlags.ForceNonOpaque; - } - - return result; - } -} diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index a591de4b..0c51bf92 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -1,211 +1,74 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; +namespace Zenith.NET.DirectX12; -namespace Zenith.NET.DirectX12; - -internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(Backend.DirectX12, useValidationLayer) +internal class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.DirectX12, useValidationLayer) { - private const uint TextureDataPitchAlignment = 256; - - private const uint TextureDataPlacementAlignment = 512; - - public const uint SwapChainBufferCount = 3; - - public const uint Shader4ComponentMapping = 0x1688; - - public ComPtr Factory7; - - public ComPtr Adapter4; - - public ComPtr Device; - - public ComPtr? Device2; - - public ComPtr? Device5; - - public ComPtr? InfoQueue1; - - public ComPtr GraphicsQueue; - - public ComPtr ComputeQueue; - - public ComPtr CopyQueue; - - public ComPtr DrawSignature; - - public ComPtr DrawIndexedSignature; - - public ComPtr DispatchSignature; - - public ComPtr DispatchMeshSignature; - - public DXGI DXGI { get; } = DXGI.GetApi(null); - - public D3D12 D3D12 { get; } = D3D12.GetApi(); - - public DXDescriptorAllocator RtvAllocator => field ??= new(this, DescriptorHeapType.Rtv); - - public DXDescriptorAllocator DsvAllocator => field ??= new(this, DescriptorHeapType.Dsv); - - public DXDescriptorAllocator CbvSrvUavAllocator => field ??= new(this, DescriptorHeapType.CbvSrvUav); - - public DXDescriptorAllocator SamplerAllocator => field ??= new(this, DescriptorHeapType.Sampler); + public override nint GetNativeObject(NativeObjectType type) + { + throw new NotImplementedException(); + } protected override void Initialize(bool useValidationLayer, out Capabilities capabilities, - out CommandQueue graphics, - out CommandQueue compute, - out CommandQueue copy, + out CommandQueue graphicsQueue, + out CommandQueue computeQueue, + out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - if (useValidationLayer && D3D12.GetDebugInterface(out ComPtr debug).IsSuccess()) - { - debug.EnableDebugLayer(); - - debug.Dispose(); - } - - DXGI.CreateDXGIFactory2(useValidationLayer ? DXGI.CreateFactoryDebug : 0u, out Factory7).Success(); - - Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, out Adapter4).Success(); - - D3D12.CreateDevice(Adapter4, D3DFeatureLevel.Level120, out Device).Success(); - - if (Device.QueryInterface(out ComPtr device2).IsSuccess()) - { - Device2 = device2; - } - - if (Device.QueryInterface(out ComPtr device5).IsSuccess()) - { - Device5 = device5; - } - - if (Device.QueryInterface(out ComPtr infoQueue1).IsSuccess()) - { - InfoQueue1 = infoQueue1; - } - - CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; - Device.CreateCommandQueue(&commandQueueDesc, out GraphicsQueue).Success(); - - commandQueueDesc.Type = CommandListType.Compute; - Device.CreateCommandQueue(&commandQueueDesc, out ComputeQueue).Success(); - - commandQueueDesc.Type = CommandListType.Copy; - Device.CreateCommandQueue(&commandQueueDesc, out CopyQueue).Success(); - - IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; - CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawSignature).Success(); - - indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; - commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawIndexedSignature).Success(); - - indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; - commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchSignature).Success(); - - indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; - commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchMeshSignature).Success(); - - capabilities = new DXCapabilities(this); - graphics = new DXCommandQueue(this, CommandQueueType.Graphics, GraphicsQueue); - compute = new DXCommandQueue(this, CommandQueueType.Compute, ComputeQueue); - copy = new DXCommandQueue(this, CommandQueueType.Copy, CopyQueue); - validationLayer = useValidationLayer ? new DXValidationLayer(this) : null; + throw new NotImplementedException(); } protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) { - return new DXSwapChain(this, desc); - } - - protected override Shader CreateShaderImpl(ShaderDesc desc) - { - return new DXShader(this, desc); + throw new NotImplementedException(); } protected override Buffer CreateBufferImpl(BufferDesc desc) { - return new DXBuffer(this, desc); + throw new NotImplementedException(); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) { - return new DXBufferView(this, desc); + throw new NotImplementedException(); } protected override Texture CreateTextureImpl(TextureDesc desc) { - return new DXTexture(this, desc); + throw new NotImplementedException(); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { - return new DXTextureView(this, desc); + throw new NotImplementedException(); } protected override Sampler CreateSamplerImpl(SamplerDesc desc) { - return new DXSampler(this, desc); + throw new NotImplementedException(); } - protected override ResourceTable CreateResourceTableImpl(ResourceTableDesc desc) + protected override Shader CreateShaderImpl(ShaderDesc desc) { - return new DXResourceTable(this, desc); + throw new NotImplementedException(); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) { - return new DXGraphicsPipeline(this, desc); + throw new NotImplementedException(); } protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) { - return new DXComputePipeline(this, desc); + throw new NotImplementedException(); } protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) { - return new DXMeshShadingPipeline(this, desc); + throw new NotImplementedException(); } protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { - return new DXQueryHeap(this, desc); - } - - protected override void Destroy() - { - base.Destroy(); - - SamplerAllocator.Dispose(); - CbvSrvUavAllocator.Dispose(); - DsvAllocator.Dispose(); - RtvAllocator.Dispose(); - - DispatchMeshSignature.Dispose(); - DispatchSignature.Dispose(); - DrawIndexedSignature.Dispose(); - DrawSignature.Dispose(); - - CopyQueue.Dispose(); - ComputeQueue.Dispose(); - GraphicsQueue.Dispose(); - - InfoQueue1?.Dispose(); - Device5?.Dispose(); - Device2?.Dispose(); - - Device.Dispose(); - Adapter4.Dispose(); - Factory7.Dispose(); - - D3D12.Dispose(); - DXGI.Dispose(); + throw new NotImplementedException(); } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs deleted file mode 100644 index 4baafb86..00000000 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXGraphicsPipeline : GraphicsPipeline -{ - public ComPtr RootSignature; - - public ComPtr PipelineState; - - public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() - { - PRootSignature = RootSignature = desc.ResourceBindings.RootSignature(context), - VS = desc.Vertex.DirectX12().GetShaderBytecode(scope), - PS = desc.Pixel.DirectX12().GetShaderBytecode(scope), - SampleMask = uint.MaxValue, - PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).PrimitiveTopologyType - }; - - // RenderStates - Output - { - BlendStateRenderTarget[] blendStateRenderTargets = - [ - desc.RenderStates.BlendState.RenderTarget0, - desc.RenderStates.BlendState.RenderTarget1, - desc.RenderStates.BlendState.RenderTarget2, - desc.RenderStates.BlendState.RenderTarget3, - desc.RenderStates.BlendState.RenderTarget4, - desc.RenderStates.BlendState.RenderTarget5, - desc.RenderStates.BlendState.RenderTarget6, - desc.RenderStates.BlendState.RenderTarget7 - ]; - - graphicsPipelineStateDesc.RasterizerState = new() - { - FillMode = DXFormats.DirectX12(desc.RenderStates.RasterizerState.FillMode), - CullMode = DXFormats.DirectX12(desc.RenderStates.RasterizerState.CullMode), - FrontCounterClockwise = desc.RenderStates.RasterizerState.FrontFace is FrontFace.CounterClockwise, - DepthBias = desc.RenderStates.RasterizerState.DepthBias, - DepthBiasClamp = desc.RenderStates.RasterizerState.DepthBiasClamp, - SlopeScaledDepthBias = desc.RenderStates.RasterizerState.SlopeScaledDepthBias, - DepthClipEnable = desc.RenderStates.RasterizerState.DepthClipEnable, - MultisampleEnable = desc.Output.SampleCount is not SampleCount.Count1, - AntialiasedLineEnable = true - }; - - graphicsPipelineStateDesc.DepthStencilState = new() - { - DepthEnable = desc.RenderStates.DepthStencilState.DepthEnable, - DepthWriteMask = desc.RenderStates.DepthStencilState.DepthWriteEnable ? DepthWriteMask.All : DepthWriteMask.Zero, - DepthFunc = DXFormats.DirectX12(default, desc.RenderStates.DepthStencilState.DepthFunc).ComparisonFunc, - StencilEnable = desc.RenderStates.DepthStencilState.StencilEnable, - StencilReadMask = desc.RenderStates.DepthStencilState.StencilReadMask, - StencilWriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - FrontFace = new() - { - StencilFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.FrontFace.StencilFailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.FrontFace.StencilDepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.FrontFace.StencilPassOp), - StencilFunc = DXFormats.DirectX12(default, desc.RenderStates.DepthStencilState.FrontFace.StencilFunc).ComparisonFunc - }, - BackFace = new() - { - StencilFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.BackFace.StencilFailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.BackFace.StencilDepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.BackFace.StencilPassOp), - StencilFunc = DXFormats.DirectX12(default, desc.RenderStates.DepthStencilState.BackFace.StencilFunc).ComparisonFunc - } - }; - - graphicsPipelineStateDesc.BlendState = new() - { - AlphaToCoverageEnable = desc.RenderStates.BlendState.AlphaToCoverageEnable, - IndependentBlendEnable = desc.RenderStates.BlendState.IndependentBlendEnable - }; - - for (int i = 0; i < blendStateRenderTargets.Length; i++) - { - graphicsPipelineStateDesc.BlendState.RenderTarget[i] = new() - { - BlendEnable = blendStateRenderTargets[i].BlendEnable, - SrcBlend = DXFormats.DirectX12(blendStateRenderTargets[i].SrcBlend), - DestBlend = DXFormats.DirectX12(blendStateRenderTargets[i].DestBlend), - BlendOp = DXFormats.DirectX12(blendStateRenderTargets[i].BlendOp), - SrcBlendAlpha = DXFormats.DirectX12(blendStateRenderTargets[i].SrcBlendAlpha), - DestBlendAlpha = DXFormats.DirectX12(blendStateRenderTargets[i].DestBlendAlpha), - BlendOpAlpha = DXFormats.DirectX12(blendStateRenderTargets[i].BlendOpAlpha), - RenderTargetWriteMask = (byte)DXFormats.DirectX12(blendStateRenderTargets[i].Flags) - }; - } - - graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.Output.ColorAttachments.Length; - - for (int i = 0; i < desc.Output.ColorAttachments.Length; i++) - { - graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.Output.ColorAttachments[i]); - } - - graphicsPipelineStateDesc.DSVFormat = desc.Output.DepthStencilAttachment.HasValue ? DXFormats.DirectX12(desc.Output.DepthStencilAttachment.Value) : Format.FormatUnknown; - - graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); - } - - // InputLayouts - { - List inputElementDescs = []; - for (int i = 0; i < desc.InputLayouts.Length; i++) - { - InputLayout inputLayout = desc.InputLayouts[i]; - - foreach (InputElement element in inputLayout.Elements) - { - inputElementDescs.Add(new() - { - SemanticName = (byte*)ZenithMarshal.StringToPointer(scope, element.Semantic.ToString().ToUpper(), StringEncoding.UTF8), - SemanticIndex = element.Index, - Format = DXFormats.DirectX12(element.Format), - InputSlot = (uint)i, - AlignedByteOffset = element.OffsetInBytes - }); - } - } - - graphicsPipelineStateDesc.InputLayout = new() - { - PInputElementDescs = (InputElementDesc*)ZenithMarshal.AllocateAndFill(scope, [.. inputElementDescs]), - NumElements = (uint)inputElementDescs.Count - }; - } - - context.Device.CreateGraphicsPipelineState(&graphicsPipelineStateDesc, out PipelineState).Success(); - } - - protected override void SetResourceName(string name) - { - PipelineState.SetName(name).Success(); - } - - protected override void Destroy() - { - PipelineState.Dispose(); - RootSignature.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs deleted file mode 100644 index 9c68ea23..00000000 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXHeap : GraphicsResource -{ - public ComPtr Heap; - - public DXHeap(DXGraphicsContext context, ResourceDesc resourceDesc, HeapType type, HeapFlags flags) : base(context) - { - ResourceAllocationInfo allocationInfo = context.Device.GetResourceAllocationInfo(0, 1, ref resourceDesc); - - HeapDesc desc = new() - { - SizeInBytes = allocationInfo.SizeInBytes, - Properties = new(type), - Alignment = allocationInfo.Alignment, - Flags = flags - }; - - context.Device.CreateHeap(&desc, out Heap).Success(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Heap.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs deleted file mode 100644 index 76139354..00000000 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ /dev/null @@ -1,144 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXMeshShadingPipeline : MeshShadingPipeline -{ - public ComPtr RootSignature; - - public ComPtr PipelineState; - - public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() - { - PRootSignature = RootSignature = desc.ResourceBindings.RootSignature(context), - SampleMask = uint.MaxValue - }; - - // RenderStates - Output - { - BlendStateRenderTarget[] blendStateRenderTargets = - [ - desc.RenderStates.BlendState.RenderTarget0, - desc.RenderStates.BlendState.RenderTarget1, - desc.RenderStates.BlendState.RenderTarget2, - desc.RenderStates.BlendState.RenderTarget3, - desc.RenderStates.BlendState.RenderTarget4, - desc.RenderStates.BlendState.RenderTarget5, - desc.RenderStates.BlendState.RenderTarget6, - desc.RenderStates.BlendState.RenderTarget7 - ]; - - graphicsPipelineStateDesc.RasterizerState = new() - { - FillMode = DXFormats.DirectX12(desc.RenderStates.RasterizerState.FillMode), - CullMode = DXFormats.DirectX12(desc.RenderStates.RasterizerState.CullMode), - FrontCounterClockwise = desc.RenderStates.RasterizerState.FrontFace is FrontFace.CounterClockwise, - DepthBias = desc.RenderStates.RasterizerState.DepthBias, - DepthBiasClamp = desc.RenderStates.RasterizerState.DepthBiasClamp, - SlopeScaledDepthBias = desc.RenderStates.RasterizerState.SlopeScaledDepthBias, - DepthClipEnable = desc.RenderStates.RasterizerState.DepthClipEnable, - MultisampleEnable = desc.Output.SampleCount is not SampleCount.Count1, - AntialiasedLineEnable = true - }; - - graphicsPipelineStateDesc.DepthStencilState = new() - { - DepthEnable = desc.RenderStates.DepthStencilState.DepthEnable, - DepthWriteMask = desc.RenderStates.DepthStencilState.DepthWriteEnable ? DepthWriteMask.All : DepthWriteMask.Zero, - DepthFunc = DXFormats.DirectX12(default, desc.RenderStates.DepthStencilState.DepthFunc).ComparisonFunc, - StencilEnable = desc.RenderStates.DepthStencilState.StencilEnable, - StencilReadMask = desc.RenderStates.DepthStencilState.StencilReadMask, - StencilWriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - FrontFace = new() - { - StencilFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.FrontFace.StencilFailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.FrontFace.StencilDepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.FrontFace.StencilPassOp), - StencilFunc = DXFormats.DirectX12(default, desc.RenderStates.DepthStencilState.FrontFace.StencilFunc).ComparisonFunc - }, - BackFace = new() - { - StencilFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.BackFace.StencilFailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.BackFace.StencilDepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderStates.DepthStencilState.BackFace.StencilPassOp), - StencilFunc = DXFormats.DirectX12(default, desc.RenderStates.DepthStencilState.BackFace.StencilFunc).ComparisonFunc - } - }; - - graphicsPipelineStateDesc.BlendState = new() - { - AlphaToCoverageEnable = desc.RenderStates.BlendState.AlphaToCoverageEnable, - IndependentBlendEnable = desc.RenderStates.BlendState.IndependentBlendEnable - }; - - for (int i = 0; i < blendStateRenderTargets.Length; i++) - { - graphicsPipelineStateDesc.BlendState.RenderTarget[i] = new() - { - BlendEnable = blendStateRenderTargets[i].BlendEnable, - SrcBlend = DXFormats.DirectX12(blendStateRenderTargets[i].SrcBlend), - DestBlend = DXFormats.DirectX12(blendStateRenderTargets[i].DestBlend), - BlendOp = DXFormats.DirectX12(blendStateRenderTargets[i].BlendOp), - SrcBlendAlpha = DXFormats.DirectX12(blendStateRenderTargets[i].SrcBlendAlpha), - DestBlendAlpha = DXFormats.DirectX12(blendStateRenderTargets[i].DestBlendAlpha), - BlendOpAlpha = DXFormats.DirectX12(blendStateRenderTargets[i].BlendOpAlpha), - RenderTargetWriteMask = (byte)DXFormats.DirectX12(blendStateRenderTargets[i].Flags) - }; - } - - graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.Output.ColorAttachments.Length; - - for (int i = 0; i < desc.Output.ColorAttachments.Length; i++) - { - graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.Output.ColorAttachments[i]); - } - - graphicsPipelineStateDesc.DSVFormat = desc.Output.DepthStencilAttachment.HasValue ? DXFormats.DirectX12(desc.Output.DepthStencilAttachment.Value) : Format.FormatUnknown; - - graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); - } - - // PrimitiveTopology - { - graphicsPipelineStateDesc.PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).PrimitiveTopologyType; - } - - PipelineStateStream2 pipelineStateStream2 = (PipelineStateStream2)graphicsPipelineStateDesc; - - // Amplification - Mesh - Pixel - { - if (desc.Amplification is not null) - { - pipelineStateStream2.AS.Data = desc.Amplification.DirectX12().GetShaderBytecode(scope); - } - - pipelineStateStream2.MS.Data = desc.Mesh.DirectX12().GetShaderBytecode(scope); - pipelineStateStream2.PS.Data = desc.Pixel.DirectX12().GetShaderBytecode(scope); - } - - PipelineStateStreamDesc pipelineStateStreamDesc = new() - { - SizeInBytes = (uint)sizeof(PipelineStateStream2), - PPipelineStateSubobjectStream = &pipelineStateStream2 - }; - - context.Device2?.CreatePipelineState(&pipelineStateStreamDesc, out PipelineState).Success(); - } - - protected override void SetResourceName(string name) - { - PipelineState.SetName(name).Success(); - } - - protected override void Destroy() - { - PipelineState.Dispose(); - RootSignature.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs deleted file mode 100644 index c49e6bf3..00000000 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXQueryHeap : QueryHeap -{ - public ComPtr QueryHeap; - - public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context, desc) - { - DxQueryHeapDesc queryHeapDesc = new() - { - Type = DXFormats.DirectX12(desc.Type).HeapType, - Count = desc.Count - }; - - context.Device.CreateQueryHeap(&queryHeapDesc, out QueryHeap).Success(); - - Buffer = new(context, new() - { - SizeInBytes = sizeof(ulong) * desc.Count, - StrideInBytes = sizeof(ulong), - Flags = BufferUsageFlags.MapRead - }); - } - - public DXBuffer Buffer { get; } - - protected override void GetResultsImpl(Span results, uint startIndex) - { - MappedMemory mappedMemory = Buffer.Map(); - - unsafe - { - new ReadOnlySpan((void*)(mappedMemory.Pointer + (nint)(sizeof(ulong) * startIndex)), results.Length).CopyTo(results); - } - - Buffer.Unmap(); - } - - protected override void SetResourceName(string name) - { - QueryHeap.SetName(name).Success(); - } - - protected override void Destroy() - { - Buffer.Dispose(); - - QueryHeap.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXResourceTable.cs b/sources/Zenith.NET.DirectX12/DXResourceTable.cs deleted file mode 100644 index d511c787..00000000 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal class DXResourceTable : ResourceTable -{ - private readonly DXDescriptorToken cbvSrvUavToken; - private readonly DXDescriptorToken samplerToken; - - public DXResourceTable(DXGraphicsContext context, ResourceTableDesc desc) : base(context, desc) - { - desc.Bindings.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); - - if (cbvSrvUavRanges.Length > 0) - { - cbvSrvUavToken = Context.CbvSrvUavAllocator.Allocate((uint)cbvSrvUavRanges.Sum(static item => item.NumDescriptors)); - } - - if (samplerRanges.Length > 0) - { - samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.NumDescriptors)); - } - - SrvTextureViews = new DXTextureView?[cbvSrvUavToken.Length]; - UavTextureViews = new DXTextureView?[cbvSrvUavToken.Length]; - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public DXTextureView?[] SrvTextureViews { get; } - - public DXTextureView?[] UavTextureViews { get; } - - public void Bind(DXCommandBuffer commandBuffer, DXDescriptorTable cbvSrvUavTable, DXDescriptorTable samplerTable, bool isGraphics) - { - uint offset = 0; - - if (cbvSrvUavToken.Length > 0) - { - if (isGraphics) - { - commandBuffer.GraphicsCommandList4.SetGraphicsRootDescriptorTable(offset++, cbvSrvUavTable.GpuCurrentHandle); - } - else - { - commandBuffer.GraphicsCommandList4.SetComputeRootDescriptorTable(offset++, cbvSrvUavTable.GpuCurrentHandle); - } - - cbvSrvUavTable.Write(cbvSrvUavToken); - } - - if (samplerToken.Length > 0) - { - if (isGraphics) - { - commandBuffer.GraphicsCommandList4.SetGraphicsRootDescriptorTable(offset++, samplerTable.GpuCurrentHandle); - } - else - { - commandBuffer.GraphicsCommandList4.SetComputeRootDescriptorTable(offset++, samplerTable.GpuCurrentHandle); - } - - samplerTable.Write(samplerToken); - } - } - - protected override void WriteImpl(uint binding, IBindableResource[] resources) - { - switch (Desc.Bindings[binding].Type) - { - case ResourceType.ConstantBuffer: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.CbvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().CbvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.StructuredBuffer: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.StructuredBufferReadWrite: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], buffer.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - } - else if (resource is BufferView bufferView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], bufferView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - - case ResourceType.Texture: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.SrvHandle, DescriptorHeapType.CbvSrvUav); - - SrvTextureViews[index] = texture.DirectX12().View; - } - else if (resource is TextureView textureView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().SrvHandle, DescriptorHeapType.CbvSrvUav); - - SrvTextureViews[index] = textureView.DirectX12(); - } - - index++; - } - } - break; - - case ResourceType.TextureReadWrite: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Texture texture) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], texture.DirectX12().View.UavHandle, DescriptorHeapType.CbvSrvUav); - - UavTextureViews[index] = texture.DirectX12().View; - } - else if (resource is TextureView textureView) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], textureView.DirectX12().UavHandle, DescriptorHeapType.CbvSrvUav); - - UavTextureViews[index] = textureView.DirectX12(); - } - - index++; - } - } - break; - - case ResourceType.Sampler: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Sampler sampler) - { - Context.Device.CopyDescriptorsSimple(1, samplerToken[index], sampler.DirectX12().Token.Handle, DescriptorHeapType.Sampler); - } - - index++; - } - } - break; - - case ResourceType.AccelerationStructure: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is not ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - Context.Device.CopyDescriptorsSimple(1, cbvSrvUavToken[index], topLevelAccelerationStructure.DirectX12().Token.Handle, DescriptorHeapType.CbvSrvUav); - } - - index++; - } - } - break; - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - samplerToken.Dispose(); - cbvSrvUavToken.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs deleted file mode 100644 index c422114e..00000000 --- a/sources/Zenith.NET.DirectX12/DXSampler.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXSampler : Sampler -{ - public DXDescriptorToken Token; - - public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, desc) - { - DxSamplerDesc samplerDesc = new() - { - Filter = DXFormats.DirectX12(desc.Filter, desc.ComparisonFunc).Filter, - AddressU = DXFormats.DirectX12(desc.U), - AddressV = DXFormats.DirectX12(desc.V), - AddressW = DXFormats.DirectX12(desc.W), - MipLODBias = desc.LodBias, - MaxAnisotropy = desc.MaxAnisotropy, - ComparisonFunc = DXFormats.DirectX12(desc.Filter, desc.ComparisonFunc).ComparisonFunc, - MinLOD = desc.MinLod, - MaxLOD = desc.MaxLod - }; - - switch (desc.BorderColor) - { - case BorderColor.OpaqueBlack: - samplerDesc.BorderColor[3] = 1.0f; - break; - - case BorderColor.OpaqueWhite: - samplerDesc.BorderColor[0] = 1.0f; - samplerDesc.BorderColor[1] = 1.0f; - samplerDesc.BorderColor[2] = 1.0f; - samplerDesc.BorderColor[3] = 1.0f; - break; - } - - context.Device.CreateSampler(&samplerDesc, (Token = context.SamplerAllocator.Allocate(1)).Handle); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Token.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXShader.cs b/sources/Zenith.NET.DirectX12/DXShader.cs deleted file mode 100644 index 5d425ab5..00000000 --- a/sources/Zenith.NET.DirectX12/DXShader.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXShader(DXGraphicsContext context, ShaderDesc desc) : Shader(context, desc) -{ - public ShaderBytecode GetShaderBytecode(ZenithMarshal.Scope scope) - { - return new() - { - PShaderBytecode = (byte*)ZenithMarshal.AllocateAndFill(scope, Desc.ShaderBytes), - BytecodeLength = (uint)Desc.ShaderBytes.Length - }; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - } -} diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs deleted file mode 100644 index 89a3e976..00000000 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ /dev/null @@ -1,134 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.DXGI; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXSwapChain : SwapChain -{ - private readonly DXFence fence; - private readonly DXSwapChainTargets swapChainTargets; - - public ComPtr SwapChain3; - - public uint BufferIndex; - - public DXSwapChain(DXGraphicsContext context, SwapChainDesc desc) : base(context, desc) - { - fence = new(context); - swapChainTargets = new(context, this); - - CreateSwapChain(); - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public override uint Width => CurrentColorTarget.Desc.Width; - - public override uint Height => CurrentColorTarget.Desc.Height; - - public override Texture CurrentColorTarget => swapChainTargets[BufferIndex]; - - public override Texture? CurrentDepthStencilTarget => swapChainTargets.DepthStencilTarget; - - public override Output Output => new() - { - ColorAttachments = [Desc.ColorTargetFormat], - DepthStencilAttachment = Desc.DepthStencilTargetFormat, - SampleCount = SampleCount.Count1 - }; - - public override void Present() - { - if (SwapChain3.Handle is not null) - { - SwapChain3.Present(0, DXGI.PresentAllowTearing).Success(); - - fence.Wait(Context.GraphicsQueue); - - BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); - } - } - - protected override void ResizeImpl() - { - if (SwapChain3.Handle is not null) - { - fence.Wait(Context.GraphicsQueue); - - swapChainTargets.DestroyTargets(); - - SwapChain3.ResizeBuffers(DXGraphicsContext.SwapChainBufferCount, - Desc.Surface.Width, - Desc.Surface.Height, - DXFormats.DirectX12(Desc.ColorTargetFormat), - (uint)SwapChainFlag.AllowTearing).Success(); - - swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, []); - - BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); - } - } - - protected override void RefreshImpl() - { - CreateSwapChain(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroySwapChain(); - - swapChainTargets.Dispose(); - fence.Dispose(); - } - - private void CreateSwapChain() - { - DestroySwapChain(); - - if (Desc.Surface.Type is not SurfaceType.D3D11Interop) - { - SwapChainDesc1 swapChainDesc = new() - { - Width = Desc.Surface.Width, - Height = Desc.Surface.Height, - Format = DXFormats.DirectX12(Desc.ColorTargetFormat), - SampleDesc = new(1, 0), - BufferUsage = DXGI.UsageRenderTargetOutput, - BufferCount = DXGraphicsContext.SwapChainBufferCount, - SwapEffect = SwapEffect.FlipDiscard, - AlphaMode = AlphaMode.Ignore, - Flags = (uint)SwapChainFlag.AllowTearing - }; - - Context.Factory7.CreateSwapChainForHwnd(Context.GraphicsQueue, - Desc.Surface.Handles[0], - &swapChainDesc, - null, - (ComPtr)null, - ref SwapChain3).Success(); - - swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, []); - - BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); - } - else - { - swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); - } - } - - private void DestroySwapChain() - { - swapChainTargets.DestroyTargets(); - - SwapChain3.Dispose(); - SwapChain3 = default; - - BufferIndex = 0; - } -} diff --git a/sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs b/sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs deleted file mode 100644 index cbfc3b61..00000000 --- a/sources/Zenith.NET.DirectX12/DXSwapChainTargets.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXSwapChainTargets(DXGraphicsContext context, DXSwapChain swapChain) : GraphicsResource(context) -{ - private DXTexture? depthStencilTarget; - private DXTexture[] colorTargets = []; - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public DXTexture this[uint index] => colorTargets[index]; - - public DXTexture? DepthStencilTarget => depthStencilTarget; - - public void CreateTargets(uint width, uint height, nint[] handles) - { - if (swapChain.Desc.DepthStencilTargetFormat is not null) - { - depthStencilTarget = new(context, new() - { - Type = TextureType.Texture2D, - Format = swapChain.Desc.DepthStencilTargetFormat.Value, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil - }); - } - - TextureDesc colorTargetDesc = new() - { - Type = TextureType.Texture2D, - Format = swapChain.Desc.ColorTargetFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.RenderTarget - }; - - if (swapChain.Desc.Surface.Type is not SurfaceType.D3D11Interop) - { - using ZenithMarshal.Scope scope = new(); - - colorTargets = new DXTexture[DXGraphicsContext.SwapChainBufferCount]; - - for (uint i = 0; i < DXGraphicsContext.SwapChainBufferCount; i++) - { - colorTargets[i] = new(context, colorTargetDesc, swapChain.SwapChain3.GetBuffer(i)); - } - } - else if (swapChain.Desc.Surface.Type is SurfaceType.D3D11Interop) - { - colorTargets = new DXTexture[1]; - - Context.Device.OpenSharedHandle((void*)handles[0], out ComPtr resource).Success(); - - colorTargets[0] = new(context, colorTargetDesc, resource); - } - } - - public void DestroyTargets() - { - foreach (DXTexture texture in colorTargets) - { - texture.Dispose(); - } - colorTargets = []; - - depthStencilTarget?.Dispose(); - depthStencilTarget = null; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroyTargets(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs deleted file mode 100644 index 119b413c..00000000 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ /dev/null @@ -1,236 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXTexture : Texture -{ - public ComPtr Resource; - - public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, desc) - { - bool isRenderTargetOrDepthStencil = desc.Flags.HasFlag(TextureUsageFlags.RenderTarget) || desc.Flags.HasFlag(TextureUsageFlags.DepthStencil); - - ResourceDesc resourceDesc = new() - { - Dimension = DXFormats.DirectX12(desc.Type), - Width = desc.Width, - Height = desc.Height, - DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : desc.ArrayLayers), - MipLevels = (ushort)desc.MipLevels, - Format = DXFormats.DirectX12(desc.Format), - SampleDesc = DXFormats.DirectX12(desc.SampleCount), - Flags = DXFormats.DirectX12(desc.Flags).Flags - }; - - Heap = new(context, resourceDesc, HeapType.Default, isRenderTargetOrDepthStencil ? HeapFlags.AllowOnlyRTDSTextures : HeapFlags.AllowOnlyNonRTDSTextures); - - if (isRenderTargetOrDepthStencil) - { - DxClearValue clearValue = new() { Format = DXFormats.DirectX12(desc.Format) }; - - if (desc.Flags.HasFlag(TextureUsageFlags.RenderTarget)) - { - clearValue.Anonymous.Color[3] = 1.0f; - } - - if (desc.Flags.HasFlag(TextureUsageFlags.DepthStencil)) - { - clearValue.Anonymous.DepthStencil.Depth = 1.0f; - } - - context.Device.CreatePlacedResource(Heap.Heap, 0, &resourceDesc, DXFormats.DirectX12(desc.Flags).States, &clearValue, out Resource).Success(); - } - else - { - context.Device.CreatePlacedResource(Heap.Heap, 0, &resourceDesc, DXFormats.DirectX12(desc.Flags).States, null, out Resource).Success(); - } - - View = new(context, new() - { - Texture = this, - Type = desc.Type, - Format = desc.Format, - Range = new() - { - BaseMipLevel = 0, - LevelCount = desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = desc.ArrayLayers - } - }); - } - - public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr resource) : base(context, desc) - { - Resource = resource; - - View = new(context, new() - { - Texture = this, - Type = desc.Type, - Format = desc.Format, - Range = new() - { - BaseMipLevel = 0, - LevelCount = desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = desc.ArrayLayers - } - }); - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public DXHeap? Heap { get; } - - public DXTextureView View { get; } - - public uint SubresourceIndex(TextureSubresource subresource) - { - return (subresource.ArrayLayer * Desc.MipLevels) + subresource.MipLevel; - } - - public DXDescriptorToken CreateRtvToken(TextureSubresource subresource) - { - DXDescriptorToken token = Context.RtvAllocator.Allocate(1); - - RenderTargetViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; - - switch (Desc.Type) - { - case TextureType.Texture1D: - { - viewDesc.ViewDimension = RtvDimension.Texture1D; - viewDesc.Texture1D.MipSlice = subresource.MipLevel; - } - break; - - case TextureType.Texture1DArray: - { - viewDesc.ViewDimension = RtvDimension.Texture1Darray; - viewDesc.Texture1DArray.MipSlice = subresource.MipLevel; - viewDesc.Texture1DArray.FirstArraySlice = subresource.ArrayLayer; - viewDesc.Texture1DArray.ArraySize = 1; - } - break; - - case TextureType.Texture2D: - if (Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = RtvDimension.Texture2D; - viewDesc.Texture2D.MipSlice = subresource.MipLevel; - } - else - { - viewDesc.ViewDimension = RtvDimension.Texture2Dms; - } - break; - - case TextureType.Texture2DArray: - case TextureType.TextureCube: - case TextureType.TextureCubeArray: - if (Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = RtvDimension.Texture2Darray; - viewDesc.Texture2DArray.MipSlice = subresource.MipLevel; - viewDesc.Texture2DArray.FirstArraySlice = subresource.ArrayLayer; - viewDesc.Texture2DArray.ArraySize = 1; - } - else - { - viewDesc.ViewDimension = RtvDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = subresource.ArrayLayer; - viewDesc.Texture2DMSArray.ArraySize = 1; - } - break; - - case TextureType.Texture3D: - { - viewDesc.ViewDimension = RtvDimension.Texture3D; - viewDesc.Texture3D.MipSlice = subresource.MipLevel; - viewDesc.Texture3D.WSize = Desc.Depth; - } - break; - } - - Context.Device.CreateRenderTargetView(Resource, &viewDesc, token.Handle); - - return token; - } - - public DXDescriptorToken CreateDsvToken(TextureSubresource subresource) - { - DXDescriptorToken token = Context.DsvAllocator.Allocate(1); - - DepthStencilViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; - - switch (Desc.Type) - { - case TextureType.Texture1D: - { - viewDesc.ViewDimension = DsvDimension.Texture1D; - viewDesc.Texture1D.MipSlice = subresource.MipLevel; - } - break; - - case TextureType.Texture1DArray: - { - viewDesc.ViewDimension = DsvDimension.Texture1Darray; - viewDesc.Texture1DArray.MipSlice = subresource.MipLevel; - viewDesc.Texture1DArray.FirstArraySlice = subresource.ArrayLayer; - viewDesc.Texture1DArray.ArraySize = 1; - } - break; - - case TextureType.Texture2D: - case TextureType.Texture3D: - if (Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = DsvDimension.Texture2D; - viewDesc.Texture2D.MipSlice = subresource.MipLevel; - } - else - { - viewDesc.ViewDimension = DsvDimension.Texture2Dms; - } - break; - - case TextureType.Texture2DArray: - case TextureType.TextureCube: - case TextureType.TextureCubeArray: - if (Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = DsvDimension.Texture2Darray; - viewDesc.Texture2DArray.MipSlice = subresource.MipLevel; - viewDesc.Texture2DArray.FirstArraySlice = subresource.ArrayLayer; - viewDesc.Texture2DArray.ArraySize = 1; - } - else - { - viewDesc.ViewDimension = DsvDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = subresource.ArrayLayer; - viewDesc.Texture2DMSArray.ArraySize = 1; - } - break; - } - - Context.Device.CreateDepthStencilView(Resource, &viewDesc, token.Handle); - - return token; - } - - protected override void SetResourceName(string name) - { - Resource.SetName(name).Success(); - } - - protected override void Destroy() - { - View.Dispose(); - - Resource.Dispose(); - - Heap?.Dispose(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs deleted file mode 100644 index 59e7f37d..00000000 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXTextureView(DXGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) -{ - private const uint CubeMapFaceCount = 6; - - private DXDescriptorToken? srvToken; - private DXDescriptorToken? uavToken; - - public CpuDescriptorHandle SrvHandle => (srvToken ??= CreateSrvToken()).Handle; - - public CpuDescriptorHandle UavHandle => (uavToken ??= CreateUavToken()).Handle; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - uavToken?.Dispose(); - srvToken?.Dispose(); - } - - private DXDescriptorToken CreateSrvToken() - { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); - TextureSubresourceRange range = Desc.Range; - - ShaderResourceViewDesc viewDesc = new() - { - Format = Resolve(Desc.Format), - Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping - }; - - switch (Desc.Type) - { - case TextureType.Texture1D: - { - viewDesc.ViewDimension = SrvDimension.Texture1D; - viewDesc.Texture1D.MostDetailedMip = range.BaseMipLevel; - viewDesc.Texture1D.MipLevels = range.LevelCount; - } - break; - - case TextureType.Texture1DArray: - { - viewDesc.ViewDimension = SrvDimension.Texture1Darray; - viewDesc.Texture1DArray.MostDetailedMip = range.BaseMipLevel; - viewDesc.Texture1DArray.MipLevels = range.LevelCount; - viewDesc.Texture1DArray.FirstArraySlice = range.BaseArrayLayer; - viewDesc.Texture1DArray.ArraySize = range.LayerCount; - } - break; - - case TextureType.Texture2D: - if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = SrvDimension.Texture2D; - viewDesc.Texture2D.MostDetailedMip = range.BaseMipLevel; - viewDesc.Texture2D.MipLevels = range.LevelCount; - } - else - { - viewDesc.ViewDimension = SrvDimension.Texture2Dms; - } - break; - - case TextureType.Texture2DArray: - if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = SrvDimension.Texture2Darray; - viewDesc.Texture2DArray.MostDetailedMip = range.BaseMipLevel; - viewDesc.Texture2DArray.MipLevels = range.LevelCount; - viewDesc.Texture2DArray.FirstArraySlice = range.BaseArrayLayer; - viewDesc.Texture2DArray.ArraySize = range.LayerCount; - } - else - { - viewDesc.ViewDimension = SrvDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = range.BaseArrayLayer; - viewDesc.Texture2DMSArray.ArraySize = range.LayerCount; - } - break; - - case TextureType.Texture3D: - { - viewDesc.ViewDimension = SrvDimension.Texture3D; - viewDesc.Texture3D.MostDetailedMip = range.BaseMipLevel; - viewDesc.Texture3D.MipLevels = range.LevelCount; - } - break; - - case TextureType.TextureCube: - { - viewDesc.ViewDimension = SrvDimension.Texturecube; - viewDesc.TextureCube.MostDetailedMip = range.BaseMipLevel; - viewDesc.TextureCube.MipLevels = range.LevelCount; - } - break; - - case TextureType.TextureCubeArray: - { - viewDesc.ViewDimension = SrvDimension.Texturecubearray; - viewDesc.TextureCubeArray.MostDetailedMip = range.BaseMipLevel; - viewDesc.TextureCubeArray.MipLevels = range.LevelCount; - viewDesc.TextureCubeArray.First2DArrayFace = range.BaseArrayLayer; - viewDesc.TextureCubeArray.NumCubes = range.LayerCount / CubeMapFaceCount; - } - break; - } - - context.Device.CreateShaderResourceView(Desc.Texture.DirectX12().Resource, &viewDesc, token.Handle); - - return token; - } - - private DXDescriptorToken CreateUavToken() - { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); - TextureSubresourceRange range = Desc.Range; - - UnorderedAccessViewDesc viewDesc = new() { Format = Resolve(Desc.Format) }; - - switch (Desc.Type) - { - case TextureType.Texture1D: - { - viewDesc.ViewDimension = UavDimension.Texture1D; - viewDesc.Texture1D.MipSlice = range.BaseMipLevel; - } - break; - - case TextureType.Texture1DArray: - { - viewDesc.ViewDimension = UavDimension.Texture1Darray; - viewDesc.Texture1DArray.MipSlice = range.BaseMipLevel; - viewDesc.Texture1DArray.FirstArraySlice = range.BaseArrayLayer; - viewDesc.Texture1DArray.ArraySize = range.LayerCount; - } - break; - - case TextureType.Texture2D: - if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = UavDimension.Texture2D; - viewDesc.Texture2D.MipSlice = range.BaseMipLevel; - } - else - { - viewDesc.ViewDimension = UavDimension.Texture2Dms; - } - break; - - case TextureType.Texture2DArray: - case TextureType.TextureCube: - case TextureType.TextureCubeArray: - if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = UavDimension.Texture2Darray; - viewDesc.Texture2DArray.MipSlice = range.BaseMipLevel; - viewDesc.Texture2DArray.FirstArraySlice = range.BaseArrayLayer; - viewDesc.Texture2DArray.ArraySize = range.LayerCount; - } - else - { - viewDesc.ViewDimension = UavDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = range.BaseArrayLayer; - viewDesc.Texture2DMSArray.ArraySize = range.LayerCount; - } - break; - - case TextureType.Texture3D: - { - viewDesc.ViewDimension = UavDimension.Texture3D; - viewDesc.Texture3D.MipSlice = range.BaseMipLevel; - viewDesc.Texture3D.WSize = Desc.Texture.Desc.Depth; - } - break; - } - - context.Device.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, (ID3D12Resource*)null, &viewDesc, token.Handle); - - return token; - } - - private static Format Resolve(PixelFormat pixelFormat) - { - return pixelFormat switch - { - PixelFormat.D16UNorm => Format.FormatR16Unorm, - PixelFormat.D24UNormS8UInt => Format.FormatR24G8Typeless, - PixelFormat.D32Float => Format.FormatR32Float, - PixelFormat.D32FloatS8UInt => Format.FormatR32G8X24Typeless, - _ => DXFormats.DirectX12(pixelFormat) - }; - } -} diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs deleted file mode 100644 index 1c21312e..00000000 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ /dev/null @@ -1,153 +0,0 @@ -using Silk.NET.Direct3D12; -using Silk.NET.Maths; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXTopLevelAccelerationStructure : TopLevelAccelerationStructure -{ - public DXDescriptorToken Token; - - public DXTopLevelAccelerationStructure(DXGraphicsContext context, TopLevelAccelerationStructureDesc desc, DXCommandBuffer commandBuffer) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - InstanceBuffer = new(context, new() - { - SizeInBytes = (uint)(sizeof(RaytracingInstanceDesc) * desc.Instances.Length), - StrideInBytes = (uint)sizeof(RaytracingInstanceDesc), - Flags = BufferUsageFlags.MapWrite - }); - - FillInstanceBuffer(desc, out BuildRaytracingAccelerationStructureInputs inputs); - - RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); - - context.Device5?.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - - AccelerationStructureBuffer = new(context, new() - { - SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, - StrideInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, - Flags = BufferUsageFlags.AccelerationStructure - }); - - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, - StrideInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, - Flags = BufferUsageFlags.UnorderedAccess - }); - - BuildRaytracingAccelerationStructureDesc buildDesc = new() - { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - Inputs = inputs, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress - }; - - commandBuffer.GraphicsCommandList4.BuildRaytracingAccelerationStructure(&buildDesc, 0, (RaytracingAccelerationStructurePostbuildInfoDesc*)null); - - ResourceBarrier barrier = new() - { - Type = ResourceBarrierType.Uav, - UAV = new() - { - PResource = AccelerationStructureBuffer.Resource - } - }; - - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); - - ShaderResourceViewDesc viewDesc = new() - { - ViewDimension = SrvDimension.RaytracingAccelerationStructure, - Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, - RaytracingAccelerationStructure = new() - { - Location = AccelerationStructureBuffer.GPUVirtualAddress - } - }; - - context.Device.CreateShaderResourceView((ID3D12Resource*)null, &viewDesc, (Token = context.CbvSrvUavAllocator.Allocate(1)).Handle); - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - public DXBuffer InstanceBuffer { get; } - - public DXBuffer AccelerationStructureBuffer { get; } - - public DXBuffer ScratchBuffer { get; } - - public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) - { - FillInstanceBuffer(newDesc, out BuildRaytracingAccelerationStructureInputs inputs); - - BuildRaytracingAccelerationStructureDesc buildDesc = new() - { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - Inputs = inputs, - SourceAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress - }; - - commandBuffer.GraphicsCommandList4.BuildRaytracingAccelerationStructure(&buildDesc, 0, (RaytracingAccelerationStructurePostbuildInfoDesc*)null); - - ResourceBarrier barrier = new() - { - Type = ResourceBarrierType.Uav, - UAV = new() - { - PResource = AccelerationStructureBuffer.Resource - } - }; - - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Token.Dispose(); - - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - InstanceBuffer.Dispose(); - } - - private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) - { - uint instanceCount = (uint)desc.Instances.Length; - - MappedMemory mappedMemory = InstanceBuffer.Map(); - - RaytracingInstanceDesc* instances = (RaytracingInstanceDesc*)mappedMemory.Pointer; - for (uint i = 0; i < instanceCount; i++) - { - RayTracingInstance instance = desc.Instances[i]; - - instances[i] = new() - { - InstanceID = instance.ID, - InstanceMask = instance.Mask, - Flags = (uint)DXFormats.DirectX12(instance.Flags), - AccelerationStructure = instance.AccelerationStructure.DirectX12().AccelerationStructureBuffer.GPUVirtualAddress - }; - - *(Matrix3X4*)instances[i].Transform = DXFormats.DirectX12(instance.Transform); - } - - InstanceBuffer.Unmap(); - - inputs = new() - { - Type = RaytracingAccelerationStructureType.TopLevel, - Flags = DXFormats.DirectX12(desc.Flags), - NumDescs = instanceCount, - InstanceDescs = InstanceBuffer.GPUVirtualAddress - }; - } -} diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs deleted file mode 100644 index 99a079ad..00000000 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXValidationLayer : ValidationLayer -{ - private readonly PfnMessageFunc callback; - private readonly uint callbackCookie; - - public DXValidationLayer(DXGraphicsContext context) : base(context) - { - context.InfoQueue1?.RegisterMessageCallback(callback = new(Callback), MessageCallbackFlags.FlagNone, null, ref callbackCookie).Success(); - } - - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Context.InfoQueue1?.UnregisterMessageCallback(callbackCookie).Success(); - - callback.Dispose(); - } - - private void Callback(MessageCategory category, DxMessageSeverity severity, MessageID messageID, byte* pDescription, void* context) - { - Report(MessageSource.GraphicsAPI, severity switch - { - DxMessageSeverity.Error => MessageSeverity.Error, - DxMessageSeverity.Warning => MessageSeverity.Warning, - _ => MessageSeverity.Info - }, ZenithMarshal.StringFromPointer((nint)pDescription, StringEncoding.UTF8)); - } -} diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 188c0b60..f3a086cf 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -1,6 +1,4 @@ using System.Diagnostics; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; @@ -29,236 +27,4 @@ internal bool IsSuccess() return result is 0; } } - - extension(ResourceBinding[] resourceBindings) - { - internal void DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges) - { - List cbvSrvUavRangeList = []; - List samplerRangeList = []; - - uint cbvIndex = 0; - uint srvIndex = 0; - uint uavIndex = 0; - uint samplerIndex = 0; - uint cbvSrvUavRangeOffset = 0; - uint samplerRangeOffset = 0; - foreach (ResourceBinding resourceBinding in resourceBindings) - { - DescriptorRange range = new() - { - RangeType = DXFormats.DirectX12(resourceBinding.Type), - NumDescriptors = resourceBinding.Count - }; - - switch (resourceBinding.Type) - { - case ResourceType.ConstantBuffer: - range.BaseShaderRegister = cbvIndex++; - range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset++; - - cbvSrvUavRangeList.Add(range); - break; - - case ResourceType.StructuredBuffer: - case ResourceType.Texture: - case ResourceType.AccelerationStructure: - range.BaseShaderRegister = srvIndex++; - range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset++; - - cbvSrvUavRangeList.Add(range); - break; - - case ResourceType.StructuredBufferReadWrite: - case ResourceType.TextureReadWrite: - range.BaseShaderRegister = uavIndex++; - range.OffsetInDescriptorsFromTableStart = cbvSrvUavRangeOffset++; - - cbvSrvUavRangeList.Add(range); - break; - - case ResourceType.Sampler: - range.BaseShaderRegister = samplerIndex++; - range.OffsetInDescriptorsFromTableStart = samplerRangeOffset++; - - samplerRangeList.Add(range); - break; - } - } - - cbvSrvUavRanges = [.. cbvSrvUavRangeList]; - samplerRanges = [.. samplerRangeList]; - } - - internal ComPtr RootSignature(DXGraphicsContext context) - { - using ZenithMarshal.Scope scope = new(); - - resourceBindings.DirectX12(out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges); - - List parameters = []; - - if (cbvSrvUavRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } - - if (samplerRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } - - RootSignatureDesc desc = new() - { - NumParameters = (uint)parameters.Count, - PParameters = (RootParameter*)ZenithMarshal.AllocateAndFill(scope, [.. parameters]), - Flags = RootSignatureFlags.AllowInputAssemblerInputLayout - }; - - ComPtr blob = default; - ComPtr error = default; - context.D3D12.SerializeRootSignature(&desc, D3DRootSignatureVersion.Version1, ref blob, ref error).Success(); - context.Device.CreateRootSignature(0, blob.GetBufferPointer(), blob.GetBufferSize(), out ComPtr rootSignature).Success(); - blob.Dispose(); - error.Dispose(); - - return rootSignature; - } - } - - extension(CommandBuffer commandBuffer) - { - internal DXCommandBuffer DirectX12() - { - return (DXCommandBuffer)commandBuffer; - } - } - - extension(SwapChain swapChain) - { - internal DXSwapChain DirectX12() - { - return (DXSwapChain)swapChain; - } - } - - extension(Shader shader) - { - internal DXShader DirectX12() - { - return (DXShader)shader; - } - } - - extension(Buffer buffer) - { - internal DXBuffer DirectX12() - { - return (DXBuffer)buffer; - } - } - - extension(BufferView bufferView) - { - internal DXBufferView DirectX12() - { - return (DXBufferView)bufferView; - } - } - - extension(Texture texture) - { - internal DXTexture DirectX12() - { - return (DXTexture)texture; - } - } - - extension(TextureView textureView) - { - internal DXTextureView DirectX12() - { - return (DXTextureView)textureView; - } - } - - extension(Sampler sampler) - { - internal DXSampler DirectX12() - { - return (DXSampler)sampler; - } - } - - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) - { - internal DXBottomLevelAccelerationStructure DirectX12() - { - return (DXBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; - } - } - - extension(TopLevelAccelerationStructure topLevelAccelerationStructure) - { - internal DXTopLevelAccelerationStructure DirectX12() - { - return (DXTopLevelAccelerationStructure)topLevelAccelerationStructure; - } - } - - extension(ResourceTable resourceTable) - { - internal DXResourceTable DirectX12() - { - return (DXResourceTable)resourceTable; - } - } - - extension(GraphicsPipeline graphicsPipeline) - { - internal DXGraphicsPipeline DirectX12() - { - return (DXGraphicsPipeline)graphicsPipeline; - } - } - - extension(ComputePipeline computePipeline) - { - internal DXComputePipeline DirectX12() - { - return (DXComputePipeline)computePipeline; - } - } - - extension(MeshShadingPipeline meshShadingPipeline) - { - internal DXMeshShadingPipeline DirectX12() - { - return (DXMeshShadingPipeline)meshShadingPipeline; - } - } - - extension(QueryHeap queryHeap) - { - internal DXQueryHeap DirectX12() - { - return (DXQueryHeap)queryHeap; - } - } } diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 5371b106..5f282702 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,14 +1 @@ -global using DxBlend = Silk.NET.Direct3D12.Blend; -global using DxBlendOp = Silk.NET.Direct3D12.BlendOp; -global using DxClearValue = Silk.NET.Direct3D12.ClearValue; -global using DxComparisonFunc = Silk.NET.Direct3D12.ComparisonFunc; -global using DxCullMode = Silk.NET.Direct3D12.CullMode; -global using DxFillMode = Silk.NET.Direct3D12.FillMode; -global using DxFilter = Silk.NET.Direct3D12.Filter; -global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; -global using DxQueryHeapDesc = Silk.NET.Direct3D12.QueryHeapDesc; -global using DxQueryType = Silk.NET.Direct3D12.QueryType; -global using DxRange = Silk.NET.Direct3D12.Range; -global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; -global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; -global using DxViewport = Silk.NET.Direct3D12.Viewport; + \ No newline at end of file From 009dc222e6cdd534c458b3e96889b877ae16bfe9 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 21:00:27 +0800 Subject: [PATCH 080/466] Add initial DirectX 12 backend implementation Implement DXGraphicsContext to initialize D3D12 device, queues, root signature, and command signatures with validation layer support. Add DXCapabilities, DXCommandQueue, and DXValidationLayer classes. Integrate Silk.NET Direct3D12/DXGI bindings and global using aliases. Lays foundation for DirectX 12 support in Zenith.NET. --- .../Zenith.NET.DirectX12/DXCapabilities.cs | 30 +++++ .../Zenith.NET.DirectX12/DXCommandQueue.cs | 37 ++++++ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 115 +++++++++++++++++- .../Zenith.NET.DirectX12/DXValidationLayer.cs | 42 +++++++ sources/Zenith.NET.DirectX12/Usings.cs | 2 +- 5 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXCapabilities.cs create mode 100644 sources/Zenith.NET.DirectX12/DXCommandQueue.cs create mode 100644 sources/Zenith.NET.DirectX12/DXValidationLayer.cs diff --git a/sources/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Zenith.NET.DirectX12/DXCapabilities.cs new file mode 100644 index 00000000..abbcf6e8 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXCapabilities.cs @@ -0,0 +1,30 @@ +using Silk.NET.Direct3D12; +using Silk.NET.DXGI; +using Feature = Silk.NET.Direct3D12.Feature; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXCapabilities : Capabilities +{ + public DXCapabilities(DXGraphicsContext context) + { + AdapterDesc desc; + context.Adapter4.GetDesc(&desc).Success(); + + FeatureDataD3D12Options5 options5 = new(); + context.Device10.CheckFeatureSupport(Feature.D3D12Options5, &options5, (uint)sizeof(FeatureDataD3D12Options5)).Success(); + + FeatureDataD3D12Options7 options7 = new(); + context.Device10.CheckFeatureSupport(Feature.D3D12Options7, &options7, (uint)sizeof(FeatureDataD3D12Options7)).Success(); + + DeviceName = ZenithMarshal.StringFromPointer((nint)desc.Description, StringEncoding.UTF16); + RayTracing = options5.RaytracingTier >= RaytracingTier.Tier11; + MeshShading = options7.MeshShaderTier >= MeshShaderTier.Tier1; + } + + public override string DeviceName { get; } + + public override bool RayTracing { get; } + + public override bool MeshShading { get; } +} diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs new file mode 100644 index 00000000..33d2ca6c --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -0,0 +1,37 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal class DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr commandQueue) : CommandQueue(context, type) +{ + public override nint GetNativeObject(NativeObjectType type) + { + throw new NotImplementedException(); + } + + protected override ulong GetCompletedValue() + { + throw new NotImplementedException(); + } + + protected override CommandBuffer CreateCommandBuffer() + { + throw new NotImplementedException(); + } + + protected override void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue) + { + throw new NotImplementedException(); + } + + protected override void WaitImpl(ulong waitValue) + { + throw new NotImplementedException(); + } + + protected override void SetResourceName(string name) + { + throw new NotImplementedException(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 0c51bf92..94838aa1 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -1,7 +1,39 @@ -namespace Zenith.NET.DirectX12; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; +using Silk.NET.DXGI; -internal class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.DirectX12, useValidationLayer) +namespace Zenith.NET.DirectX12; + +internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.DirectX12, useValidationLayer) { + public ComPtr Factory7; + + public ComPtr Adapter4; + + public ComPtr Device10; + + public ComPtr? InfoQueue1; + + public ComPtr RootSignature; + + public ComPtr DrawSignature; + + public ComPtr DrawIndexedSignature; + + public ComPtr DispatchSignature; + + public ComPtr DispatchMeshSignature; + + public ComPtr GraphicsCommandQueue; + + public ComPtr ComputeCommandQueue; + + public ComPtr CopyCommandQueue; + + public DXGI DXGI { get; } = DXGI.GetApi(null); + + public D3D12 D3D12 { get; } = D3D12.GetApi(); + public override nint GetNativeObject(NativeObjectType type) { throw new NotImplementedException(); @@ -14,7 +46,84 @@ protected override void Initialize(bool useValidationLayer, out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - throw new NotImplementedException(); + if (useValidationLayer && D3D12.GetDebugInterface(out ComPtr debug).IsSuccess()) + { + debug.EnableDebugLayer(); + + debug.Dispose(); + } + + DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), out Factory7).Success(); + + Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, out Adapter4).Success(); + + if (!D3D12.CreateDevice(Adapter4, D3DFeatureLevel.Level122, out Device10).IsSuccess()) + { + throw new NotSupportedException("Direct3D 12 Feature Level 12_2 is not supported on the selected adapter."); + } + + if (Device10.QueryInterface(out ComPtr infoQueue1).IsSuccess()) + { + InfoQueue1 = infoQueue1; + } + + RootParameter1 rootParameter = new() + { + ParameterType = RootParameterType.TypeCbv, + ShaderVisibility = ShaderVisibility.All, + Descriptor = new() { Flags = RootDescriptorFlags.DataVolatile } + }; + + RootSignatureDesc1 rootSignatureDesc = new() + { + NumParameters = 1, + PParameters = &rootParameter, + Flags = RootSignatureFlags.AllowInputAssemblerInputLayout | RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed | RootSignatureFlags.SamplerHeapDirectlyIndexed + }; + + VersionedRootSignatureDesc versionedRootSignatureDesc = new() + { + Version = D3DRootSignatureVersion.Version11, + Desc11 = rootSignatureDesc + }; + + ComPtr rootSignatureBlob = default; + ComPtr rootSignatureError = default; + D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, ref rootSignatureBlob, ref rootSignatureError).Success(); + Device10.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), out RootSignature).Success(); + rootSignatureBlob.Dispose(); + rootSignatureError.Dispose(); + + IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; + CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; + Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawSignature).Success(); + + indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; + commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); + Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawIndexedSignature).Success(); + + indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; + commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); + Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchSignature).Success(); + + indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; + commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); + Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchMeshSignature).Success(); + + CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; + Device10.CreateCommandQueue(&commandQueueDesc, out GraphicsCommandQueue).Success(); + + commandQueueDesc.Type = CommandListType.Compute; + Device10.CreateCommandQueue(&commandQueueDesc, out ComputeCommandQueue).Success(); + + commandQueueDesc.Type = CommandListType.Copy; + Device10.CreateCommandQueue(&commandQueueDesc, out CopyCommandQueue).Success(); + + capabilities = new DXCapabilities(this); + graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); + computeQueue = new DXCommandQueue(this, CommandQueueType.Compute, ComputeCommandQueue); + copyQueue = new DXCommandQueue(this, CommandQueueType.Copy, CopyCommandQueue); + validationLayer = useValidationLayer ? new DXValidationLayer(this) : null; } protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs new file mode 100644 index 00000000..41429f3d --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -0,0 +1,42 @@ +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXValidationLayer : ValidationLayer +{ + private readonly PfnMessageFunc callback; + private readonly uint callbackCookie; + + public DXValidationLayer(DXGraphicsContext context) : base(context) + { + context.InfoQueue1?.RegisterMessageCallback(callback = new(Callback), MessageCallbackFlags.FlagNone, null, ref callbackCookie).Success(); + } + + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + Context.InfoQueue1?.UnregisterMessageCallback(callbackCookie).Success(); + + callback.Dispose(); + } + + private void Callback(MessageCategory category, DxMessageSeverity severity, MessageID messageID, byte* pDescription, void* context) + { + Report(MessageSource.GraphicsApi, severity switch + { + DxMessageSeverity.Error => MessageSeverity.Error, + DxMessageSeverity.Warning => MessageSeverity.Warning, + _ => MessageSeverity.Info + }, ZenithMarshal.StringFromPointer((nint)pDescription, StringEncoding.UTF8)); + } +} diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 5f282702..bf2480a1 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1 +1 @@ - \ No newline at end of file +global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; From bdc0f9853717f3219137a55756c8094bbf7c64bc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 21:36:38 +0800 Subject: [PATCH 081/466] Add DXDescriptorHeap and improve resource management Added DXDescriptorHeap class for descriptor heap management and DXDescriptorToken for slot handling. Updated DXGraphicsContext with descriptor heap properties and implemented comprehensive resource cleanup in Destroy(). --- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 55 +++++++++++++++++++ .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 14 +++++ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 38 +++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs create mode 100644 sources/Zenith.NET.DirectX12/DXDescriptorToken.cs diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs new file mode 100644 index 00000000..22ccfedc --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -0,0 +1,55 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXDescriptorHeap : DisposableObject +{ + private readonly Stack slots = []; + + public ComPtr Heap; + + private uint currentIndex; + + public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint capacity, bool shaderVisible) + { + DescriptorHeapDesc desc = new() + { + Type = type, + NumDescriptors = Capacity = capacity, + Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None + }; + + context.Device10.CreateDescriptorHeap(&desc, out Heap).Success(); + + DescriptorSize = context.Device10.GetDescriptorHandleIncrementSize(type); + CpuStart = Heap.GetCPUDescriptorHandleForHeapStart(); + GpuStart = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; + } + + public uint Capacity { get; } + + public uint DescriptorSize { get; } + + public CpuDescriptorHandle CpuStart { get; } + + public GpuDescriptorHandle GpuStart { get; } + + public DXDescriptorToken Allocate() + { + uint slot = slots.TryPop(out uint reused) ? reused : currentIndex++; + + return new(slot, DescriptorSize, CpuStart, GpuStart); + } + + public void Free(DXDescriptorToken token) + { + slots.Push(token.Slot); + } + + protected override void Destroy() + { + Heap.Dispose(); + } +} + diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs new file mode 100644 index 00000000..04a0d630 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -0,0 +1,14 @@ +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal readonly struct DXDescriptorToken(uint slot, uint descriptorSize, CpuDescriptorHandle cpuStart, GpuDescriptorHandle gpuStart) +{ + public uint Slot => slot; + + public ResourceHandle ResourceHandle => new(slot, 0); + + public CpuDescriptorHandle CpuHandle => new() { Ptr = (slot * descriptorSize) + cpuStart.Ptr }; + + public GpuDescriptorHandle GpuHandle => new() { Ptr = (slot * descriptorSize) + gpuStart.Ptr }; +} \ No newline at end of file diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 94838aa1..1b3fb70f 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -34,6 +34,14 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public D3D12 D3D12 { get; } = D3D12.GetApi(); + public DXDescriptorHeap RtvHeap => field ??= new(this, DescriptorHeapType.Rtv, 1024, false); + + public DXDescriptorHeap DsvHeap => field ??= new(this, DescriptorHeapType.Dsv, 256, false); + + public DXDescriptorHeap CbvSrvUavHeap => field ??= new(this, DescriptorHeapType.CbvSrvUav, 1000000, true); + + public DXDescriptorHeap SamplerHeap => field ??= new(this, DescriptorHeapType.Sampler, 2048, true); + public override nint GetNativeObject(NativeObjectType type) { throw new NotImplementedException(); @@ -180,4 +188,34 @@ protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { throw new NotImplementedException(); } + + protected override void Destroy() + { + base.Destroy(); + + SamplerHeap.Dispose(); + CbvSrvUavHeap.Dispose(); + DsvHeap.Dispose(); + RtvHeap.Dispose(); + + CopyCommandQueue.Dispose(); + ComputeCommandQueue.Dispose(); + GraphicsCommandQueue.Dispose(); + + DispatchMeshSignature.Dispose(); + DispatchSignature.Dispose(); + DrawIndexedSignature.Dispose(); + DrawSignature.Dispose(); + + RootSignature.Dispose(); + + InfoQueue1?.Dispose(); + + Device10.Dispose(); + Adapter4.Dispose(); + Factory7.Dispose(); + + D3D12.Dispose(); + DXGI.Dispose(); + } } From 1e47a15f0e817858e3079fc06d8b326a7bcd495c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 21:43:22 +0800 Subject: [PATCH 082/466] Refactor descriptor heap slot management and naming Renamed variables for clarity (e.g., DescriptorSize to IncrementSize, Capacity to NumDescriptors, slots to recycled). Updated allocation logic to use a head counter and recycled stack. Adjusted DXDescriptorToken to use incrementSize for handle calculations, improving consistency and resource management. --- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 23 +++++++++++-------- .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 6 ++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 22ccfedc..795956b6 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -5,31 +5,31 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXDescriptorHeap : DisposableObject { - private readonly Stack slots = []; + private readonly Stack recycled = []; public ComPtr Heap; - private uint currentIndex; + private uint head; - public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint capacity, bool shaderVisible) + public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint numDescriptors, bool shaderVisible) { DescriptorHeapDesc desc = new() { Type = type, - NumDescriptors = Capacity = capacity, + NumDescriptors = NumDescriptors = numDescriptors, Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None }; context.Device10.CreateDescriptorHeap(&desc, out Heap).Success(); - DescriptorSize = context.Device10.GetDescriptorHandleIncrementSize(type); + IncrementSize = context.Device10.GetDescriptorHandleIncrementSize(type); CpuStart = Heap.GetCPUDescriptorHandleForHeapStart(); GpuStart = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; } - public uint Capacity { get; } + public uint NumDescriptors { get; } - public uint DescriptorSize { get; } + public uint IncrementSize { get; } public CpuDescriptorHandle CpuStart { get; } @@ -37,14 +37,17 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint public DXDescriptorToken Allocate() { - uint slot = slots.TryPop(out uint reused) ? reused : currentIndex++; + if (!recycled.TryPop(out uint slot)) + { + slot = head++; + } - return new(slot, DescriptorSize, CpuStart, GpuStart); + return new(slot, IncrementSize, CpuStart, GpuStart); } public void Free(DXDescriptorToken token) { - slots.Push(token.Slot); + recycled.Push(token.Slot); } protected override void Destroy() diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index 04a0d630..44250e70 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -2,13 +2,13 @@ namespace Zenith.NET.DirectX12; -internal readonly struct DXDescriptorToken(uint slot, uint descriptorSize, CpuDescriptorHandle cpuStart, GpuDescriptorHandle gpuStart) +internal readonly struct DXDescriptorToken(uint slot, uint incrementSize, CpuDescriptorHandle cpuStart, GpuDescriptorHandle gpuStart) { public uint Slot => slot; public ResourceHandle ResourceHandle => new(slot, 0); - public CpuDescriptorHandle CpuHandle => new() { Ptr = (slot * descriptorSize) + cpuStart.Ptr }; + public CpuDescriptorHandle CpuHandle => new() { Ptr = (slot * incrementSize) + cpuStart.Ptr }; - public GpuDescriptorHandle GpuHandle => new() { Ptr = (slot * descriptorSize) + gpuStart.Ptr }; + public GpuDescriptorHandle GpuHandle => new() { Ptr = (slot * incrementSize) + gpuStart.Ptr }; } \ No newline at end of file From 19fc50c104b416c2a80718223eb13f08ed9b3bc3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 21 May 2026 21:44:34 +0800 Subject: [PATCH 083/466] Rename descriptor handle properties for clarity Renamed CpuStart and GpuStart to CpuStartHandle and GpuStartHandle in DXDescriptorHeap and updated all references. Also updated DXDescriptorToken to use the new parameter names and property implementations. These changes improve code readability and clarify that these properties represent descriptor handles. --- sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs | 10 +++++----- sources/Zenith.NET.DirectX12/DXDescriptorToken.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 795956b6..5b8e4a5f 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -23,17 +23,17 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint context.Device10.CreateDescriptorHeap(&desc, out Heap).Success(); IncrementSize = context.Device10.GetDescriptorHandleIncrementSize(type); - CpuStart = Heap.GetCPUDescriptorHandleForHeapStart(); - GpuStart = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; + CpuStartHandle = Heap.GetCPUDescriptorHandleForHeapStart(); + GpuStartHandle = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; } public uint NumDescriptors { get; } public uint IncrementSize { get; } - public CpuDescriptorHandle CpuStart { get; } + public CpuDescriptorHandle CpuStartHandle { get; } - public GpuDescriptorHandle GpuStart { get; } + public GpuDescriptorHandle GpuStartHandle { get; } public DXDescriptorToken Allocate() { @@ -42,7 +42,7 @@ public DXDescriptorToken Allocate() slot = head++; } - return new(slot, IncrementSize, CpuStart, GpuStart); + return new(slot, IncrementSize, CpuStartHandle, GpuStartHandle); } public void Free(DXDescriptorToken token) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index 44250e70..8b3303c9 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -2,13 +2,13 @@ namespace Zenith.NET.DirectX12; -internal readonly struct DXDescriptorToken(uint slot, uint incrementSize, CpuDescriptorHandle cpuStart, GpuDescriptorHandle gpuStart) +internal readonly struct DXDescriptorToken(uint slot, uint incrementSize, CpuDescriptorHandle cpuStartHandle, GpuDescriptorHandle gpuStartHandle) { public uint Slot => slot; public ResourceHandle ResourceHandle => new(slot, 0); - public CpuDescriptorHandle CpuHandle => new() { Ptr = (slot * incrementSize) + cpuStart.Ptr }; + public CpuDescriptorHandle CpuHandle => new() { Ptr = (slot * incrementSize) + cpuStartHandle.Ptr }; - public GpuDescriptorHandle GpuHandle => new() { Ptr = (slot * incrementSize) + gpuStart.Ptr }; + public GpuDescriptorHandle GpuHandle => new() { Ptr = (slot * incrementSize) + gpuStartHandle.Ptr }; } \ No newline at end of file From 990e11728cf1370730d40b6fdc5366e6792c91a5 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 22 May 2026 19:02:18 +0800 Subject: [PATCH 084/466] Add Heap abstraction and resource barrier improvements Introduce Heap, HeapType, and HeapDesc for GPU memory management, enabling buffer and texture creation from heaps. Extend GraphicsContext with heap creation and resource size/alignment queries. Add CommandBuffer overloads for memory and aliasing barriers with validation. Refactor CommandSubmission and MappedMemory to use primary constructors and readonly fields. Update ValidationLayer for HeapDesc validation and improve API consistency. --- sources/Zenith.NET/CommandBuffer.cs | 62 +++++++++++++++++-- sources/Zenith.NET/CommandQueue.cs | 10 +-- sources/Zenith.NET/Downloader.cs | 4 +- sources/Zenith.NET/Enums/HeapType.cs | 10 +++ sources/Zenith.NET/GraphicsContext.cs | 27 ++++++++ sources/Zenith.NET/Heap.cs | 26 ++++++++ .../Zenith.NET/Structs/CommandSubmission.cs | 6 +- sources/Zenith.NET/Structs/HeapDesc.cs | 8 +++ sources/Zenith.NET/Structs/MappedMemory.cs | 6 +- .../Structs/ResourceSizeAndAlignment.cs | 8 +++ sources/Zenith.NET/ValidationLayer.cs | 6 ++ 11 files changed, 156 insertions(+), 17 deletions(-) create mode 100644 sources/Zenith.NET/Enums/HeapType.cs create mode 100644 sources/Zenith.NET/Heap.cs create mode 100644 sources/Zenith.NET/Structs/HeapDesc.cs create mode 100644 sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index ba235945..c3e92647 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -18,6 +18,16 @@ public void MemoryBarrier() MemoryBarrierImpl(); } + public void MemoryBarrier(Buffer buffer) + { + if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.buffer", buffer) is false) + { + return; + } + + MemoryBarrierImpl(buffer); + } + public void MemoryBarrier(Texture texture) { if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.texture", texture) is false) @@ -28,14 +38,50 @@ public void MemoryBarrier(Texture texture) MemoryBarrierImpl(texture); } - public void MemoryBarrier(Buffer buffer) + public void MemoryBarrier(ReadOnlySpan resources) { - if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.buffer", buffer) is false) + for (int i = 0; i < resources.Length; i++) + { + if (Context.ValidationLayer?.ValidateResource($"MemoryBarrier.resources[{i}]", resources[i]) is false) + { + return; + } + } + + MemoryBarrierImpl(resources); + } + + public void AliasingBarrier(Buffer buffer) + { + if (Context.ValidationLayer?.ValidateResource("AliasingBarrier.buffer", buffer) is false) { return; } - MemoryBarrierImpl(buffer); + AliasingBarrierImpl(buffer); + } + + public void AliasingBarrier(Texture texture) + { + if (Context.ValidationLayer?.ValidateResource("AliasingBarrier.texture", texture) is false) + { + return; + } + + AliasingBarrierImpl(texture); + } + + public void AliasingBarrier(ReadOnlySpan resources) + { + for (int i = 0; i < resources.Length; i++) + { + if (Context.ValidationLayer?.ValidateResource($"AliasingBarrier.resources[{i}]", resources[i]) is false) + { + return; + } + } + + AliasingBarrierImpl(resources); } public void Transition(Texture texture, TextureSubresource subresource, TextureState state) @@ -604,9 +650,17 @@ private bool TryGetCurrentPipeline(string commandName, out TPipeline protected abstract void MemoryBarrierImpl(); + protected abstract void MemoryBarrierImpl(Buffer buffer); + protected abstract void MemoryBarrierImpl(Texture texture); - protected abstract void MemoryBarrierImpl(Buffer buffer); + protected abstract void MemoryBarrierImpl(ReadOnlySpan resources); + + protected abstract void AliasingBarrierImpl(Buffer buffer); + + protected abstract void AliasingBarrierImpl(Texture texture); + + protected abstract void AliasingBarrierImpl(ReadOnlySpan resources); protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureState state); diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 8288b6b8..cf1de1ad 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -29,9 +29,9 @@ internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan= sizeInBytes; } - public Lease Borrow(BufferData data) + public Lease Borrow(BufferData newData) { - this.data = data; + data = newData; return this; } diff --git a/sources/Zenith.NET/Enums/HeapType.cs b/sources/Zenith.NET/Enums/HeapType.cs new file mode 100644 index 00000000..04ce3464 --- /dev/null +++ b/sources/Zenith.NET/Enums/HeapType.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public enum HeapType +{ + GpuOnly, + + CpuReadOnly, + + CpuWriteOnly +} diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 3b892251..9ab11663 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -53,6 +53,27 @@ public SwapChain CreateSwapChain(SwapChainDesc desc) return CreateSwapChainImpl(desc); } + public Heap CreateHeap(HeapDesc desc) + { + ValidationLayer?.ValidateDesc(desc); + + return CreateHeapImpl(desc); + } + + public ResourceSizeAndAlignment GetSizeAndAlignment(BufferDesc desc) + { + ValidationLayer?.ValidateDesc(desc); + + return GetSizeAndAlignmentImpl(desc); + } + + public ResourceSizeAndAlignment GetSizeAndAlignment(TextureDesc desc) + { + ValidationLayer?.ValidateDesc(desc); + + return GetSizeAndAlignmentImpl(desc); + } + public Buffer CreateBuffer(BufferDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -166,6 +187,12 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract SwapChain CreateSwapChainImpl(SwapChainDesc desc); + protected abstract Heap CreateHeapImpl(HeapDesc desc); + + protected abstract ResourceSizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc); + + protected abstract ResourceSizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc); + protected abstract Buffer CreateBufferImpl(BufferDesc desc); protected abstract BufferView CreateBufferViewImpl(BufferViewDesc desc); diff --git a/sources/Zenith.NET/Heap.cs b/sources/Zenith.NET/Heap.cs new file mode 100644 index 00000000..289ee7e3 --- /dev/null +++ b/sources/Zenith.NET/Heap.cs @@ -0,0 +1,26 @@ +namespace Zenith.NET; + +public abstract class Heap(GraphicsContext context, HeapDesc desc) : GraphicsResource(context) +{ + private HeapDesc desc = desc; + + public ref readonly HeapDesc Desc => ref desc; + + public Buffer CreateBuffer(ulong offsetInBytes, BufferDesc desc) + { + Context.ValidationLayer?.ValidateDesc(desc); + + return CreateBufferImpl(offsetInBytes, desc); + } + + public Texture CreateTexture(ulong offsetInBytes, TextureDesc desc) + { + Context.ValidationLayer?.ValidateDesc(desc); + + return CreateTextureImpl(offsetInBytes, desc); + } + + protected abstract Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc); + + protected abstract Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc); +} diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs index 9f16718f..e2b60e27 100644 --- a/sources/Zenith.NET/Structs/CommandSubmission.cs +++ b/sources/Zenith.NET/Structs/CommandSubmission.cs @@ -1,10 +1,10 @@ namespace Zenith.NET; -public readonly struct CommandSubmission +public readonly struct CommandSubmission(CommandQueue? queue, ulong value) { - public CommandQueue? Queue { get; init; } + public readonly CommandQueue? Queue = queue; - public ulong Value { get; init; } + public readonly ulong Value = value; public void Wait() { diff --git a/sources/Zenith.NET/Structs/HeapDesc.cs b/sources/Zenith.NET/Structs/HeapDesc.cs new file mode 100644 index 00000000..a15eae1e --- /dev/null +++ b/sources/Zenith.NET/Structs/HeapDesc.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public struct HeapDesc +{ + public HeapType Type; + + public ulong SizeInBytes; +} diff --git a/sources/Zenith.NET/Structs/MappedMemory.cs b/sources/Zenith.NET/Structs/MappedMemory.cs index da67088a..d9334119 100644 --- a/sources/Zenith.NET/Structs/MappedMemory.cs +++ b/sources/Zenith.NET/Structs/MappedMemory.cs @@ -1,8 +1,8 @@ namespace Zenith.NET; -public readonly struct MappedMemory +public readonly struct MappedMemory(nint pointer, uint sizeInBytes) { - public nint Pointer { get; init; } + public readonly nint Pointer = pointer; - public uint SizeInBytes { get; init; } + public readonly uint SizeInBytes = sizeInBytes; } diff --git a/sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs b/sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs new file mode 100644 index 00000000..8fa4f3ea --- /dev/null +++ b/sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public readonly struct ResourceSizeAndAlignment(ulong sizeInBytes, ulong alignmentInBytes) +{ + public readonly ulong SizeInBytes = sizeInBytes; + + public readonly ulong AlignmentInBytes = alignmentInBytes; +} diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index 5eed999d..e2fb7e16 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -43,6 +43,12 @@ internal void ValidateDesc(SwapChainDesc desc) } } + internal void ValidateDesc(HeapDesc desc) + { + CheckEnum("HeapDesc.Type", desc.Type); + CheckGreaterThanZero("HeapDesc.SizeInBytes", desc.SizeInBytes); + } + internal void ValidateDesc(BufferDesc desc) { CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); From 7d4897d87f4bf55eddba263165e28aa026ce7fe4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 22 May 2026 19:10:53 +0800 Subject: [PATCH 085/466] Add heap and size/alignment method stubs to DXGraphicsContext Added CreateHeapImpl, GetSizeAndAlignmentImpl(BufferDesc), and GetSizeAndAlignmentImpl(TextureDesc) method overrides to DXGraphicsContext. All new methods currently throw NotImplementedException. --- sources/Zenith.NET.DirectX12/DXGraphicsContext.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 1b3fb70f..8928ce4a 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -139,6 +139,21 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) throw new NotImplementedException(); } + protected override Heap CreateHeapImpl(HeapDesc desc) + { + throw new NotImplementedException(); + } + + protected override ResourceSizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) + { + throw new NotImplementedException(); + } + + protected override ResourceSizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) + { + throw new NotImplementedException(); + } + protected override Buffer CreateBufferImpl(BufferDesc desc) { throw new NotImplementedException(); From 83bb2af9d85bf5cc3a4a39f6399140baa46b8ce8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 22 May 2026 19:17:29 +0800 Subject: [PATCH 086/466] Make CommandQueue non-nullable in CommandSubmission Changed CommandSubmission to require a non-null CommandQueue. Removed null checks and simplified the Wait() method to always call Queue.Wait(Value) directly. --- sources/Zenith.NET/Structs/CommandSubmission.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs index e2b60e27..9311fc73 100644 --- a/sources/Zenith.NET/Structs/CommandSubmission.cs +++ b/sources/Zenith.NET/Structs/CommandSubmission.cs @@ -1,13 +1,13 @@ namespace Zenith.NET; -public readonly struct CommandSubmission(CommandQueue? queue, ulong value) +public readonly struct CommandSubmission(CommandQueue queue, ulong value) { - public readonly CommandQueue? Queue = queue; + public readonly CommandQueue Queue = queue; public readonly ulong Value = value; public void Wait() { - Queue?.Wait(Value); + Queue.Wait(Value); } } From deea9f3c8ad1d2d197c1b7f856c6293bca8be8af Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 22 May 2026 19:21:39 +0800 Subject: [PATCH 087/466] Replace ResourceSizeAndAlignment with SizeAndAlignment Renamed ResourceSizeAndAlignment to SizeAndAlignment and updated all related method signatures and usages in GraphicsContext and DXGraphicsContext. Removed ResourceSizeAndAlignment.cs and added SizeAndAlignment.cs with the new struct definition. --- sources/Zenith.NET.DirectX12/DXGraphicsContext.cs | 4 ++-- sources/Zenith.NET/GraphicsContext.cs | 8 ++++---- .../{ResourceSizeAndAlignment.cs => SizeAndAlignment.cs} | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename sources/Zenith.NET/Structs/{ResourceSizeAndAlignment.cs => SizeAndAlignment.cs} (61%) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 8928ce4a..ebb752f5 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -144,12 +144,12 @@ protected override Heap CreateHeapImpl(HeapDesc desc) throw new NotImplementedException(); } - protected override ResourceSizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { throw new NotImplementedException(); } - protected override ResourceSizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { throw new NotImplementedException(); } diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 9ab11663..4e4be412 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -60,14 +60,14 @@ public Heap CreateHeap(HeapDesc desc) return CreateHeapImpl(desc); } - public ResourceSizeAndAlignment GetSizeAndAlignment(BufferDesc desc) + public SizeAndAlignment GetSizeAndAlignment(BufferDesc desc) { ValidationLayer?.ValidateDesc(desc); return GetSizeAndAlignmentImpl(desc); } - public ResourceSizeAndAlignment GetSizeAndAlignment(TextureDesc desc) + public SizeAndAlignment GetSizeAndAlignment(TextureDesc desc) { ValidationLayer?.ValidateDesc(desc); @@ -189,9 +189,9 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract Heap CreateHeapImpl(HeapDesc desc); - protected abstract ResourceSizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc); + protected abstract SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc); - protected abstract ResourceSizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc); + protected abstract SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc); protected abstract Buffer CreateBufferImpl(BufferDesc desc); diff --git a/sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs b/sources/Zenith.NET/Structs/SizeAndAlignment.cs similarity index 61% rename from sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs rename to sources/Zenith.NET/Structs/SizeAndAlignment.cs index 8fa4f3ea..d3b6373e 100644 --- a/sources/Zenith.NET/Structs/ResourceSizeAndAlignment.cs +++ b/sources/Zenith.NET/Structs/SizeAndAlignment.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public readonly struct ResourceSizeAndAlignment(ulong sizeInBytes, ulong alignmentInBytes) +public readonly struct SizeAndAlignment(ulong sizeInBytes, ulong alignmentInBytes) { public readonly ulong SizeInBytes = sizeInBytes; From 3e87b58158d247ecb68e099af0e2d15914287369 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 22 May 2026 19:25:53 +0800 Subject: [PATCH 088/466] Update MAUI and WindowsAppSDK package versions Updated Microsoft.Maui.Controls from 10.0.60 to 10.0.70. Updated Microsoft.WindowsAppSDK from 2.0.1 to 2.1.3. --- sources/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index ec3a0ee3..86c36453 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -8,8 +8,8 @@ - - + + From 8ffe49c14497ba72965687df9ba136fbb804122f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 23 May 2026 13:41:03 +0800 Subject: [PATCH 089/466] Add DXBuffer and refactor resource and validation logic Implemented DXBuffer for DirectX 12 resource management. Refactored Buffer to make Map() abstract and removed MapImpl(). Updated DXDescriptorToken to use readonly fields. Removed validation checks from several methods for simplification. GraphicsResource now always sets resource name on Name change. Added global using for DxRange and made minor formatting improvements. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 47 +++++++++++++++++++ .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 8 ++-- sources/Zenith.NET.DirectX12/Extensions.cs | 2 +- sources/Zenith.NET.DirectX12/Usings.cs | 1 + sources/Zenith.NET/Buffer.cs | 12 +---- sources/Zenith.NET/GraphicsResource.cs | 5 +- sources/Zenith.NET/QueryHeap.cs | 5 -- sources/Zenith.NET/SwapChain.cs | 7 --- 8 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXBuffer.cs diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs new file mode 100644 index 00000000..4a6f3b64 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -0,0 +1,47 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXBuffer : Buffer +{ + public ComPtr Resource; + + public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc) + { + } + + public override ResourceHandle UniformHandle { get; } + + public override ResourceHandle StorageReadOnlyHandle { get; } + + public override ResourceHandle StorageReadWriteHandle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + public override MappedMemory Map() + { + void* pointer; + Resource.Map(0, (DxRange*)null, &pointer).Success(); + + return new((nint)pointer, Desc.SizeInBytes); + } + + public override void Unmap() + { + Resource.Unmap(0, (DxRange*)null); + } + + protected override void SetResourceName(string name) + { + Resource.SetName(name).Success(); + } + + protected override void Destroy() + { + Resource.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index 8b3303c9..b3791ce1 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -4,11 +4,11 @@ namespace Zenith.NET.DirectX12; internal readonly struct DXDescriptorToken(uint slot, uint incrementSize, CpuDescriptorHandle cpuStartHandle, GpuDescriptorHandle gpuStartHandle) { - public uint Slot => slot; + public readonly uint Slot = slot; - public ResourceHandle ResourceHandle => new(slot, 0); + public readonly ResourceHandle ResourceHandle = new(slot, 0); - public CpuDescriptorHandle CpuHandle => new() { Ptr = (slot * incrementSize) + cpuStartHandle.Ptr }; + public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = (slot * incrementSize) + cpuStartHandle.Ptr }; - public GpuDescriptorHandle GpuHandle => new() { Ptr = (slot * incrementSize) + gpuStartHandle.Ptr }; + public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = (slot * incrementSize) + gpuStartHandle.Ptr }; } \ No newline at end of file diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index f3a086cf..d24c27b0 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -2,7 +2,7 @@ namespace Zenith.NET.DirectX12; -public static unsafe class Extensions +public static class Extensions { extension(GraphicsContext) { diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index bf2480a1..6ee3818a 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1 +1,2 @@ global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; +global using DxRange = Silk.NET.Direct3D12.Range; diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index d760962d..35362bdd 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -12,15 +12,7 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public abstract ResourceHandle StorageReadWriteHandle { get; } - public MappedMemory Map() - { - if (Context.ValidationLayer?.ValidateMap(this) is false) - { - return default; - } - - return MapImpl(); - } + public abstract MappedMemory Map(); public abstract void Unmap(); @@ -67,6 +59,4 @@ public void Download(uint offsetInBytes, BufferData data) commandBuffer.Submit().Wait(); } } - - protected abstract MappedMemory MapImpl(); } diff --git a/sources/Zenith.NET/GraphicsResource.cs b/sources/Zenith.NET/GraphicsResource.cs index 5590d806..3adfdf79 100644 --- a/sources/Zenith.NET/GraphicsResource.cs +++ b/sources/Zenith.NET/GraphicsResource.cs @@ -11,10 +11,7 @@ public string Name { field = value; - if (!string.IsNullOrWhiteSpace(value)) - { - SetResourceName(value); - } + SetResourceName(value); } } } = string.Empty; diff --git a/sources/Zenith.NET/QueryHeap.cs b/sources/Zenith.NET/QueryHeap.cs index f2bc1a54..1b4ddd5e 100644 --- a/sources/Zenith.NET/QueryHeap.cs +++ b/sources/Zenith.NET/QueryHeap.cs @@ -8,11 +8,6 @@ public abstract class QueryHeap(GraphicsContext context, QueryHeapDesc desc) : G public void GetResults(Span results, uint startIndex) { - if (results.Length is 0) - { - return; - } - GetResultsImpl(results, startIndex); } diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 3c44aa7b..34cc8269 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -16,11 +16,6 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public void Resize(uint width, uint height) { - if (Context.ValidationLayer?.ValidateResize(width, height) is false) - { - return; - } - desc.Surface.Width = width; desc.Surface.Height = height; @@ -31,8 +26,6 @@ public void Resize(uint width, uint height) public void Refresh(Surface surface) { - Context.ValidationLayer?.ValidateRefresh(surface); - desc.Surface = surface; RefreshImpl(); From 52f41ba8585081fa660e74c6d7165949684f191c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 23 May 2026 14:06:11 +0800 Subject: [PATCH 090/466] Remove framework-level validation from API surface All framework-side validation logic and checks have been removed from the core API, including the `ValidationLayer` implementation and all related method calls in `GraphicsContext`, `CommandBuffer`, and `Heap`. Only backend/native validation messages are now reported via `ValidationMessage`. The `ValidationMessageEventArgs` and documentation have been updated to reflect this change. --- documents/docs/best-practices.md | 2 +- sources/Zenith.NET/CommandBuffer.cs | 250 +-- sources/Zenith.NET/Enums/MessageSource.cs | 8 - sources/Zenith.NET/GraphicsContext.cs | 28 - sources/Zenith.NET/Heap.cs | 4 - sources/Zenith.NET/ValidationLayer.cs | 1704 +---------------- .../Zenith.NET/ValidationMessageEventArgs.cs | 4 +- 7 files changed, 19 insertions(+), 1981 deletions(-) delete mode 100644 sources/Zenith.NET/Enums/MessageSource.cs diff --git a/documents/docs/best-practices.md b/documents/docs/best-practices.md index ce4224ff..295caed6 100644 --- a/documents/docs/best-practices.md +++ b/documents/docs/best-practices.md @@ -71,7 +71,7 @@ file struct Constants ## Debugging - **Enable the validation layer** during development: `GraphicsContext.Create*(useValidationLayer: true)`. -- **Subscribe to `ValidationMessage`** to capture both framework-level and driver-level diagnostics. +- **Subscribe to `ValidationMessage`** to capture driver-level diagnostics emitted by the backend's native validation layer. - **Use debug events** to annotate command streams for GPU debugging tools (RenderDoc, PIX, Xcode GPU Debugger): ```csharp diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index c3e92647..74da22be 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -20,87 +20,41 @@ public void MemoryBarrier() public void MemoryBarrier(Buffer buffer) { - if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.buffer", buffer) is false) - { - return; - } - MemoryBarrierImpl(buffer); } public void MemoryBarrier(Texture texture) { - if (Context.ValidationLayer?.ValidateResource("MemoryBarrier.texture", texture) is false) - { - return; - } - MemoryBarrierImpl(texture); } public void MemoryBarrier(ReadOnlySpan resources) { - for (int i = 0; i < resources.Length; i++) - { - if (Context.ValidationLayer?.ValidateResource($"MemoryBarrier.resources[{i}]", resources[i]) is false) - { - return; - } - } - MemoryBarrierImpl(resources); } public void AliasingBarrier(Buffer buffer) { - if (Context.ValidationLayer?.ValidateResource("AliasingBarrier.buffer", buffer) is false) - { - return; - } - AliasingBarrierImpl(buffer); } public void AliasingBarrier(Texture texture) { - if (Context.ValidationLayer?.ValidateResource("AliasingBarrier.texture", texture) is false) - { - return; - } - AliasingBarrierImpl(texture); } public void AliasingBarrier(ReadOnlySpan resources) { - for (int i = 0; i < resources.Length; i++) - { - if (Context.ValidationLayer?.ValidateResource($"AliasingBarrier.resources[{i}]", resources[i]) is false) - { - return; - } - } - AliasingBarrierImpl(resources); } public void Transition(Texture texture, TextureSubresource subresource, TextureState state) { - if (Context.ValidationLayer?.ValidateTransition(texture, subresource, state) is false) - { - return; - } - TransitionImpl(texture, subresource, state); } public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) { - if (Context.ValidationLayer?.ValidateUpload(buffer, offsetInBytes, data) is false) - { - return; - } - Buffer stagingBuffer = Context.Uploader.Buffer(this, data.SizeInBytes); stagingBuffer.Upload(0, data); @@ -109,11 +63,6 @@ public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { - if (Context.ValidationLayer?.ValidateDownload(buffer, offsetInBytes, data) is false) - { - return; - } - Buffer stagingBuffer = Context.Downloader.Buffer(this, data); CopyBuffer(buffer, offsetInBytes, stagingBuffer, 0, data.SizeInBytes); @@ -121,11 +70,6 @@ public void Download(Buffer buffer, uint offsetInBytes, BufferData data) public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - if (Context.ValidationLayer?.ValidateUpload(texture, subresource, offset, extent, data) is false) - { - return; - } - Buffer stagingBuffer = Context.Uploader.Buffer(this, data.Layout.SizeInBytes); stagingBuffer.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); @@ -134,11 +78,6 @@ public void Upload(Texture texture, TextureSubresource subresource, Offset3D off public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - if (Context.ValidationLayer?.ValidateDownload(texture, subresource, offset, extent, data) is false) - { - return; - } - Buffer stagingBuffer = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); CopyTextureToBuffer(texture, subresource, offset, extent, stagingBuffer, 0, data.Layout); @@ -146,77 +85,41 @@ public void Download(Texture texture, TextureSubresource subresource, Offset3D o public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { - if (Context.ValidationLayer?.ValidateCopyBuffer(src, srcOffsetInBytes, dst, dstOffsetInBytes, sizeInBytes) is false) - { - return; - } - CopyBufferImpl(src, srcOffsetInBytes, dst, dstOffsetInBytes, sizeInBytes); } public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - if (Context.ValidationLayer?.ValidateCopyBufferToTexture(src, srcOffsetInBytes, srcLayout, dst, dstSubresource, dstOffset, dstExtent) is false) - { - return; - } - CopyBufferToTextureImpl(src, srcOffsetInBytes, srcLayout, dst, dstSubresource, dstOffset, dstExtent); } public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - if (Context.ValidationLayer?.ValidateCopyTexture(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent) is false) - { - return; - } - CopyTextureImpl(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent); } public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) { - if (Context.ValidationLayer?.ValidateCopyTextureToBuffer(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstLayout) is false) - { - return; - } - CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstLayout); } public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { - if (Context.ValidationLayer?.ValidateResolveTexture(src, srcSubresource, dst, dstSubresource) is false) - { - return; - } - ResolveTextureImpl(src, srcSubresource, dst, dstSubresource); } public BottomLevelAccelerationStructure BuildAccelerationStructure(BottomLevelAccelerationStructureDesc desc) { - Context.ValidationLayer?.ValidateDesc(desc); - return BuildAccelerationStructureImpl(desc); } public TopLevelAccelerationStructure BuildAccelerationStructure(TopLevelAccelerationStructureDesc desc) { - Context.ValidationLayer?.ValidateDesc(desc); - return BuildAccelerationStructureImpl(desc); } public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { - if (Context.ValidationLayer?.ValidateResource("UpdateAccelerationStructure.accelerationStructure", accelerationStructure) is false) - { - return; - } - - Context.ValidationLayer?.ValidateDesc(accelerationStructure.Desc, newDesc); - UpdateAccelerationStructureImpl(accelerationStructure, newDesc); accelerationStructure.Refresh(newDesc); @@ -224,12 +127,8 @@ public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerati public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { - if (Context.ValidationLayer?.ValidateRenderPass(colorAttachments, depthStencilAttachment) is false) - { - return; - } - int attachmentCount = colorAttachments.Length > 0 ? colorAttachments.Length : depthStencilAttachment is null ? 0 : 1; + Span scissors = stackalloc Scissor[attachmentCount]; Span viewports = stackalloc Viewport[attachmentCount]; @@ -275,31 +174,16 @@ public void EndRenderPass() public void SetScissors(ReadOnlySpan scissors) { - if (Context.ValidationLayer?.ValidateScissors(scissors) is false) - { - return; - } - SetScissorsImpl(scissors); } public void SetViewports(ReadOnlySpan viewports) { - if (Context.ValidationLayer?.ValidateViewports(viewports) is false) - { - return; - } - SetViewportsImpl(viewports); } public void SetPipeline(GraphicsPipeline pipeline) { - if (Context.ValidationLayer?.ValidateResource("SetPipeline.pipeline", pipeline) is false) - { - return; - } - SetPipelineImpl(pipeline); currentPipeline = pipeline; @@ -307,11 +191,6 @@ public void SetPipeline(GraphicsPipeline pipeline) public void SetPipeline(ComputePipeline pipeline) { - if (Context.ValidationLayer?.ValidateResource("SetPipeline.pipeline", pipeline) is false) - { - return; - } - SetPipelineImpl(pipeline); currentPipeline = pipeline; @@ -319,11 +198,6 @@ public void SetPipeline(ComputePipeline pipeline) public void SetPipeline(MeshShadingPipeline pipeline) { - if (Context.ValidationLayer?.ValidateResource("SetPipeline.pipeline", pipeline) is false) - { - return; - } - SetPipelineImpl(pipeline); currentPipeline = pipeline; @@ -331,32 +205,17 @@ public void SetPipeline(MeshShadingPipeline pipeline) public void SetStencilReference(uint stencilReference) { - if (Context.ValidationLayer?.ValidateSetStencilReference(currentPipeline) is false) - { - return; - } - SetStencilReferenceImpl(stencilReference); } public void SetBlendConstant(Vector4 blendConstant) { - if (Context.ValidationLayer?.ValidateSetBlendConstant(currentPipeline, blendConstant) is false) - { - return; - } - SetBlendConstantImpl(blendConstant); } public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) { - if (!TryGetCurrentPipeline(nameof(SetVertexBuffer), out GraphicsPipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateSetVertexBuffer(buffer, offsetInBytes, slot, pipeline) is false) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -366,12 +225,7 @@ public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - if (!TryGetCurrentPipeline(nameof(SetIndexBuffer), out GraphicsPipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateSetIndexBuffer(buffer, offsetInBytes, indexFormat) is false) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -381,12 +235,7 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexF public void SetConstants(T data) where T : unmanaged, IConstantsLayout { - if (!TryGetCurrentPipeline(nameof(SetConstants), out Pipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateSetConstants() is false) + if (!TryGetCurrentPipeline(out Pipeline pipeline)) { return; } @@ -396,7 +245,7 @@ public void SetConstants(T data) where T : unmanaged, IConstantsLayout public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { - if (!TryGetCurrentPipeline(nameof(Draw), out GraphicsPipeline pipeline)) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -406,12 +255,7 @@ public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint fi public void DrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - if (!TryGetCurrentPipeline(nameof(DrawIndirect), out GraphicsPipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateDrawIndirect(indirectBuffer, offsetInBytes, drawCount) is false) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -421,7 +265,7 @@ public void DrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCou public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) { - if (!TryGetCurrentPipeline(nameof(DrawIndexed), out GraphicsPipeline pipeline)) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -431,12 +275,7 @@ public void DrawIndexed(uint indexCount, uint instanceCount, uint firstIndex, in public void DrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - if (!TryGetCurrentPipeline(nameof(DrawIndexedIndirect), out GraphicsPipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateDrawIndexedIndirect(indirectBuffer, offsetInBytes, drawCount) is false) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -446,7 +285,7 @@ public void DrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ) { - if (!TryGetCurrentPipeline(nameof(Dispatch), out ComputePipeline pipeline)) + if (!TryGetCurrentPipeline(out ComputePipeline pipeline)) { return; } @@ -456,12 +295,7 @@ public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ) public void DispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) { - if (!TryGetCurrentPipeline(nameof(DispatchIndirect), out ComputePipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateDispatchIndirect(indirectBuffer, offsetInBytes) is false) + if (!TryGetCurrentPipeline(out ComputePipeline pipeline)) { return; } @@ -471,7 +305,7 @@ public void DispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) public void DispatchMesh(uint groupCountX, uint groupCountY, uint groupCountZ) { - if (!TryGetCurrentPipeline(nameof(DispatchMesh), out MeshShadingPipeline pipeline)) + if (!TryGetCurrentPipeline(out MeshShadingPipeline pipeline)) { return; } @@ -481,12 +315,7 @@ public void DispatchMesh(uint groupCountX, uint groupCountY, uint groupCountZ) public void DispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) { - if (!TryGetCurrentPipeline(nameof(DispatchMeshIndirect), out MeshShadingPipeline pipeline)) - { - return; - } - - if (Context.ValidationLayer?.ValidateDispatchMeshIndirect(indirectBuffer, offsetInBytes, dispatchCount) is false) + if (!TryGetCurrentPipeline(out MeshShadingPipeline pipeline)) { return; } @@ -496,11 +325,6 @@ public void DispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint public void BeginQuery(QueryHeap queryHeap, uint index) { - if (Context.ValidationLayer?.ValidateBeginQuery(queryHeap, index) is false) - { - return; - } - if (queryHeap.Desc.Type is QueryType.Timestamp) { return; @@ -511,11 +335,6 @@ public void BeginQuery(QueryHeap queryHeap, uint index) public void EndQuery(QueryHeap queryHeap, uint index) { - if (Context.ValidationLayer?.ValidateEndQuery(queryHeap, index) is false) - { - return; - } - if (queryHeap.Desc.Type is QueryType.Timestamp) { return; @@ -526,11 +345,6 @@ public void EndQuery(QueryHeap queryHeap, uint index) public void WriteTimestamp(QueryHeap queryHeap, uint index) { - if (Context.ValidationLayer?.ValidateWriteTimestamp(queryHeap, index) is false) - { - return; - } - if (queryHeap.Desc.Type is not QueryType.Timestamp) { return; @@ -541,16 +355,6 @@ public void WriteTimestamp(QueryHeap queryHeap, uint index) public void BeginDebugEvent(string label) { - if (Context.ValidationLayer?.ValidateDebugLabel(nameof(BeginDebugEvent), label) is false) - { - return; - } - - if (string.IsNullOrWhiteSpace(label)) - { - return; - } - BeginDebugEventImpl(label); } @@ -561,16 +365,6 @@ public void EndDebugEvent() public void InsertDebugMarker(string label) { - if (Context.ValidationLayer?.ValidateDebugLabel(nameof(InsertDebugMarker), label) is false) - { - return; - } - - if (string.IsNullOrWhiteSpace(label)) - { - return; - } - InsertDebugMarkerImpl(label); } @@ -604,45 +398,29 @@ protected override void Destroy() currentPipeline = null; } - private bool TryGetCurrentPipeline(string commandName, out Pipeline pipeline) + private bool TryGetCurrentPipeline(out Pipeline pipeline) { if (currentPipeline is not null) { - if (Context.ValidationLayer?.ValidateResource($"{commandName}.pipeline", currentPipeline) is false) - { - pipeline = null!; - - return false; - } - pipeline = currentPipeline; return true; } - Context.ValidationLayer?.ValidateCurrentPipeline(commandName); pipeline = null!; return false; } - private bool TryGetCurrentPipeline(string commandName, out TPipeline pipeline) where TPipeline : Pipeline + private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPipeline : Pipeline { if (currentPipeline is TPipeline typedPipeline) { - if (Context.ValidationLayer?.ValidateResource($"{commandName}.pipeline", typedPipeline) is false) - { - pipeline = null!; - - return false; - } - pipeline = typedPipeline; return true; } - Context.ValidationLayer?.ValidateCurrentPipeline(commandName, currentPipeline); pipeline = null!; return false; diff --git a/sources/Zenith.NET/Enums/MessageSource.cs b/sources/Zenith.NET/Enums/MessageSource.cs deleted file mode 100644 index c0255dbb..00000000 --- a/sources/Zenith.NET/Enums/MessageSource.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public enum MessageSource -{ - Framework, - - GraphicsApi -} diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 4e4be412..9874f07d 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -48,99 +48,71 @@ protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) public SwapChain CreateSwapChain(SwapChainDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateSwapChainImpl(desc); } public Heap CreateHeap(HeapDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateHeapImpl(desc); } public SizeAndAlignment GetSizeAndAlignment(BufferDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return GetSizeAndAlignmentImpl(desc); } public SizeAndAlignment GetSizeAndAlignment(TextureDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return GetSizeAndAlignmentImpl(desc); } public Buffer CreateBuffer(BufferDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateBufferImpl(desc); } public BufferView CreateBufferView(BufferViewDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateBufferViewImpl(desc); } public Texture CreateTexture(TextureDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateTextureImpl(desc); } public TextureView CreateTextureView(TextureViewDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateTextureViewImpl(desc); } public Sampler CreateSampler(SamplerDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateSamplerImpl(desc); } public Shader CreateShader(ShaderDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateShaderImpl(desc); } public GraphicsPipeline CreateGraphicsPipeline(GraphicsPipelineDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateGraphicsPipelineImpl(desc); } public ComputePipeline CreateComputePipeline(ComputePipelineDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateComputePipelineImpl(desc); } public MeshShadingPipeline CreateMeshShadingPipeline(MeshShadingPipelineDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateMeshShadingPipelineImpl(desc); } public QueryHeap CreateQueryHeap(QueryHeapDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateQueryHeapImpl(desc); } diff --git a/sources/Zenith.NET/Heap.cs b/sources/Zenith.NET/Heap.cs index 289ee7e3..f8c7175f 100644 --- a/sources/Zenith.NET/Heap.cs +++ b/sources/Zenith.NET/Heap.cs @@ -8,15 +8,11 @@ public abstract class Heap(GraphicsContext context, HeapDesc desc) : GraphicsRes public Buffer CreateBuffer(ulong offsetInBytes, BufferDesc desc) { - Context.ValidationLayer?.ValidateDesc(desc); - return CreateBufferImpl(offsetInBytes, desc); } public Texture CreateTexture(ulong offsetInBytes, TextureDesc desc) { - Context.ValidationLayer?.ValidateDesc(desc); - return CreateTextureImpl(offsetInBytes, desc); } diff --git a/sources/Zenith.NET/ValidationLayer.cs b/sources/Zenith.NET/ValidationLayer.cs index e2fb7e16..c2f4dd14 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -1,1707 +1,9 @@ -using System.Numerics; - -namespace Zenith.NET; +namespace Zenith.NET; public abstract class ValidationLayer(GraphicsContext context) : GraphicsResource(context) { - private static readonly Dictionary ExpectedSurfaceHandleCount = new() - { - [SurfaceType.Win32] = 1, - [SurfaceType.Wayland] = 2, - [SurfaceType.Xlib] = 2, - [SurfaceType.Android] = 1, - [SurfaceType.Apple] = 1, - [SurfaceType.D3D11Interop] = 1 - }; - - protected void Report(MessageSource source, MessageSeverity severity, string message) - { - Context.OnValidationMessage(new(source, severity, message)); - } - - private void ReportError(string message) - { - Report(MessageSource.Framework, MessageSeverity.Error, message); - } - - private void ReportWarning(string message) - { - Report(MessageSource.Framework, MessageSeverity.Warning, message); - } - - #region Descriptor Validation - - internal void ValidateDesc(SwapChainDesc desc) - { - CheckSurface("SwapChainDesc.Surface", desc.Surface); - - CheckEnum("SwapChainDesc.ColorFormat", desc.ColorFormat); - - if (desc.DepthStencilFormat is { } format) - { - CheckEnum("SwapChainDesc.DepthStencilFormat", format); - } - } - - internal void ValidateDesc(HeapDesc desc) - { - CheckEnum("HeapDesc.Type", desc.Type); - CheckGreaterThanZero("HeapDesc.SizeInBytes", desc.SizeInBytes); - } - - internal void ValidateDesc(BufferDesc desc) - { - CheckGreaterThanZero("BufferDesc.SizeInBytes", desc.SizeInBytes); - - if (desc.StrideInBytes is 0) - { - ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); - } - - CheckEnum("BufferDesc.Access", desc.Access); - CheckFlags("BufferDesc.Usages", desc.Usages); - - if (desc.Access is BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly) - { - const BufferUsages GpuOnlyUsages = BufferUsages.StorageReadWrite - | BufferUsages.Indirect - | BufferUsages.AccelerationStructure; - - BufferUsages forbiddenUsages = desc.Usages & GpuOnlyUsages; - - if (forbiddenUsages is not BufferUsages.None) - { - ReportError(string.Format(ValidationMessages.UsagesIncompatibleWithAccess, "BufferDesc.Usages", forbiddenUsages, desc.Access)); - } - } - } - - internal void ValidateDesc(BufferViewDesc desc) - { - if (!CheckResource("BufferViewDesc.Buffer", desc.Buffer)) - { - return; - } - - CheckBufferRange("BufferViewDesc", desc.Buffer, desc.OffsetInBytes, desc.SizeInBytes); - - if (desc.StrideInBytes is 0) - { - ReportWarning(string.Format(ValidationMessages.IsZeroWarning, "BufferViewDesc.StrideInBytes", "structured buffer views")); - } - } - - internal void ValidateDesc(TextureDesc desc) - { - CheckEnum("TextureDesc.Type", desc.Type); - CheckEnum("TextureDesc.Format", desc.Format); - - if (desc.Width is 0 || desc.Height is 0 || desc.Depth is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc dimensions (Width, Height, Depth)")); - } - - CheckGreaterThanZero("TextureDesc.MipLevels", desc.MipLevels); - CheckGreaterThanZero("TextureDesc.ArrayLayers", desc.ArrayLayers); - - if (desc.Type is TextureType.Texture3D && desc.ArrayLayers is not 1) - { - ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", 1)); - } - - if (desc.Type is TextureType.TextureCube && desc.ArrayLayers is not ValidationConstants.CubeMapFaceCount) - { - ReportError(string.Format(ValidationMessages.MustBeEqualTo, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Type is TextureType.TextureCubeArray && desc.ArrayLayers % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureDesc.ArrayLayers", ValidationConstants.CubeMapFaceCount)); - } - - CheckEnum("TextureDesc.SampleCount", desc.SampleCount); - CheckFlags("TextureDesc.Usages", desc.Usages); - - if (desc.Usages is TextureUsages.None) - { - ReportWarning(string.Format(ValidationMessages.IsSetToNoneWarning, "TextureDesc.Usages")); - } - } - - internal void ValidateDesc(TextureViewDesc desc) - { - if (!CheckResource("TextureViewDesc.Texture", desc.Texture)) - { - return; - } - - CheckEnum("TextureViewDesc.Type", desc.Type); - CheckEnum("TextureViewDesc.Format", desc.Format); - - if (desc.Range.BaseMipLevel >= desc.Texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseMipLevel", "the number of mip levels in the texture")); - } - - if (desc.Range.LevelCount is 0 || desc.Range.BaseMipLevel + desc.Range.LevelCount > desc.Texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LevelCount", "the texture mip levels")); - } - - if (desc.Range.BaseArrayLayer >= desc.Texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.Range.BaseArrayLayer", "the number of array layers in the texture")); - } - - if (desc.Range.LayerCount is 0 || desc.Range.BaseArrayLayer + desc.Range.LayerCount > desc.Texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.Range.LayerCount", "the texture array layers")); - } - - if (desc.Type is TextureType.TextureCube) - { - if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Range.LayerCount is not ValidationConstants.CubeMapFaceCount) - { - ReportError(string.Format(ValidationMessages.MustDescribeACompleteCube, "TextureViewDesc.Range.LayerCount")); - } - } - - if (desc.Type is TextureType.TextureCubeArray) - { - if (desc.Range.BaseArrayLayer % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.BaseArrayLayer", ValidationConstants.CubeMapFaceCount)); - } - - if (desc.Range.LayerCount % ValidationConstants.CubeMapFaceCount is not 0) - { - ReportError(string.Format(ValidationMessages.MustBeAMultipleOf, "TextureViewDesc.Range.LayerCount", ValidationConstants.CubeMapFaceCount)); - } - } - } - - internal void ValidateDesc(SamplerDesc desc) - { - CheckEnum("SamplerDesc.MinFilter", desc.MinFilter); - CheckEnum("SamplerDesc.MagFilter", desc.MagFilter); - CheckEnum("SamplerDesc.MipFilter", desc.MipFilter); - CheckEnum("SamplerDesc.AddressU", desc.AddressU); - CheckEnum("SamplerDesc.AddressV", desc.AddressV); - CheckEnum("SamplerDesc.AddressW", desc.AddressW); - CheckEnum("SamplerDesc.CompareOp", desc.CompareOp); - - if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MaxAnisotropy", ValidationConstants.MaxAnisotropy)); - } - - if (desc.MinLod > desc.MaxLod) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "SamplerDesc.MinLod", "MaxLod")); - } - - CheckEnum("SamplerDesc.BorderColor", desc.BorderColor); - } - - internal void ValidateDesc(ShaderDesc desc) - { - CheckArrayNotEmpty("ShaderDesc.Bytecode", desc.Bytecode); - CheckStringNotWhitespace("ShaderDesc.EntryPoint", desc.EntryPoint); - CheckEnum("ShaderDesc.Stage", desc.Stage); - } - - internal void ValidateDesc(GraphicsPipelineDesc desc) - { - CheckResource("GraphicsPipelineDesc.VertexShader", desc.VertexShader); - CheckResource("GraphicsPipelineDesc.FragmentShader", desc.FragmentShader); - - if (desc.InputLayouts is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); - } - else - { - for (int index = 0; index < desc.InputLayouts.Length; index++) - { - CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{index}]", desc.InputLayouts[index]); - } - } - - CheckEnum("GraphicsPipelineDesc.PrimitiveTopology", desc.PrimitiveTopology); - CheckAttachmentFormats("GraphicsPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - CheckRenderState("GraphicsPipelineDesc.RenderState", desc.RenderState); - } - - internal void ValidateDesc(ComputePipelineDesc desc) - { - CheckResource("ComputePipelineDesc.ComputeShader", desc.ComputeShader); - } - - internal void ValidateDesc(MeshShadingPipelineDesc desc) - { - if (desc.TaskShader is not null) - { - CheckResource("MeshShadingPipelineDesc.TaskShader", desc.TaskShader); - } - - CheckResource("MeshShadingPipelineDesc.MeshShader", desc.MeshShader); - CheckResource("MeshShadingPipelineDesc.FragmentShader", desc.FragmentShader); - - if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) - { - ReportError(string.Format(ValidationMessages.MustBeOneOf, "MeshShadingPipelineDesc.PrimitiveTopology", "LineList, TriangleList")); - } - - CheckAttachmentFormats("MeshShadingPipelineDesc.AttachmentFormats", desc.AttachmentFormats); - CheckRenderState("MeshShadingPipelineDesc.RenderState", desc.RenderState); - } - - internal void ValidateDesc(QueryHeapDesc desc) - { - CheckEnum("QueryHeapDesc.Type", desc.Type); - CheckGreaterThanZero("QueryHeapDesc.Count", desc.Count); - } - - internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) - { - if (CheckArrayNotEmpty("BottomLevelAccelerationStructureDesc.Geometries", desc.Geometries)) - { - for (int index = 0; index < desc.Geometries.Length; index++) - { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{index}]", desc.Geometries[index]); - } - } - - CheckFlags("BottomLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) - { - if (CheckArrayNotEmpty("TopLevelAccelerationStructureDesc.Instances", desc.Instances)) - { - for (int index = 0; index < desc.Instances.Length; index++) - { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{index}]", desc.Instances[index]); - } - } - - CheckFlags("TopLevelAccelerationStructureDesc.BuildFlags", desc.BuildFlags); - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) - { - if (!oldDesc.BuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) - { - ReportError(string.Format(ValidationMessages.MustHaveFlag, "TopLevelAccelerationStructureDesc.BuildFlags", AccelerationStructureBuildFlags.AllowUpdate)); - } - - ValidateDesc(newDesc); - - if (newDesc.Instances is null) - { - return; - } - - if (oldDesc.Instances.Length != newDesc.Instances.Length) - { - ReportError(ValidationMessages.InstanceCountMustRemainSame); - } - } - - #endregion - - #region Resource Validation - - internal bool ValidateResource(string name, T? resource) where T : GraphicsResource - { - return CheckResource(name, resource); - } - - internal bool ValidateMap(Buffer buffer) - { - if (!CheckResource("Map.buffer", buffer)) - { - return false; - } - - if (buffer.Desc.Access is not (BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly)) - { - ReportError(string.Format(ValidationMessages.MustBeCpuAccessible, "Map.buffer")); - - return false; - } - - return true; - } - - #endregion - - #region SwapChain Validation - - internal bool ValidateResize(uint width, uint height) - { - bool isValid = CheckGreaterThanZero("Resize.width", width); - isValid &= CheckGreaterThanZero("Resize.height", height); - - return isValid; - } - - internal void ValidateRefresh(Surface surface) - { - CheckSurface("Refresh.surface", surface); - } - - #endregion - - #region Transition Validation - - internal bool ValidateTransition(Texture texture, TextureSubresource subresource, TextureState state) - { - bool isValid = CheckResource("Transition.texture", texture); - - if (isValid) - { - isValid &= CheckTextureSubresource("Transition.subresource", texture, subresource); - } - - isValid &= CheckEnum("Transition.state", state); - - return isValid; - } - - #endregion - - #region Transfer Validation - - internal bool ValidateUpload(Buffer buffer, uint offsetInBytes, BufferData data) - { - bool isValid = CheckResource("Upload.buffer", buffer); - isValid &= CheckBufferData("Upload.data", data); - - if (isValid) - { - isValid &= CheckBufferUsage("Upload.buffer", buffer, BufferUsages.CopyDst); - isValid &= CheckBufferRange("Upload.buffer", buffer, offsetInBytes, data.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateDownload(Buffer buffer, uint offsetInBytes, BufferData data) - { - bool isValid = CheckResource("Download.buffer", buffer); - isValid &= CheckBufferData("Download.data", data); - - if (isValid) - { - isValid &= CheckBufferUsage("Download.buffer", buffer, BufferUsages.CopySrc); - isValid &= CheckBufferRange("Download.buffer", buffer, offsetInBytes, data.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateUpload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) - { - bool isValid = CheckResource("Upload.texture", texture); - isValid &= CheckTextureData("Upload.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); - - if (isValid) - { - isValid &= CheckTextureUsage("Upload.texture", texture, TextureUsages.CopyDst); - isValid &= CheckTextureRange("Upload.texture", texture, subresource, offset, extent); - } - - return isValid; - } - - internal bool ValidateDownload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) - { - bool isValid = CheckResource("Download.texture", texture); - isValid &= CheckTextureData("Download.data", data, isValid ? texture.Desc.Format : PixelFormat.Unknown, extent); - - if (isValid) - { - isValid &= CheckTextureUsage("Download.texture", texture, TextureUsages.CopySrc); - isValid &= CheckTextureRange("Download.texture", texture, subresource, offset, extent); - } - - return isValid; - } - - internal bool ValidateCopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) - { - bool hasSrc = CheckResource("CopyBuffer.src", src); - bool hasDst = CheckResource("CopyBuffer.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckBufferUsage("CopyBuffer.src", src, BufferUsages.CopySrc); - isValid &= CheckBufferRange("CopyBuffer.src", src, srcOffsetInBytes, sizeInBytes); - } - - if (hasDst) - { - isValid &= CheckBufferUsage("CopyBuffer.dst", dst, BufferUsages.CopyDst); - isValid &= CheckBufferRange("CopyBuffer.dst", dst, dstOffsetInBytes, sizeInBytes); - } - - return isValid; - } - - internal bool ValidateCopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) - { - bool hasSrc = CheckResource("CopyBufferToTexture.src", src); - bool hasDst = CheckResource("CopyBufferToTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckBufferUsage("CopyBufferToTexture.src", src, BufferUsages.CopySrc); - isValid &= CheckTextureDataLayout("CopyBufferToTexture.srcLayout", srcLayout, hasDst ? dst.Desc.Format : PixelFormat.Unknown, dstExtent); - isValid &= CheckBufferRange("CopyBufferToTexture.src", src, srcOffsetInBytes, srcLayout.SizeInBytes); - } - - if (hasDst) - { - isValid &= CheckTextureUsage("CopyBufferToTexture.dst", dst, TextureUsages.CopyDst); - isValid &= CheckTextureRange("CopyBufferToTexture.dst", dst, dstSubresource, dstOffset, dstExtent); - } - - return isValid; - } - - internal bool ValidateCopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) - { - bool hasSrc = CheckResource("CopyTexture.src", src); - bool hasDst = CheckResource("CopyTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureUsage("CopyTexture.src", src, TextureUsages.CopySrc); - isValid &= CheckTextureRange("CopyTexture.src", src, srcSubresource, srcOffset, extent); - } - - if (hasDst) - { - isValid &= CheckTextureUsage("CopyTexture.dst", dst, TextureUsages.CopyDst); - isValid &= CheckTextureRange("CopyTexture.dst", dst, dstSubresource, dstOffset, extent); - } - - if (hasSrc && hasDst) - { - isValid &= CheckSameValue("CopyTexture.Format", src.Desc.Format, dst.Desc.Format); - isValid &= CheckSameValue("CopyTexture.SampleCount", src.Desc.SampleCount, dst.Desc.SampleCount); - } - - return isValid; - } - - internal bool ValidateCopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) - { - bool hasSrc = CheckResource("CopyTextureToBuffer.src", src); - bool hasDst = CheckResource("CopyTextureToBuffer.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureUsage("CopyTextureToBuffer.src", src, TextureUsages.CopySrc); - isValid &= CheckTextureRange("CopyTextureToBuffer.src", src, srcSubresource, srcOffset, srcExtent); - isValid &= CheckTextureDataLayout("CopyTextureToBuffer.dstLayout", dstLayout, src.Desc.Format, srcExtent); - } - - if (hasDst) - { - isValid &= CheckBufferUsage("CopyTextureToBuffer.dst", dst, BufferUsages.CopyDst); - isValid &= CheckBufferRange("CopyTextureToBuffer.dst", dst, dstOffsetInBytes, dstLayout.SizeInBytes); - } - - return isValid; - } - - internal bool ValidateResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) - { - bool hasSrc = CheckResource("ResolveTexture.src", src); - bool hasDst = CheckResource("ResolveTexture.dst", dst); - bool isValid = hasSrc && hasDst; - - if (hasSrc) - { - isValid &= CheckTextureSubresource("ResolveTexture.srcSubresource", src, srcSubresource); - - if (src.Desc.SampleCount is SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeMultisampled, "ResolveTexture.src")); - isValid = false; - } - } - - if (hasDst) - { - isValid &= CheckTextureSubresource("ResolveTexture.dstSubresource", dst, dstSubresource); - - if (dst.Desc.SampleCount is not SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeSingleSampled, "ResolveTexture.dst")); - isValid = false; - } - } - - if (hasSrc && hasDst) - { - isValid &= CheckSameValue("ResolveTexture.Format", src.Desc.Format, dst.Desc.Format); - isValid &= CheckSameMipExtent("ResolveTexture", src, srcSubresource, dst, dstSubresource); - } - - return isValid; - } - - #endregion - - #region Render Pass / Viewport Validation - - internal bool ValidateRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) - { - bool isValid = true; - bool hasExtent = false; - uint renderWidth = 0; - uint renderHeight = 0; - - if (colorAttachments.Length > ValidationConstants.MaxColorAttachments) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, "BeginRenderPass.colorAttachments.Length", ValidationConstants.MaxColorAttachments)); - isValid = false; - } - - if (colorAttachments.Length is 0 && depthStencilAttachment is null) - { - ReportError(string.Format(ValidationMessages.HasNoAttachments, "BeginRenderPass")); - isValid = false; - } - - for (int index = 0; index < colorAttachments.Length; index++) - { - isValid &= CheckColorAttachment($"BeginRenderPass.colorAttachments[{index}]", colorAttachments[index], ref hasExtent, ref renderWidth, ref renderHeight); - } - - if (depthStencilAttachment is { } attachment) - { - isValid &= CheckDepthStencilAttachment("BeginRenderPass.depthStencilAttachment", attachment, ref hasExtent, ref renderWidth, ref renderHeight); - } - - return isValid; - } - - internal bool ValidateScissors(ReadOnlySpan scissors) - { - bool isValid = true; - - if (scissors.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetScissors.scissors")); - return false; - } - - for (int index = 0; index < scissors.Length; index++) - { - isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Width", scissors[index].Width); - isValid &= CheckGreaterThanZero($"SetScissors.scissors[{index}].Height", scissors[index].Height); - } - - return isValid; - } - - internal bool ValidateViewports(ReadOnlySpan viewports) - { - bool isValid = true; - - if (viewports.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, "SetViewports.viewports")); - return false; - } - - for (int index = 0; index < viewports.Length; index++) - { - Viewport viewport = viewports[index]; - - isValid &= CheckFinite($"SetViewports.viewports[{index}].Width", viewport.Width); - isValid &= CheckFinite($"SetViewports.viewports[{index}].Height", viewport.Height); - isValid &= CheckFinite($"SetViewports.viewports[{index}].MinDepth", viewport.MinDepth); - isValid &= CheckFinite($"SetViewports.viewports[{index}].MaxDepth", viewport.MaxDepth); - - if (viewport.Width <= 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Width")); - isValid = false; - } - - if (viewport.Height <= 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"SetViewports.viewports[{index}].Height")); - isValid = false; - } - - if (viewport.MinDepth > viewport.MaxDepth) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"SetViewports.viewports[{index}].MinDepth", $"SetViewports.viewports[{index}].MaxDepth")); - isValid = false; - } - } - - return isValid; - } - - #endregion - - #region Pipeline State Validation - - internal bool ValidateCurrentPipeline(string commandName) - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); - - return false; - } - - internal bool ValidateCurrentPipeline(string commandName, Pipeline? currentPipeline) where TPipeline : Pipeline - { - if (currentPipeline is null) - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipeline, commandName)); - } - else - { - ReportError(string.Format(ValidationMessages.MustHaveCurrentPipelineType, commandName, typeof(TPipeline).Name, currentPipeline.GetType().Name)); - } - - return false; - } - - internal bool ValidateSetStencilReference(Pipeline? currentPipeline) - { - if (currentPipeline is null) - { - return ValidateCurrentPipeline("SetStencilReference"); - } - - if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) - { - return ValidateCurrentPipeline("SetStencilReference", currentPipeline); - } - - return true; - } - - internal bool ValidateSetBlendConstant(Pipeline? currentPipeline, Vector4 blendConstant) - { - bool isValid = true; - - if (currentPipeline is null) - { - isValid = ValidateCurrentPipeline("SetBlendConstant"); - } - else if (currentPipeline is not GraphicsPipeline and not MeshShadingPipeline) - { - isValid = ValidateCurrentPipeline("SetBlendConstant", currentPipeline); - } - - isValid &= CheckFinite("SetBlendConstant.blendConstant.X", blendConstant.X); - isValid &= CheckFinite("SetBlendConstant.blendConstant.Y", blendConstant.Y); - isValid &= CheckFinite("SetBlendConstant.blendConstant.Z", blendConstant.Z); - isValid &= CheckFinite("SetBlendConstant.blendConstant.W", blendConstant.W); - - return isValid; - } - - internal bool ValidateSetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot, GraphicsPipeline pipeline) - { - bool isValid = CheckResource("SetVertexBuffer.buffer", buffer); - - if (isValid) - { - isValid &= CheckBufferUsage("SetVertexBuffer.buffer", buffer, BufferUsages.Vertex); - isValid &= CheckBufferOffset("SetVertexBuffer.buffer", buffer, offsetInBytes); - } - - if (pipeline.Desc.InputLayouts is not null && slot >= pipeline.Desc.InputLayouts.Length) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, "SetVertexBuffer.slot", "GraphicsPipelineDesc.InputLayouts.Length")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateSetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) - { - bool isValid = CheckResource("SetIndexBuffer.buffer", buffer); - isValid &= CheckEnum("SetIndexBuffer.indexFormat", indexFormat); - - if (isValid) - { - isValid &= CheckBufferUsage("SetIndexBuffer.buffer", buffer, BufferUsages.Index); - isValid &= CheckBufferOffset("SetIndexBuffer.buffer", buffer, offsetInBytes); - } - - return isValid; - } - - internal bool ValidateSetConstants() where T : unmanaged, IConstantsLayout - { - uint sizeInBytes = Context.GraphicsApi switch - { - GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, - GraphicsApi.Metal => T.MetalSizeInBytes, - GraphicsApi.Vulkan => T.VulkanSizeInBytes, - _ => 0 - }; - - if (sizeInBytes is 0) - { - ReportError(string.Format(ValidationMessages.MustHaveNonZeroConstantsSize, typeof(T).Name, Context.GraphicsApi)); - - return false; - } - - return true; - } - - #endregion - - #region Indirect / Query / Debug Validation - - internal bool ValidateDrawIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - return ValidateIndirectBuffer(nameof(CommandBuffer.DrawIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawArgsSizeInBytes, drawCount); - } - - internal bool ValidateDrawIndexedIndirect(Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - return ValidateIndirectBuffer(nameof(CommandBuffer.DrawIndexedIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDrawIndexedArgsSizeInBytes, drawCount); - } - - internal bool ValidateDispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) + protected void Report(MessageSeverity severity, string message) { - return ValidateIndirectBuffer(nameof(CommandBuffer.DispatchIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchArgsSizeInBytes, 1); + Context.OnValidationMessage(new(severity, message)); } - - internal bool ValidateDispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) - { - return ValidateIndirectBuffer(nameof(CommandBuffer.DispatchMeshIndirect), indirectBuffer, offsetInBytes, ValidationConstants.IndirectDispatchMeshArgsSizeInBytes, dispatchCount); - } - - private bool ValidateIndirectBuffer(string commandName, Buffer indirectBuffer, uint offsetInBytes, uint argsSizeInBytes, uint drawOrDispatchCount) - { - bool isValid = CheckResource($"{commandName}.indirectBuffer", indirectBuffer); - - if (isValid) - { - isValid &= CheckBufferUsage($"{commandName}.indirectBuffer", indirectBuffer, BufferUsages.Indirect); - isValid &= CheckBufferRange($"{commandName}.indirectBuffer", indirectBuffer, offsetInBytes, (ulong)argsSizeInBytes * drawOrDispatchCount, allowZeroSize: true); - } - - return isValid; - } - - internal bool ValidateBeginQuery(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("BeginQuery", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "BeginQuery")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateEndQuery(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("EndQuery", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustNotBeTimestampQuery, "EndQuery")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateWriteTimestamp(QueryHeap queryHeap, uint index) - { - bool isValid = CheckQuery("WriteTimestamp", queryHeap, index); - - if (isValid && queryHeap.Desc.Type is not QueryType.Timestamp) - { - ReportError(string.Format(ValidationMessages.MustBeTimestampQuery, "WriteTimestamp")); - isValid = false; - } - - return isValid; - } - - internal bool ValidateDebugLabel(string commandName, string? label) - { - return CheckStringNotWhitespace($"{commandName}.label", label); - } - - #endregion - - #region Primitive Checks - - private bool CheckResource(string name, T? resource) where T : GraphicsResource - { - if (resource is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, name)); - - return false; - } - - if (resource.IsDisposed) - { - ReportError(string.Format(ValidationMessages.MustNotBeDisposed, name)); - - return false; - } - - return true; - } - - private bool CheckArrayNotEmpty(string name, T[]? array) - { - if (array is null || array.Length is 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrEmpty, name)); - - return false; - } - - return true; - } - - private bool CheckStringNotWhitespace(string name, string? value) - { - if (string.IsNullOrWhiteSpace(value)) - { - ReportError(string.Format(ValidationMessages.MustNotBeNullOrWhitespace, name)); - - return false; - } - - return true; - } - - private bool CheckEnum(string name, T value) where T : struct, Enum - { - if (!Enum.IsDefined(value)) - { - ReportError(string.Format(ValidationMessages.HasInvalidValue, name, value)); - - return false; - } - - return true; - } - - private bool CheckFlags(string name, T value) where T : struct, Enum - { - ulong valueBits = unchecked((ulong)Convert.ToInt64(value)); - ulong validBits = 0; - - foreach (T definedValue in Enum.GetValues()) - { - validBits |= unchecked((ulong)Convert.ToInt64(definedValue)); - } - - if ((valueBits & ~validBits) is not 0) - { - ReportError(string.Format(ValidationMessages.HasInvalidValue, name, value)); - - return false; - } - - return true; - } - - private bool CheckGreaterThanZero(string name, T value) where T : struct, INumber - { - if (value <= T.Zero) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, name)); - - return false; - } - - return true; - } - - private bool CheckFinite(string name, float value) - { - if (!float.IsFinite(value)) - { - ReportError(string.Format(ValidationMessages.MustBeFinite, name)); - - return false; - } - - return true; - } - - private bool CheckSameValue(string name, T first, T second) - { - if (!EqualityComparer.Default.Equals(first, second)) - { - ReportError(string.Format(ValidationMessages.MustHaveSameValue, name, first, second)); - - return false; - } - - return true; - } - - #endregion - - #region Buffer Checks - - private bool CheckBufferData(string name, BufferData data) - { - bool isValid = true; - - if (data.Pointer == 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeZero, $"{name}.Pointer")); - isValid = false; - } - - isValid &= CheckGreaterThanZero($"{name}.SizeInBytes", data.SizeInBytes); - - return isValid; - } - - private bool CheckBufferUsage(string name, Buffer buffer, BufferUsages requiredUsage) - { - if (!buffer.Desc.Usages.HasFlag(requiredUsage)) - { - ReportError(string.Format(ValidationMessages.MustHaveUsage, name, requiredUsage)); - - return false; - } - - return true; - } - - private bool CheckBufferOffset(string name, Buffer buffer, uint offsetInBytes) - { - if (offsetInBytes > buffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.OffsetInBytes", name)); - - return false; - } - - return true; - } - - private bool CheckBufferRange(string name, Buffer buffer, uint offsetInBytes, ulong sizeInBytes, bool allowZeroSize = false) - { - bool isValid = CheckBufferOffset(name, buffer, offsetInBytes); - - if (!allowZeroSize && sizeInBytes is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name}.SizeInBytes")); - isValid = false; - } - - if ((ulong)offsetInBytes + sizeInBytes > buffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, name, "the buffer")); - isValid = false; - } - - return isValid; - } - - #endregion - - #region Texture Checks - - private bool CheckTextureData(string name, TextureData data, PixelFormat format, Extent3D extent) - { - bool isValid = true; - - if (data.Pointer == 0) - { - ReportError(string.Format(ValidationMessages.MustNotBeZero, $"{name}.Pointer")); - isValid = false; - } - - isValid &= CheckTextureDataLayout($"{name}.Layout", data.Layout, format, extent); - - return isValid; - } - - private bool CheckTextureDataLayout(string name, TextureDataLayout layout, PixelFormat format, Extent3D extent) - { - bool isValid = true; - - isValid &= CheckGreaterThanZero($"{name}.SizeInBytes", layout.SizeInBytes); - isValid &= CheckGreaterThanZero($"{name}.RowStrideInBytes", layout.RowStrideInBytes); - isValid &= CheckGreaterThanZero($"{name}.SliceStrideInBytes", layout.SliceStrideInBytes); - - if (!CheckTextureExtent($"{name}.Extent", extent)) - { - return false; - } - - uint minRowStrideInBytes = ZenithHelper.RowStrideInBytes(format, extent.Width, extent.Height); - - if (minRowStrideInBytes is 0) - { - return isValid; - } - - if (layout.RowStrideInBytes < minRowStrideInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanOrEqualTo, $"{name}.RowStrideInBytes", minRowStrideInBytes)); - isValid = false; - } - - (_, _, _, uint blocksHigh) = ZenithHelper.BlockLayout(format, extent.Width, extent.Height); - ulong minSliceStrideInBytes = (ulong)layout.RowStrideInBytes * blocksHigh; - - if (layout.SliceStrideInBytes < minSliceStrideInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanOrEqualTo, $"{name}.SliceStrideInBytes", minSliceStrideInBytes)); - isValid = false; - } - - ulong minSizeInBytes = extent.Depth is 0 ? 0 : ((ulong)layout.SliceStrideInBytes * (extent.Depth - 1)) + minSliceStrideInBytes; - - if (layout.SizeInBytes < minSizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanOrEqualTo, $"{name}.SizeInBytes", minSizeInBytes)); - isValid = false; - } - - return isValid; - } - - private bool CheckTextureUsage(string name, Texture texture, TextureUsages requiredUsage) - { - if (!texture.Desc.Usages.HasFlag(requiredUsage)) - { - ReportError(string.Format(ValidationMessages.MustHaveUsage, name, requiredUsage)); - - return false; - } - - return true; - } - - private bool CheckTextureSubresource(string name, Texture texture, TextureSubresource subresource) - { - bool isValid = true; - - if (subresource.MipLevel >= texture.Desc.MipLevels) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{name}.MipLevel", "TextureDesc.MipLevels")); - isValid = false; - } - - if (subresource.ArrayLayer >= texture.Desc.ArrayLayers) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{name}.ArrayLayer", "TextureDesc.ArrayLayers")); - isValid = false; - } - - return isValid; - } - - private bool CheckTextureExtent(string name, Extent3D extent) - { - if (extent.Width is 0 || extent.Height is 0 || extent.Depth is 0) - { - ReportError(string.Format(ValidationMessages.MustBeGreaterThanZero, $"{name} dimensions (Width, Height, Depth)")); - - return false; - } - - return true; - } - - private bool CheckTextureRange(string name, Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent) - { - bool isValid = CheckTextureSubresource($"{name}.Subresource", texture, subresource); - isValid &= CheckTextureExtent($"{name}.Extent", extent); - - if (!isValid) - { - return false; - } - - ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, subresource.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); - - if ((ulong)offset.X + extent.Width > mipWidth) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.X range", "the texture subresource width")); - isValid = false; - } - - if ((ulong)offset.Y + extent.Height > mipHeight) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.Y range", "the texture subresource height")); - isValid = false; - } - - if ((ulong)offset.Z + extent.Depth > mipDepth) - { - ReportError(string.Format(ValidationMessages.MustBeWithinResourceBounds, $"{name}.Z range", "the texture subresource depth")); - isValid = false; - } - - return isValid; - } - - private bool CheckSameMipExtent(string name, Texture first, TextureSubresource firstSubresource, Texture second, TextureSubresource secondSubresource) - { - if (!CheckTextureSubresource($"{name}.firstSubresource", first, firstSubresource) || !CheckTextureSubresource($"{name}.secondSubresource", second, secondSubresource)) - { - return false; - } - - ZenithHelper.MipDimensions(first.Desc.Width, first.Desc.Height, first.Desc.Depth, firstSubresource.MipLevel, out uint firstWidth, out uint firstHeight, out uint firstDepth); - ZenithHelper.MipDimensions(second.Desc.Width, second.Desc.Height, second.Desc.Depth, secondSubresource.MipLevel, out uint secondWidth, out uint secondHeight, out uint secondDepth); - - bool isValid = true; - isValid &= CheckSameValue($"{name}.Width", firstWidth, secondWidth); - isValid &= CheckSameValue($"{name}.Height", firstHeight, secondHeight); - isValid &= CheckSameValue($"{name}.Depth", firstDepth, secondDepth); - - return isValid; - } - - private bool CheckColorFormat(string name, PixelFormat format) - { - if (ZenithHelper.HasDepth(format) || ZenithHelper.HasStencil(format)) - { - ReportError(string.Format(ValidationMessages.MustNotBeDepthStencilFormat, name)); - - return false; - } - - return true; - } - - private bool CheckDepthStencilFormat(string name, PixelFormat format) - { - if (!ZenithHelper.HasDepth(format) && !ZenithHelper.HasStencil(format)) - { - ReportError(string.Format(ValidationMessages.MustBeDepthStencilFormat, name)); - - return false; - } - - return true; - } - - #endregion - - #region Render Pass Checks - - private bool CheckColorAttachment(string name, ColorAttachment attachment, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) - { - bool hasTexture = CheckResource($"{name}.Texture", attachment.Texture); - bool isValid = hasTexture; - - isValid &= CheckEnum($"{name}.LoadOp", attachment.LoadOp); - isValid &= CheckEnum($"{name}.StoreOp", attachment.StoreOp); - - if (hasTexture) - { - isValid &= CheckTextureUsage($"{name}.Texture", attachment.Texture, TextureUsages.ColorAttachment); - isValid &= CheckColorFormat($"{name}.Texture.Desc.Format", attachment.Texture.Desc.Format); - - if (CheckTextureSubresource($"{name}.Subresource", attachment.Texture, attachment.Subresource)) - { - isValid &= CheckRenderPassExtent(name, attachment.Texture, attachment.Subresource, ref hasExtent, ref renderWidth, ref renderHeight); - } - else - { - isValid = false; - } - } - - if (attachment.ResolveTexture is { } resolveTexture) - { - bool hasResolveTexture = CheckResource($"{name}.ResolveTexture", resolveTexture); - isValid &= hasResolveTexture; - - if (hasResolveTexture) - { - isValid &= CheckTextureUsage($"{name}.ResolveTexture", resolveTexture, TextureUsages.ColorAttachment); - isValid &= CheckColorFormat($"{name}.ResolveTexture.Desc.Format", resolveTexture.Desc.Format); - - if (hasTexture) - { - isValid &= CheckSameValue($"{name}.ResolveTexture.Desc.Format", attachment.Texture.Desc.Format, resolveTexture.Desc.Format); - - if (attachment.Texture.Desc.SampleCount is SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeMultisampled, $"{name}.Texture")); - isValid = false; - } - } - - if (resolveTexture.Desc.SampleCount is not SampleCount.Count1) - { - ReportError(string.Format(ValidationMessages.MustBeSingleSampled, $"{name}.ResolveTexture")); - isValid = false; - } - - if (CheckTextureSubresource($"{name}.ResolveSubresource", resolveTexture, attachment.ResolveSubresource)) - { - isValid &= CheckRenderPassExtent($"{name}.ResolveTexture", resolveTexture, attachment.ResolveSubresource, ref hasExtent, ref renderWidth, ref renderHeight); - } - else - { - isValid = false; - } - } - } - - return isValid; - } - - private bool CheckDepthStencilAttachment(string name, DepthStencilAttachment attachment, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) - { - bool hasTexture = CheckResource($"{name}.Texture", attachment.Texture); - bool isValid = hasTexture; - - isValid &= CheckEnum($"{name}.DepthLoadOp", attachment.DepthLoadOp); - isValid &= CheckEnum($"{name}.DepthStoreOp", attachment.DepthStoreOp); - isValid &= CheckEnum($"{name}.StencilLoadOp", attachment.StencilLoadOp); - isValid &= CheckEnum($"{name}.StencilStoreOp", attachment.StencilStoreOp); - - if (attachment.ClearDepth is < 0.0f or > 1.0f) - { - ReportError(string.Format(ValidationMessages.MustBeBetween, $"{name}.ClearDepth", 0.0f, 1.0f)); - isValid = false; - } - - if (hasTexture) - { - isValid &= CheckTextureUsage($"{name}.Texture", attachment.Texture, TextureUsages.DepthStencil); - isValid &= CheckDepthStencilFormat($"{name}.Texture.Desc.Format", attachment.Texture.Desc.Format); - - if (CheckTextureSubresource($"{name}.Subresource", attachment.Texture, attachment.Subresource)) - { - isValid &= CheckRenderPassExtent(name, attachment.Texture, attachment.Subresource, ref hasExtent, ref renderWidth, ref renderHeight); - } - else - { - isValid = false; - } - } - - return isValid; - } - - private bool CheckRenderPassExtent(string name, Texture texture, TextureSubresource subresource, ref bool hasExtent, ref uint renderWidth, ref uint renderHeight) - { - ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, subresource.MipLevel, out uint width, out uint height, out _); - - if (!hasExtent) - { - renderWidth = width; - renderHeight = height; - hasExtent = true; - - return true; - } - - bool isValid = true; - - if (width != renderWidth) - { - ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Width", width, renderWidth)); - isValid = false; - } - - if (height != renderHeight) - { - ReportError(string.Format(ValidationMessages.MustHaveSameValue, $"{name}.Height", height, renderHeight)); - isValid = false; - } - - return isValid; - } - - #endregion - - #region Pipeline State Checks - - private void CheckRenderState(string name, RenderState renderState) - { - CheckRasterizerState($"{name}.RasterizerState", renderState.RasterizerState); - CheckDepthStencilState($"{name}.DepthStencilState", renderState.DepthStencilState); - CheckBlendState($"{name}.BlendState", renderState.BlendState); - } - - private void CheckRasterizerState(string name, RasterizerState rasterizerState) - { - CheckEnum($"{name}.FillMode", rasterizerState.FillMode); - CheckEnum($"{name}.CullMode", rasterizerState.CullMode); - CheckEnum($"{name}.FrontFace", rasterizerState.FrontFace); - } - - private void CheckDepthStencilState(string name, DepthStencilState depthStencilState) - { - CheckEnum($"{name}.DepthCompareOp", depthStencilState.DepthCompareOp); - CheckStencilFaceState($"{name}.FrontFace", depthStencilState.FrontFace); - CheckStencilFaceState($"{name}.BackFace", depthStencilState.BackFace); - } - - private void CheckBlendState(string name, BlendState blendState) - { - CheckColorAttachmentBlendState($"{name}.ColorAttachment0", blendState.ColorAttachment0); - CheckColorAttachmentBlendState($"{name}.ColorAttachment1", blendState.ColorAttachment1); - CheckColorAttachmentBlendState($"{name}.ColorAttachment2", blendState.ColorAttachment2); - CheckColorAttachmentBlendState($"{name}.ColorAttachment3", blendState.ColorAttachment3); - CheckColorAttachmentBlendState($"{name}.ColorAttachment4", blendState.ColorAttachment4); - CheckColorAttachmentBlendState($"{name}.ColorAttachment5", blendState.ColorAttachment5); - CheckColorAttachmentBlendState($"{name}.ColorAttachment6", blendState.ColorAttachment6); - CheckColorAttachmentBlendState($"{name}.ColorAttachment7", blendState.ColorAttachment7); - } - - private void CheckStencilFaceState(string name, StencilFaceState faceState) - { - CheckEnum($"{name}.FailOp", faceState.FailOp); - CheckEnum($"{name}.DepthFailOp", faceState.DepthFailOp); - CheckEnum($"{name}.PassOp", faceState.PassOp); - CheckEnum($"{name}.CompareOp", faceState.CompareOp); - } - - private void CheckColorAttachmentBlendState(string name, ColorAttachmentBlendState blendState) - { - CheckEnum($"{name}.SrcRgbFactor", blendState.SrcRgbFactor); - CheckEnum($"{name}.DstRgbFactor", blendState.DstRgbFactor); - CheckEnum($"{name}.RgbOp", blendState.RgbOp); - CheckEnum($"{name}.SrcAlphaFactor", blendState.SrcAlphaFactor); - CheckEnum($"{name}.DstAlphaFactor", blendState.DstAlphaFactor); - CheckEnum($"{name}.AlphaOp", blendState.AlphaOp); - CheckFlags($"{name}.ColorWrites", blendState.ColorWrites); - } - - private void CheckAttachmentFormats(string name, AttachmentFormats attachmentFormats) - { - if (attachmentFormats.ColorFormats is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorFormats")); - - return; - } - - for (int index = 0; index < attachmentFormats.ColorFormats.Length; index++) - { - CheckEnum($"{name}.ColorFormats[{index}]", attachmentFormats.ColorFormats[index]); - } - - if (attachmentFormats.DepthStencilFormat is { } depthStencilFormat) - { - CheckEnum($"{name}.DepthStencilFormat", depthStencilFormat); - } - - CheckEnum($"{name}.SampleCount", attachmentFormats.SampleCount); - - if (attachmentFormats.ColorFormats.Length is 0 && attachmentFormats.DepthStencilFormat is null) - { - ReportWarning(string.Format(ValidationMessages.HasNoAttachments, name)); - } - } - - private void CheckInputLayout(string name, InputLayout inputLayout) - { - if (CheckArrayNotEmpty($"{name}.InputElements", inputLayout.InputElements)) - { - for (int index = 0; index < inputLayout.InputElements.Length; index++) - { - CheckInputElement($"{name}.InputElements[{index}]", inputLayout.InputElements[index]); - } - } - - CheckGreaterThanZero($"{name}.StrideInBytes", inputLayout.StrideInBytes); - } - - private void CheckInputElement(string name, InputElement inputElement) - { - CheckEnum($"{name}.Format", inputElement.Format); - CheckEnum($"{name}.Semantic", inputElement.Semantic); - } - - #endregion - - #region Surface / Query / Ray Tracing Checks - - private void CheckSurface(string name, Surface surface) - { - if (!CheckEnum($"{name}.Type", surface.Type)) - { - return; - } - - if (!ExpectedSurfaceHandleCount.TryGetValue(surface.Type, out int expectedHandleCount)) - { - ReportError(string.Format(ValidationMessages.HasUnsupportedSurfaceType, name, surface.Type)); - - return; - } - - if (surface.NativeHandles is null) - { - ReportError(string.Format(ValidationMessages.MustNotBeNull, $"{name}.NativeHandles")); - - return; - } - - if (surface.NativeHandles.Length != expectedHandleCount) - { - ReportError(string.Format(ValidationMessages.MustHaveExactlyNHandles, $"{name}.NativeHandles", expectedHandleCount, surface.Type)); - - return; - } - - for (int index = 0; index < surface.NativeHandles.Length; index++) - { - if (surface.NativeHandles[index] is 0) - { - if (expectedHandleCount is 1) - { - ReportError(string.Format(ValidationMessages.MustBeValidHandle, $"{name}.NativeHandles[0]", surface.Type)); - } - else - { - ReportError(string.Format(ValidationMessages.MustBeValidHandles, $"{name}.NativeHandles", surface.Type)); - } - - return; - } - } - - CheckGreaterThanZero($"{name}.Width", surface.Width); - CheckGreaterThanZero($"{name}.Height", surface.Height); - } - - private bool CheckQuery(string commandName, QueryHeap queryHeap, uint index) - { - bool isValid = CheckResource($"{commandName}.queryHeap", queryHeap); - - if (isValid) - { - isValid &= CheckEnum($"{commandName}.queryHeap.Desc.Type", queryHeap.Desc.Type); - - if (index >= queryHeap.Desc.Count) - { - ReportError(string.Format(ValidationMessages.MustBeLessThan, $"{commandName}.index", "QueryHeapDesc.Count")); - isValid = false; - } - } - - return isValid; - } - - private void CheckRayTracingGeometry(string name, RayTracingGeometry geometry) - { - if (!CheckEnum($"{name}.Type", geometry.Type)) - { - return; - } - - if (geometry.Type is RayTracingGeometryType.Triangle) - { - CheckRayTracingTriangleGeometry($"{name}.TriangleGeometry", geometry.TriangleGeometry); - } - - if (geometry.Type is RayTracingGeometryType.Aabb) - { - CheckRayTracingAabbGeometry($"{name}.AabbGeometry", geometry.AabbGeometry); - } - } - - private void CheckRayTracingTriangleGeometry(string name, RayTracingTriangleGeometry triangleGeometry) - { - bool hasVertexBuffer = CheckResource($"{name}.VertexBuffer", triangleGeometry.VertexBuffer); - bool hasIndexBuffer = triangleGeometry.IndexBuffer is not null && CheckResource($"{name}.IndexBuffer", triangleGeometry.IndexBuffer); - - CheckEnum($"{name}.VertexFormat", triangleGeometry.VertexFormat); - bool hasVertexCount = CheckGreaterThanZero($"{name}.VertexCount", triangleGeometry.VertexCount); - bool hasVertexStride = CheckGreaterThanZero($"{name}.VertexStrideInBytes", triangleGeometry.VertexStrideInBytes); - - if (hasVertexBuffer && hasVertexCount && hasVertexStride && triangleGeometry.VertexOffsetInBytes + (triangleGeometry.VertexCount * triangleGeometry.VertexStrideInBytes) > triangleGeometry.VertexBuffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.VertexCount", "the vertex buffer")); - } - - if (triangleGeometry.IndexBuffer is null) - { - return; - } - - CheckEnum($"{name}.IndexFormat", triangleGeometry.IndexFormat); - bool hasIndexCount = CheckGreaterThanZero($"{name}.IndexCount", triangleGeometry.IndexCount); - - uint indexSizeInBytes = triangleGeometry.IndexFormat switch - { - IndexFormat.UInt16 => ValidationConstants.IndexSizeUInt16, - IndexFormat.UInt32 => ValidationConstants.IndexSizeUInt32, - _ => 0 - }; - - if (hasIndexBuffer && hasIndexCount && indexSizeInBytes is not 0 && triangleGeometry.IndexOffsetInBytes + (triangleGeometry.IndexCount * indexSizeInBytes) > triangleGeometry.IndexBuffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.IndexCount", "the index buffer")); - } - } - - private void CheckRayTracingAabbGeometry(string name, RayTracingAabbGeometry aabbGeometry) - { - bool hasBuffer = CheckResource($"{name}.Buffer", aabbGeometry.Buffer); - - bool hasCount = CheckGreaterThanZero($"{name}.Count", aabbGeometry.Count); - bool hasStride = CheckGreaterThanZero($"{name}.StrideInBytes", aabbGeometry.StrideInBytes); - - if (hasBuffer && hasCount && hasStride && aabbGeometry.OffsetInBytes + (aabbGeometry.Count * aabbGeometry.StrideInBytes) > aabbGeometry.Buffer.Desc.SizeInBytes) - { - ReportError(string.Format(ValidationMessages.MustBeWithinBounds, $"{name}.Count", "the Aabb geometry buffer")); - } - } - - private void CheckRayTracingInstance(string name, RayTracingInstance instance) - { - CheckResource($"{name}.AccelerationStructure", instance.AccelerationStructure); - - if (instance.InstanceId > ValidationConstants.MaxRayTracingInstanceId) - { - ReportError(string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.InstanceId", ValidationConstants.MaxRayTracingInstanceId)); - } - - CheckFlags($"{name}.Flags", instance.Flags); - } - - #endregion -} - -file static class ValidationConstants -{ - public const int CubeMapFaceCount = 6; - - public const int MaxAnisotropy = 16; - - public const int MaxColorAttachments = 8; - - public const uint IndirectDrawArgsSizeInBytes = 16; - - public const uint IndirectDrawIndexedArgsSizeInBytes = 20; - - public const uint IndirectDispatchArgsSizeInBytes = 12; - - public const uint IndirectDispatchMeshArgsSizeInBytes = 12; - - public const int IndexSizeUInt16 = 2; - - public const int IndexSizeUInt32 = 4; - - public const int MaxRayTracingInstanceId = 16777215; -} - -file static class ValidationMessages -{ - public const string MustNotBeNull = "{0} must not be null."; - - public const string MustNotBeZero = "{0} must not be zero."; - - public const string MustHaveExactlyNHandles = "{0} must have exactly {1} handles for {2}."; - - public const string MustBeValidHandle = "{0} must be a valid handle for {1}."; - - public const string MustBeValidHandles = "{0} must be valid handles for {1}."; - - public const string HasUnsupportedSurfaceType = "{0} has unsupported SurfaceType '{1}'."; - - public const string HasInvalidValue = "{0} has an invalid value '{1}'."; - - public const string HasNoAttachments = "{0} has no attachments."; - - public const string MustNotBeDisposed = "{0} must not be disposed."; - - public const string MustBeLessThan = "{0} must be less than {1}."; - - public const string MustNotBeNullOrEmpty = "{0} must not be null or empty."; - - public const string MustNotBeNullOrWhitespace = "{0} must not be null or whitespace."; - - public const string MustBeGreaterThanZero = "{0} must be greater than zero."; - - public const string IsZeroWarning = "{0} is zero, which may be valid for some {1} but could indicate an issue."; - - public const string IsSetToNoneWarning = "{0} is set to None, which may be valid but could indicate an issue."; - - public const string MustBeWithinBounds = "{0} must be greater than zero and within the bounds of {1}."; - - public const string MustBeLessThanOrEqualTo = "{0} must be less than or equal to {1}."; - - public const string MustBeGreaterThanOrEqualTo = "{0} must be greater than or equal to {1}."; - - public const string MustBeEqualTo = "{0} must be equal to {1}."; - - public const string MustBeBetween = "{0} must be between {1} and {2}."; - - public const string MustBeAMultipleOf = "{0} must be a multiple of {1}."; - - public const string MustDescribeACompleteCube = "{0} must describe a complete cube view."; - - public const string MustBeOneOf = "{0} must be one of: {1}."; - - public const string MustHaveFlag = "{0} must have the flag '{1}' set."; - - public const string MustHaveUsage = "{0} must have usage flag '{1}'."; - - public const string MustHaveSameValue = "{0} must match. First value: '{1}', second value: '{2}'."; - - public const string MustBeWithinResourceBounds = "{0} must be within the bounds of {1}."; - - public const string MustNotBeDepthStencilFormat = "{0} must not use a depth/stencil format."; - - public const string MustBeDepthStencilFormat = "{0} must use a depth/stencil format."; - - public const string MustBeSingleSampled = "{0} must use SampleCount.Count1."; - - public const string MustBeMultisampled = "{0} must use a sample count greater than SampleCount.Count1."; - - public const string MustBeFinite = "{0} must be finite."; - - public const string MustHaveCurrentPipeline = "{0} requires a pipeline to be set."; - - public const string MustHaveCurrentPipelineType = "{0} requires current pipeline type {1}, but current pipeline is {2}."; - - public const string MustNotBeTimestampQuery = "{0} cannot be used with QueryType.Timestamp."; - - public const string MustBeTimestampQuery = "{0} requires QueryType.Timestamp."; - - public const string InstanceCountMustRemainSame = "When updating a TopLevelAccelerationStructure, the number of instances must remain the same."; - - public const string UsagesIncompatibleWithAccess = "{0} contains flags '{1}' that require GPU read-write access and cannot be combined with BufferAccess.{2}."; - - public const string MustHaveNonZeroConstantsSize = "{0} reports a zero size for {1}; the constants layout has no payload for the current backend."; - - public const string MustBeCpuAccessible = "{0} must use BufferAccess.CpuReadOnly or BufferAccess.CpuWriteOnly to be mappable."; } diff --git a/sources/Zenith.NET/ValidationMessageEventArgs.cs b/sources/Zenith.NET/ValidationMessageEventArgs.cs index 7f795e76..13f78db3 100644 --- a/sources/Zenith.NET/ValidationMessageEventArgs.cs +++ b/sources/Zenith.NET/ValidationMessageEventArgs.cs @@ -1,9 +1,7 @@ namespace Zenith.NET; -public class ValidationMessageEventArgs(MessageSource source, MessageSeverity severity, string message) : EventArgs +public class ValidationMessageEventArgs(MessageSeverity severity, string message) : EventArgs { - public MessageSource Source { get; } = source; - public MessageSeverity Severity { get; } = severity; public string Message { get; } = message; From f2a02d77a749b12040bc4b9e8df7f1a8351ab0f8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 23 May 2026 14:19:48 +0800 Subject: [PATCH 091/466] Remove early-return checks from core methods Removed conditional early returns in CommandBuffer, CommandQueue, and ZenithMarshal. Implementation methods are now always called, ensuring consistent execution regardless of input values or query types. --- sources/Zenith.NET/CommandBuffer.cs | 15 --------------- sources/Zenith.NET/CommandQueue.cs | 5 ----- sources/Zenith.NET/ZenithMarshal.cs | 5 ----- 3 files changed, 25 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 74da22be..0a71f5ca 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -325,31 +325,16 @@ public void DispatchMeshIndirect(Buffer indirectBuffer, uint offsetInBytes, uint public void BeginQuery(QueryHeap queryHeap, uint index) { - if (queryHeap.Desc.Type is QueryType.Timestamp) - { - return; - } - BeginQueryImpl(queryHeap, index); } public void EndQuery(QueryHeap queryHeap, uint index) { - if (queryHeap.Desc.Type is QueryType.Timestamp) - { - return; - } - EndQueryImpl(queryHeap, index); } public void WriteTimestamp(QueryHeap queryHeap, uint index) { - if (queryHeap.Desc.Type is not QueryType.Timestamp) - { - return; - } - WriteTimestampImpl(queryHeap, index); } diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index cf1de1ad..41ef0f4b 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -36,11 +36,6 @@ internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan(ReadOnlySpan data) where T : unmanaged { - if (data.Length is 0) - { - return nint.Zero; - } - nint pointer = (nint)NativeMemory.Alloc((uint)(sizeof(T) * data.Length)); data.CopyTo(new((void*)pointer, data.Length)); From ec83f1681f126680992b108c6a4b7bfa9caed454 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 23 May 2026 14:28:53 +0800 Subject: [PATCH 092/466] Remove MessageSource from validation reporting Validation message reporting in DXValidationLayer and VKValidationLayer no longer includes the MessageSource parameter. The App.cs output format was updated to display only severity and message text, omitting the source. --- sources/Experiments/CornellBox/App.cs | 2 +- sources/Zenith.NET.DirectX12/DXValidationLayer.cs | 2 +- sources/Zenith.NET.Vulkan/VKValidationLayer.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 93861793..8edddf62 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -45,7 +45,7 @@ static App() Context = GraphicsContext.CreateVulkan(useValidationLayer: true); } - Context.ValidationMessage += static (sender, args) => Console.WriteLine($"[{args.Source} - {args.Severity}] {args.Message}"); + Context.ValidationMessage += static (sender, args) => Console.WriteLine($"[{args.Severity}] {args.Message}"); window = Window.Create(WindowOptions.Default with { diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index 41429f3d..b74781cc 100644 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -32,7 +32,7 @@ protected override void Destroy() private void Callback(MessageCategory category, DxMessageSeverity severity, MessageID messageID, byte* pDescription, void* context) { - Report(MessageSource.GraphicsApi, severity switch + Report(severity switch { DxMessageSeverity.Error => MessageSeverity.Error, DxMessageSeverity.Warning => MessageSeverity.Warning, diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs index bc6e0fce..df6c94ee 100644 --- a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs +++ b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs @@ -43,7 +43,7 @@ private uint UserCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { - Report(MessageSource.GraphicsAPI, messageSeverity switch + Report(messageSeverity switch { DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => MessageSeverity.Error, DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => MessageSeverity.Warning, From d9a2a25a52f6eb8bd371015c6169469ec87d9e6a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 23 May 2026 18:29:39 +0800 Subject: [PATCH 093/466] Add DXBufferView, DXFormats, and update DXBuffer usage Introduce DXBufferView for DirectX 12 buffer views with stubbed resource handles. Update DXBuffer to delegate resource handle properties to DXBufferView. Implement CreateBufferImpl and CreateBufferViewImpl in DXGraphicsContext. Add DXFormats utility for mapping buffer usages and access types. Add global alias for DxHeapType in Usings.cs. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 8 +++-- sources/Zenith.NET.DirectX12/DXBufferView.cs | 27 +++++++++++++++ sources/Zenith.NET.DirectX12/DXFormats.cs | 34 +++++++++++++++++++ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 6 ++-- sources/Zenith.NET.DirectX12/Usings.cs | 3 +- 5 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXBufferView.cs create mode 100644 sources/Zenith.NET.DirectX12/DXFormats.cs diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 4a6f3b64..fb09c6f5 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -11,11 +11,13 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc { } - public override ResourceHandle UniformHandle { get; } + public DXBufferView View { get; } - public override ResourceHandle StorageReadOnlyHandle { get; } + public override ResourceHandle UniformHandle => View.UniformHandle; - public override ResourceHandle StorageReadWriteHandle { get; } + public override ResourceHandle StorageReadOnlyHandle => View.StorageReadOnlyHandle; + + public override ResourceHandle StorageReadWriteHandle => View.StorageReadWriteHandle; public override nint GetNativeObject(NativeObjectType type) { diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs new file mode 100644 index 00000000..43daa7bb --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -0,0 +1,27 @@ +namespace Zenith.NET.DirectX12; + +internal class DXBufferView : BufferView +{ + public DXBufferView(DXGraphicsContext context, BufferViewDesc desc) : base(context, desc) + { + } + + public override ResourceHandle UniformHandle { get; } + + public override ResourceHandle StorageReadOnlyHandle { get; } + + public override ResourceHandle StorageReadWriteHandle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + } +} diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs new file mode 100644 index 00000000..bad5851a --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -0,0 +1,34 @@ +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal static class DXFormats +{ + public static ResourceFlags DirectX12(BufferUsages bufferUsages) + { + ResourceFlags result = ResourceFlags.None; + + if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) + { + result |= ResourceFlags.AllowUnorderedAccess; + } + + if (bufferUsages.HasFlag(BufferUsages.AccelerationStructure)) + { + result |= ResourceFlags.RaytracingAccelerationStructure; + } + + return result; + } + + public static DxHeapType DirectX12(BufferAccess bufferAccess) + { + return bufferAccess switch + { + BufferAccess.GpuOnly => DxHeapType.Default, + BufferAccess.CpuReadOnly => DxHeapType.Readback, + BufferAccess.CpuWriteOnly => DxHeapType.Upload, + _ => DxHeapType.Default + }; + } +} diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index ebb752f5..58d5b34e 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -44,7 +44,7 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public override nint GetNativeObject(NativeObjectType type) { - throw new NotImplementedException(); + return 0; } protected override void Initialize(bool useValidationLayer, @@ -156,12 +156,12 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) protected override Buffer CreateBufferImpl(BufferDesc desc) { - throw new NotImplementedException(); + return new DXBuffer(this, desc); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) { - throw new NotImplementedException(); + return new DXBufferView(this, desc); } protected override Texture CreateTextureImpl(TextureDesc desc) diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 6ee3818a..27d86b43 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,2 +1,3 @@ -global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; +global using DxHeapType = Silk.NET.Direct3D12.HeapType; +global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxRange = Silk.NET.Direct3D12.Range; From d02599c40fe90794aaa21e32efafb8f0381141dc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 23 May 2026 19:11:23 +0800 Subject: [PATCH 094/466] Refactor DXBuffer and DXBufferView resource management - Store GPU virtual address in DXBuffer and initialize DXBufferView in constructor - Add disposal of DXBufferView in DXBuffer.Destroy - Refactor DXBufferView to use primary constructor and manage CBV/SRV/UAV descriptor tokens with lazy init and disposal - Add methods to create CBV, SRV, and UAV tokens in DXBufferView - Update DXDescriptorHeap and DXDescriptorToken to use heap reference for allocation and disposal - Define Shader4ComponentMapping constant in DXGraphicsContext - Add extension methods for DirectX12 casting of Buffer and BufferView --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 11 +++ sources/Zenith.NET.DirectX12/DXBufferView.cs | 77 +++++++++++++++++-- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 2 +- .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 11 ++- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 2 + sources/Zenith.NET.DirectX12/Extensions.cs | 16 ++++ 6 files changed, 107 insertions(+), 12 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index fb09c6f5..663e3ae2 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -7,8 +7,17 @@ internal unsafe class DXBuffer : Buffer { public ComPtr Resource; + public ulong GPUVirtualAddress; + public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc) { + View = new(context, new() + { + Buffer = this, + OffsetInBytes = 0, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); } public DXBufferView View { get; } @@ -44,6 +53,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + View.Dispose(); + Resource.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs index 43daa7bb..08758945 100644 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -1,16 +1,18 @@ -namespace Zenith.NET.DirectX12; +using Silk.NET.Direct3D12; -internal class DXBufferView : BufferView +namespace Zenith.NET.DirectX12; + +internal unsafe class DXBufferView(DXGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) { - public DXBufferView(DXGraphicsContext context, BufferViewDesc desc) : base(context, desc) - { - } + private DXDescriptorToken? cbvToken; + private DXDescriptorToken? srvToken; + private DXDescriptorToken? uavToken; - public override ResourceHandle UniformHandle { get; } + public override ResourceHandle UniformHandle => (cbvToken ??= CreateCbvToken()).ResourceHandle; - public override ResourceHandle StorageReadOnlyHandle { get; } + public override ResourceHandle StorageReadOnlyHandle => (srvToken ??= CreateSrvToken()).ResourceHandle; - public override ResourceHandle StorageReadWriteHandle { get; } + public override ResourceHandle StorageReadWriteHandle => (uavToken ??= CreateUavToken()).ResourceHandle; public override nint GetNativeObject(NativeObjectType type) { @@ -23,5 +25,64 @@ protected override void SetResourceName(string name) protected override void Destroy() { + uavToken?.Dispose(); + srvToken?.Dispose(); + cbvToken?.Dispose(); + } + + private DXDescriptorToken CreateCbvToken() + { + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); + + ConstantBufferViewDesc viewDesc = new() + { + BufferLocation = Desc.Buffer.DirectX12().GPUVirtualAddress + Desc.OffsetInBytes, + SizeInBytes = ZenithHelper.Align(Desc.SizeInBytes, 256u) + }; + + context.Device10.CreateConstantBufferView(&viewDesc, token.CpuHandle); + + return token; + } + + private DXDescriptorToken CreateSrvToken() + { + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); + + ShaderResourceViewDesc viewDesc = new() + { + ViewDimension = SrvDimension.Buffer, + Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, + Buffer = new() + { + FirstElement = Desc.OffsetInBytes / Desc.StrideInBytes, + NumElements = Desc.SizeInBytes / Desc.StrideInBytes, + StructureByteStride = Desc.StrideInBytes + } + }; + + context.Device10.CreateShaderResourceView(Desc.Buffer.DirectX12().Resource, &viewDesc, token.CpuHandle); + + return token; + } + + private DXDescriptorToken CreateUavToken() + { + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); + + UnorderedAccessViewDesc viewDesc = new() + { + ViewDimension = UavDimension.Buffer, + Buffer = new() + { + FirstElement = Desc.OffsetInBytes / Desc.StrideInBytes, + NumElements = Desc.SizeInBytes / Desc.StrideInBytes, + StructureByteStride = Desc.StrideInBytes + } + }; + + context.Device10.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, (ID3D12Resource*)null, &viewDesc, token.CpuHandle); + + return token; } } diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 5b8e4a5f..61a0afa9 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -42,7 +42,7 @@ public DXDescriptorToken Allocate() slot = head++; } - return new(slot, IncrementSize, CpuStartHandle, GpuStartHandle); + return new(this, slot); } public void Free(DXDescriptorToken token) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index b3791ce1..998e5dc4 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -2,13 +2,18 @@ namespace Zenith.NET.DirectX12; -internal readonly struct DXDescriptorToken(uint slot, uint incrementSize, CpuDescriptorHandle cpuStartHandle, GpuDescriptorHandle gpuStartHandle) +internal readonly struct DXDescriptorToken(DXDescriptorHeap heap, uint slot) : IDisposable { public readonly uint Slot = slot; public readonly ResourceHandle ResourceHandle = new(slot, 0); - public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = (slot * incrementSize) + cpuStartHandle.Ptr }; + public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = (slot * heap.IncrementSize) + heap.CpuStartHandle.Ptr }; - public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = (slot * incrementSize) + gpuStartHandle.Ptr }; + public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = (slot * heap.IncrementSize) + heap.GpuStartHandle.Ptr }; + + public void Dispose() + { + heap.Free(this); + } } \ No newline at end of file diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 58d5b34e..5d800179 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -6,6 +6,8 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.DirectX12, useValidationLayer) { + public const uint Shader4ComponentMapping = 0x1688; + public ComPtr Factory7; public ComPtr Adapter4; diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index d24c27b0..cca96613 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -27,4 +27,20 @@ internal bool IsSuccess() return result is 0; } } + + extension(Buffer buffer) + { + internal DXBuffer DirectX12() + { + return (DXBuffer)buffer; + } + } + + extension(BufferView bufferView) + { + internal DXBufferView DirectX12() + { + return (DXBufferView)bufferView; + } + } } From 5871e39998f56034c4127f1083fc6c396e97da93 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 24 May 2026 13:42:28 +0800 Subject: [PATCH 095/466] Update Silk.NET interop calls for type safety & API changes Refactored DirectX 12 interop calls to use default-initialized ComPtr and ReadOnlySpan parameters instead of null pointers, following updated Silk.NET conventions. Improved resource creation in DXBuffer.cs with ResourceDesc1 and HeapProperties. Updated buffer mapping/unmapping and view creation for better type safety and compatibility with Silk.NET updates. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 33 +++++++++++++++++-- sources/Zenith.NET.DirectX12/DXBufferView.cs | 5 +-- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 8 ++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 663e3ae2..4acbf5de 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -1,5 +1,6 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -11,6 +12,34 @@ internal unsafe class DXBuffer : Buffer public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc) { + ResourceDesc1 resourceDesc = new() + { + Dimension = ResourceDimension.Buffer, + Alignment = 0, + Width = ZenithHelper.Align(desc.SizeInBytes, 256u), + Height = 1, + DepthOrArraySize = 1, + MipLevels = 1, + Format = Format.FormatUnknown, + SampleDesc = new(1, 0), + Layout = TextureLayout.LayoutRowMajor, + Flags = DXFormats.DirectX12(desc.Usages) + }; + + HeapProperties heapProperties = new(DXFormats.DirectX12(desc.Access)); + + context.Device10.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default, + default(ComPtr), + 0, + default, + out Resource).Success(); + + GPUVirtualAddress = Resource.GetGPUVirtualAddress(); + View = new(context, new() { Buffer = this, @@ -36,14 +65,14 @@ public override nint GetNativeObject(NativeObjectType type) public override MappedMemory Map() { void* pointer; - Resource.Map(0, (DxRange*)null, &pointer).Success(); + Resource.Map(0, default(ReadOnlySpan), &pointer).Success(); return new((nint)pointer, Desc.SizeInBytes); } public override void Unmap() { - Resource.Unmap(0, (DxRange*)null); + Resource.Unmap(0, default(ReadOnlySpan)); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs index 08758945..02326d71 100644 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -1,4 +1,5 @@ -using Silk.NET.Direct3D12; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; @@ -81,7 +82,7 @@ private DXDescriptorToken CreateUavToken() } }; - context.Device10.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, (ID3D12Resource*)null, &viewDesc, token.CpuHandle); + context.Device10.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, default(ComPtr), &viewDesc, token.CpuHandle); return token; } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 5d800179..a21c884d 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -106,19 +106,19 @@ protected override void Initialize(bool useValidationLayer, IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; - Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DrawSignature).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawIndexedSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DrawIndexedSignature).Success(); indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DispatchSignature).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchMeshSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DispatchMeshSignature).Success(); CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; Device10.CreateCommandQueue(&commandQueueDesc, out GraphicsCommandQueue).Success(); From 872231e79ae8a31d0cda96b3d825af10236a3365 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 24 May 2026 13:52:10 +0800 Subject: [PATCH 096/466] Refactor DXBuffer, DXDescriptorHeap, and validation layer - Removed redundant Alignment and Format initializations in DXBuffer. - Simplified View initialization in DXBuffer. - Changed DXDescriptorHeap properties to public fields and fixed init order. - Removed NumDescriptors property from DXDescriptorHeap. - Corrected handle calculations in DXDescriptorToken. - Clarified callback assignment in DXValidationLayer. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 3 --- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 18 ++++++++---------- .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 4 ++-- .../Zenith.NET.DirectX12/DXValidationLayer.cs | 4 +++- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 4acbf5de..61dc153d 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -15,12 +15,10 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc ResourceDesc1 resourceDesc = new() { Dimension = ResourceDimension.Buffer, - Alignment = 0, Width = ZenithHelper.Align(desc.SizeInBytes, 256u), Height = 1, DepthOrArraySize = 1, MipLevels = 1, - Format = Format.FormatUnknown, SampleDesc = new(1, 0), Layout = TextureLayout.LayoutRowMajor, Flags = DXFormats.DirectX12(desc.Usages) @@ -43,7 +41,6 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc View = new(context, new() { Buffer = this, - OffsetInBytes = 0, SizeInBytes = desc.SizeInBytes, StrideInBytes = desc.StrideInBytes }); diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 61a0afa9..13438dc4 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -9,6 +9,12 @@ internal unsafe class DXDescriptorHeap : DisposableObject public ComPtr Heap; + public CpuDescriptorHandle CpuStartHandle; + + public GpuDescriptorHandle GpuStartHandle; + + public uint IncrementSize; + private uint head; public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint numDescriptors, bool shaderVisible) @@ -16,25 +22,17 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint DescriptorHeapDesc desc = new() { Type = type, - NumDescriptors = NumDescriptors = numDescriptors, + NumDescriptors = numDescriptors, Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None }; context.Device10.CreateDescriptorHeap(&desc, out Heap).Success(); - IncrementSize = context.Device10.GetDescriptorHandleIncrementSize(type); CpuStartHandle = Heap.GetCPUDescriptorHandleForHeapStart(); GpuStartHandle = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; + IncrementSize = context.Device10.GetDescriptorHandleIncrementSize(type); } - public uint NumDescriptors { get; } - - public uint IncrementSize { get; } - - public CpuDescriptorHandle CpuStartHandle { get; } - - public GpuDescriptorHandle GpuStartHandle { get; } - public DXDescriptorToken Allocate() { if (!recycled.TryPop(out uint slot)) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index 998e5dc4..ee80e461 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -8,9 +8,9 @@ internal readonly struct DXDescriptorToken(DXDescriptorHeap heap, uint slot) : I public readonly ResourceHandle ResourceHandle = new(slot, 0); - public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = (slot * heap.IncrementSize) + heap.CpuStartHandle.Ptr }; + public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = heap.CpuStartHandle.Ptr + (slot * heap.IncrementSize) }; - public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = (slot * heap.IncrementSize) + heap.GpuStartHandle.Ptr }; + public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = heap.GpuStartHandle.Ptr + (slot * heap.IncrementSize) }; public void Dispose() { diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index b74781cc..2d425b21 100644 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -9,7 +9,9 @@ internal unsafe class DXValidationLayer : ValidationLayer public DXValidationLayer(DXGraphicsContext context) : base(context) { - context.InfoQueue1?.RegisterMessageCallback(callback = new(Callback), MessageCallbackFlags.FlagNone, null, ref callbackCookie).Success(); + callback = new(Callback); + + context.InfoQueue1?.RegisterMessageCallback(callback, MessageCallbackFlags.FlagNone, null, ref callbackCookie).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; From bcb1712a9f1a07267721f7de47e82053aea23f71 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 24 May 2026 14:54:54 +0800 Subject: [PATCH 097/466] Implement DXTexture and DXTextureView for DirectX 12 Added DXTexture and DXTextureView classes to manage DirectX 12 textures and views. Updated DXGraphicsContext to instantiate these classes instead of throwing NotImplementedException. Included resource and descriptor token management, as well as proper cleanup in Destroy methods. --- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 4 +-- sources/Zenith.NET.DirectX12/DXTexture.cs | 36 +++++++++++++++++++ sources/Zenith.NET.DirectX12/DXTextureView.cs | 36 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXTexture.cs create mode 100644 sources/Zenith.NET.DirectX12/DXTextureView.cs diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index a21c884d..179bca83 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -168,12 +168,12 @@ protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc) { - throw new NotImplementedException(); + return new DXTexture(this, desc); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { - throw new NotImplementedException(); + return new DXTextureView(this, desc); } protected override Sampler CreateSamplerImpl(SamplerDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs new file mode 100644 index 00000000..654f8137 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -0,0 +1,36 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal class DXTexture : Texture +{ + public ComPtr Resource; + + public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, desc) + { + } + + public DXTextureView View { get; } + + public override ResourceHandle SampledHandle => View.SampledHandle; + + public override ResourceHandle StorageHandle => View.StorageHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + Resource.SetName(name).Success(); + } + + protected override void Destroy() + { + View.Dispose(); + + Resource.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs new file mode 100644 index 00000000..c37e4f63 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -0,0 +1,36 @@ +namespace Zenith.NET.DirectX12; + +internal class DXTextureView(DXGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) +{ + private DXDescriptorToken? srvToken; + private DXDescriptorToken? uavToken; + + public override ResourceHandle SampledHandle => (srvToken ??= CreateSrvToken()).ResourceHandle; + + public override ResourceHandle StorageHandle => (uavToken ??= CreateUavToken()).ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + uavToken?.Dispose(); + srvToken?.Dispose(); + } + + private DXDescriptorToken CreateSrvToken() + { + throw new NotImplementedException(); + } + + private DXDescriptorToken CreateUavToken() + { + throw new NotImplementedException(); + } +} From 7e8d4d60a7a458def4941943a9502eff807ab18c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 24 May 2026 15:31:52 +0800 Subject: [PATCH 098/466] Add DirectX 12 texture and view interop support Implement mapping of engine texture enums to DirectX 12 types in DXFormats. Create and manage ID3D12Resource and views in DXTexture and DXTextureView. Add extension methods for type casting. Enables full DirectX 12 texture resource and view support. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 138 +++++++++++++ sources/Zenith.NET.DirectX12/DXTexture.cs | 38 +++- sources/Zenith.NET.DirectX12/DXTextureView.cs | 181 +++++++++++++++++- sources/Zenith.NET.DirectX12/Extensions.cs | 16 ++ 4 files changed, 368 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index bad5851a..890c5c11 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -1,4 +1,5 @@ using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -31,4 +32,141 @@ public static DxHeapType DirectX12(BufferAccess bufferAccess) _ => DxHeapType.Default }; } + + public static ResourceFlags DirectX12(TextureUsages textureUsages) + { + ResourceFlags result = ResourceFlags.None; + + if (!textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= ResourceFlags.DenyShaderResource; + } + + if (textureUsages.HasFlag(TextureUsages.Storage)) + { + result |= ResourceFlags.AllowUnorderedAccess; + } + + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) + { + result |= ResourceFlags.AllowRenderTarget; + } + + if (textureUsages.HasFlag(TextureUsages.DepthStencil)) + { + result |= ResourceFlags.AllowDepthStencil; + } + + return result; + } + + public static ResourceDimension DirectX12(TextureType textureType) + { + return textureType switch + { + TextureType.Texture1D or + TextureType.Texture1DArray => ResourceDimension.Texture1D, + + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ResourceDimension.Texture2D, + + TextureType.Texture3D => ResourceDimension.Texture3D, + + _ => ResourceDimension.Texture1D + }; + } + + public static SampleDesc DirectX12(SampleCount sampleCount) + { + return sampleCount switch + { + SampleCount.Count1 => new(1, 0), + SampleCount.Count2 => new(2, 0), + SampleCount.Count4 => new(4, 0), + SampleCount.Count8 => new(8, 0), + SampleCount.Count16 => new(16, 0), + SampleCount.Count32 => new(32, 0), + _ => new(1, 0) + }; + } + + public static Format DirectX12(PixelFormat pixelFormat) + { + return pixelFormat switch + { + PixelFormat.R8UNorm => Format.FormatR8Unorm, + PixelFormat.R8SNorm => Format.FormatR8SNorm, + PixelFormat.R8UInt => Format.FormatR8Uint, + PixelFormat.R8SInt => Format.FormatR8Sint, + + PixelFormat.R16UNorm => Format.FormatR16Unorm, + PixelFormat.R16SNorm => Format.FormatR16SNorm, + PixelFormat.R16UInt => Format.FormatR16Uint, + PixelFormat.R16SInt => Format.FormatR16Sint, + PixelFormat.R16Float => Format.FormatR16Float, + + PixelFormat.R32UInt => Format.FormatR32Uint, + PixelFormat.R32SInt => Format.FormatR32Sint, + PixelFormat.R32Float => Format.FormatR32Float, + + PixelFormat.R8G8UNorm => Format.FormatR8G8Unorm, + PixelFormat.R8G8SNorm => Format.FormatR8G8SNorm, + PixelFormat.R8G8UInt => Format.FormatR8G8Uint, + PixelFormat.R8G8SInt => Format.FormatR8G8Sint, + + PixelFormat.R16G16UNorm => Format.FormatR16G16Unorm, + PixelFormat.R16G16SNorm => Format.FormatR16G16SNorm, + PixelFormat.R16G16UInt => Format.FormatR16G16Uint, + PixelFormat.R16G16SInt => Format.FormatR16G16Sint, + PixelFormat.R16G16Float => Format.FormatR16G16Float, + + PixelFormat.R32G32UInt => Format.FormatR32G32Uint, + PixelFormat.R32G32SInt => Format.FormatR32G32Sint, + PixelFormat.R32G32Float => Format.FormatR32G32Float, + + PixelFormat.R32G32B32UInt => Format.FormatR32G32B32Uint, + PixelFormat.R32G32B32SInt => Format.FormatR32G32B32Sint, + PixelFormat.R32G32B32Float => Format.FormatR32G32B32Float, + + PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, + PixelFormat.R8G8B8A8SNorm => Format.FormatR8G8B8A8SNorm, + PixelFormat.R8G8B8A8UInt => Format.FormatR8G8B8A8Uint, + PixelFormat.R8G8B8A8SInt => Format.FormatR8G8B8A8Sint, + PixelFormat.R8G8B8A8SRgb => Format.FormatR8G8B8A8UnormSrgb, + + PixelFormat.R16G16B16A16UNorm => Format.FormatR16G16B16A16Unorm, + PixelFormat.R16G16B16A16SNorm => Format.FormatR16G16B16A16SNorm, + PixelFormat.R16G16B16A16UInt => Format.FormatR16G16B16A16Uint, + PixelFormat.R16G16B16A16SInt => Format.FormatR16G16B16A16Sint, + PixelFormat.R16G16B16A16Float => Format.FormatR16G16B16A16Float, + + PixelFormat.R32G32B32A32UInt => Format.FormatR32G32B32A32Uint, + PixelFormat.R32G32B32A32SInt => Format.FormatR32G32B32A32Sint, + PixelFormat.R32G32B32A32Float => Format.FormatR32G32B32A32Float, + + PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, + PixelFormat.B8G8R8A8SRgb => Format.FormatB8G8R8A8UnormSrgb, + + PixelFormat.D16UNorm => Format.FormatD16Unorm, + PixelFormat.D24UNormS8UInt => Format.FormatD24UnormS8Uint, + PixelFormat.D32Float => Format.FormatD32Float, + PixelFormat.D32FloatS8UInt => Format.FormatD32FloatS8X24Uint, + + PixelFormat.BC4UNorm => Format.FormatBC4Unorm, + PixelFormat.BC4SNorm => Format.FormatBC4SNorm, + + PixelFormat.BC5UNorm => Format.FormatBC5Unorm, + PixelFormat.BC5SNorm => Format.FormatBC5SNorm, + + PixelFormat.BC6HUFloat => Format.FormatBC6HUF16, + PixelFormat.BC6HSFloat => Format.FormatBC6HSF16, + + PixelFormat.BC7UNorm => Format.FormatBC7Unorm, + PixelFormat.BC7SRgb => Format.FormatBC7UnormSrgb, + + _ => Format.FormatUnknown + }; + } } diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 654f8137..e579d05a 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -3,12 +3,48 @@ namespace Zenith.NET.DirectX12; -internal class DXTexture : Texture +internal unsafe class DXTexture : Texture { public ComPtr Resource; public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, desc) { + ResourceDesc1 resourceDesc = new() + { + Dimension = DXFormats.DirectX12(desc.Type), + Width = desc.Width, + Height = desc.Height, + DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : desc.ArrayLayers), + MipLevels = (ushort)desc.MipLevels, + Format = DXFormats.DirectX12(desc.Format), + SampleDesc = DXFormats.DirectX12(desc.SampleCount), + Layout = TextureLayout.LayoutUnknown, + Flags = DXFormats.DirectX12(desc.Usages) + }; + + HeapProperties heapProperties = new(DxHeapType.Default); + + context.Device10.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default, + default(ComPtr), + 0, + default, + out Resource).Success(); + + View = new(context, new() + { + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = new() + { + LevelCount = desc.MipLevels, + LayerCount = desc.ArrayLayers + } + }); } public DXTextureView View { get; } diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs index c37e4f63..201537d9 100644 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -1,6 +1,9 @@ -namespace Zenith.NET.DirectX12; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; -internal class DXTextureView(DXGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) +namespace Zenith.NET.DirectX12; + +internal unsafe class DXTextureView(DXGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) { private DXDescriptorToken? srvToken; private DXDescriptorToken? uavToken; @@ -26,11 +29,181 @@ protected override void Destroy() private DXDescriptorToken CreateSrvToken() { - throw new NotImplementedException(); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); + + ShaderResourceViewDesc viewDesc = new() + { + Format = DXFormats.DirectX12(Desc.Format), + Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping + }; + + switch (Desc.Type) + { + case TextureType.Texture1D: + viewDesc.ViewDimension = SrvDimension.Texture1D; + viewDesc.Texture1D = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount + }; + break; + + case TextureType.Texture2D: + if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = SrvDimension.Texture2D; + viewDesc.Texture2D = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount + }; + } + else + { + viewDesc.ViewDimension = SrvDimension.Texture2Dms; + } + break; + + case TextureType.Texture3D: + viewDesc.ViewDimension = SrvDimension.Texture3D; + viewDesc.Texture3D = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount + }; + break; + + case TextureType.TextureCube: + viewDesc.ViewDimension = SrvDimension.Texturecube; + viewDesc.TextureCube = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount + }; + break; + + case TextureType.Texture1DArray: + viewDesc.ViewDimension = SrvDimension.Texture1Darray; + viewDesc.Texture1DArray = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + break; + + case TextureType.Texture2DArray: + if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = SrvDimension.Texture2Darray; + viewDesc.Texture2DArray = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + } + else + { + viewDesc.ViewDimension = SrvDimension.Texture2Dmsarray; + viewDesc.Texture2DMSArray = new() + { + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + } + break; + + case TextureType.TextureCubeArray: + viewDesc.ViewDimension = SrvDimension.Texturecubearray; + viewDesc.TextureCubeArray = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount, + First2DArrayFace = Desc.Range.BaseArrayLayer, + NumCubes = Desc.Range.LayerCount / 6 + }; + break; + } + + context.Device10.CreateShaderResourceView(Desc.Texture.DirectX12().Resource, &viewDesc, token.CpuHandle); + + return token; } private DXDescriptorToken CreateUavToken() { - throw new NotImplementedException(); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); + + UnorderedAccessViewDesc viewDesc = new() + { + Format = DXFormats.DirectX12(Desc.Format) + }; + + switch (Desc.Type) + { + case TextureType.Texture1D: + viewDesc.ViewDimension = UavDimension.Texture1D; + viewDesc.Texture1D = new() + { + MipSlice = Desc.Range.BaseMipLevel + }; + break; + + case TextureType.Texture2D: + viewDesc.ViewDimension = UavDimension.Texture2D; + viewDesc.Texture2D = new() + { + MipSlice = Desc.Range.BaseMipLevel + }; + break; + + case TextureType.Texture3D: + viewDesc.ViewDimension = UavDimension.Texture3D; + viewDesc.Texture3D = new() + { + MipSlice = Desc.Range.BaseMipLevel, + FirstWSlice = 0, + WSize = Desc.Texture.Desc.Depth + }; + break; + + case TextureType.TextureCube: + viewDesc.ViewDimension = UavDimension.Texture2Darray; + viewDesc.Texture2DArray = new() + { + MipSlice = Desc.Range.BaseMipLevel, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + break; + + case TextureType.Texture1DArray: + viewDesc.ViewDimension = UavDimension.Texture1Darray; + viewDesc.Texture1DArray = new() + { + MipSlice = Desc.Range.BaseMipLevel, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + break; + + case TextureType.Texture2DArray: + case TextureType.TextureCubeArray: + viewDesc.ViewDimension = UavDimension.Texture2Darray; + viewDesc.Texture2DArray = new() + { + MipSlice = Desc.Range.BaseMipLevel, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + break; + } + + context.Device10.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, default(ComPtr), &viewDesc, token.CpuHandle); + + return token; } } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index cca96613..76998cae 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -43,4 +43,20 @@ internal DXBufferView DirectX12() return (DXBufferView)bufferView; } } + + extension(Texture texture) + { + internal DXTexture DirectX12() + { + return (DXTexture)texture; + } + } + + extension(TextureView textureView) + { + internal DXTextureView DirectX12() + { + return (DXTextureView)textureView; + } + } } From 5e7720e686d732678ac015de6a77dc533af8cc61 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 24 May 2026 16:07:38 +0800 Subject: [PATCH 099/466] Add DirectX 12 sampler and shader support Implemented DXSampler and DXShader classes with DirectX 12 interop. Updated DXGraphicsContext to create these resources. Added DXFormats mapping functions for filter, address, comparison, and border color. Refactored switch expressions to use default values. Extended Extensions.cs with DirectX 12 casting helpers and registered DxSamplerDesc in Usings.cs. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 78 ++++++++++++++++++- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 4 +- sources/Zenith.NET.DirectX12/DXSampler.cs | 52 +++++++++++++ sources/Zenith.NET.DirectX12/DXShader.cs | 28 +++++++ sources/Zenith.NET.DirectX12/Extensions.cs | 16 ++++ sources/Zenith.NET.DirectX12/Usings.cs | 1 + 6 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXSampler.cs create mode 100644 sources/Zenith.NET.DirectX12/DXShader.cs diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 890c5c11..2899d183 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -29,7 +29,7 @@ public static DxHeapType DirectX12(BufferAccess bufferAccess) BufferAccess.GpuOnly => DxHeapType.Default, BufferAccess.CpuReadOnly => DxHeapType.Readback, BufferAccess.CpuWriteOnly => DxHeapType.Upload, - _ => DxHeapType.Default + _ => default }; } @@ -74,7 +74,7 @@ TextureType.TextureCube or TextureType.Texture3D => ResourceDimension.Texture3D, - _ => ResourceDimension.Texture1D + _ => default }; } @@ -88,7 +88,7 @@ public static SampleDesc DirectX12(SampleCount sampleCount) SampleCount.Count8 => new(8, 0), SampleCount.Count16 => new(16, 0), SampleCount.Count32 => new(32, 0), - _ => new(1, 0) + _ => default }; } @@ -166,7 +166,77 @@ public static Format DirectX12(PixelFormat pixelFormat) PixelFormat.BC7UNorm => Format.FormatBC7Unorm, PixelFormat.BC7SRgb => Format.FormatBC7UnormSrgb, - _ => Format.FormatUnknown + _ => default + }; + } + + public static Filter DirectX12(FilterMode minFilter, FilterMode magFilter, FilterMode mipFilter, uint maxAnisotropy, CompareOp compareOp) + { + if (maxAnisotropy > 1) + { + return compareOp is CompareOp.Never ? Filter.Anisotropic : Filter.ComparisonAnisotropic; + } + + return (minFilter, magFilter, mipFilter, compareOp) switch + { + (FilterMode.Point, FilterMode.Point, FilterMode.Point, CompareOp.Never) => Filter.MinMagMipPoint, + (FilterMode.Point, FilterMode.Point, FilterMode.Linear, CompareOp.Never) => Filter.MinMagPointMipLinear, + (FilterMode.Point, FilterMode.Linear, FilterMode.Point, CompareOp.Never) => Filter.MinPointMagLinearMipPoint, + (FilterMode.Point, FilterMode.Linear, FilterMode.Linear, CompareOp.Never) => Filter.MinPointMagMipLinear, + (FilterMode.Linear, FilterMode.Point, FilterMode.Point, CompareOp.Never) => Filter.MinLinearMagMipPoint, + (FilterMode.Linear, FilterMode.Point, FilterMode.Linear, CompareOp.Never) => Filter.MinLinearMagPointMipLinear, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Point, CompareOp.Never) => Filter.MinMagLinearMipPoint, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Linear, CompareOp.Never) => Filter.MinMagMipLinear, + + (FilterMode.Point, FilterMode.Point, FilterMode.Point, _) => Filter.ComparisonMinMagMipPoint, + (FilterMode.Point, FilterMode.Point, FilterMode.Linear, _) => Filter.ComparisonMinMagPointMipLinear, + (FilterMode.Point, FilterMode.Linear, FilterMode.Point, _) => Filter.ComparisonMinPointMagLinearMipPoint, + (FilterMode.Point, FilterMode.Linear, FilterMode.Linear, _) => Filter.ComparisonMinPointMagMipLinear, + (FilterMode.Linear, FilterMode.Point, FilterMode.Point, _) => Filter.ComparisonMinLinearMagMipPoint, + (FilterMode.Linear, FilterMode.Point, FilterMode.Linear, _) => Filter.ComparisonMinLinearMagPointMipLinear, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Point, _) => Filter.ComparisonMinMagLinearMipPoint, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Linear, _) => Filter.ComparisonMinMagMipLinear, + + _ => default + }; + } + + public static TextureAddressMode DirectX12(AddressMode addressMode) + { + return addressMode switch + { + AddressMode.Wrap => TextureAddressMode.Wrap, + AddressMode.Mirror => TextureAddressMode.Mirror, + AddressMode.Clamp => TextureAddressMode.Clamp, + AddressMode.Border => TextureAddressMode.Border, + _ => default + }; + } + + public static ComparisonFunc DirectX12(CompareOp compareOp) + { + return compareOp switch + { + CompareOp.Never => ComparisonFunc.Never, + CompareOp.Less => ComparisonFunc.Less, + CompareOp.Equal => ComparisonFunc.Equal, + CompareOp.LessEqual => ComparisonFunc.LessEqual, + CompareOp.Greater => ComparisonFunc.Greater, + CompareOp.NotEqual => ComparisonFunc.NotEqual, + CompareOp.GreaterEqual => ComparisonFunc.GreaterEqual, + CompareOp.Always => ComparisonFunc.Always, + _ => default + }; + } + + public static (float R, float G, float B, float A) DirectX12(BorderColor borderColor) + { + return borderColor switch + { + BorderColor.TransparentBlack => (0.0f, 0.0f, 0.0f, 0.0f), + BorderColor.OpaqueBlack => (0.0f, 0.0f, 0.0f, 1.0f), + BorderColor.OpaqueWhite => (1.0f, 1.0f, 1.0f, 1.0f), + _ => default }; } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 179bca83..c0fca984 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -178,12 +178,12 @@ protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) protected override Sampler CreateSamplerImpl(SamplerDesc desc) { - throw new NotImplementedException(); + return new DXSampler(this, desc); } protected override Shader CreateShaderImpl(ShaderDesc desc) { - throw new NotImplementedException(); + return new DXShader(this, desc); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs new file mode 100644 index 00000000..c8ba7d7a --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXSampler.cs @@ -0,0 +1,52 @@ +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXSampler : Sampler +{ + public DXDescriptorToken Token; + + public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, desc) + { + Token = context.SamplerHeap.Allocate(); + + (float r, float g, float b, float a) = DXFormats.DirectX12(desc.BorderColor); + + DxSamplerDesc samplerDesc = new() + { + Filter = DXFormats.DirectX12(desc.MinFilter, desc.MagFilter, desc.MipFilter, desc.MaxAnisotropy, desc.CompareOp), + AddressU = DXFormats.DirectX12(desc.AddressU), + AddressV = DXFormats.DirectX12(desc.AddressV), + AddressW = DXFormats.DirectX12(desc.AddressW), + MipLODBias = desc.LodBias, + MaxAnisotropy = desc.MaxAnisotropy, + ComparisonFunc = DXFormats.DirectX12(desc.CompareOp), + MinLOD = desc.MinLod, + MaxLOD = desc.MaxLod + }; + + samplerDesc.BorderColor[0] = r; + samplerDesc.BorderColor[1] = g; + samplerDesc.BorderColor[2] = b; + samplerDesc.BorderColor[3] = a; + + context.Device10.CreateSampler(&samplerDesc, Token.CpuHandle); + } + + public override ResourceHandle Handle => Token.ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + Token.Dispose(); + } +} + diff --git a/sources/Zenith.NET.DirectX12/DXShader.cs b/sources/Zenith.NET.DirectX12/DXShader.cs new file mode 100644 index 00000000..21207be3 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXShader.cs @@ -0,0 +1,28 @@ +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXShader(DXGraphicsContext context, ShaderDesc desc) : Shader(context, desc) +{ + public ShaderBytecode GetShaderBytecode(ZenithMarshal.Scope scope) + { + return new() + { + PShaderBytecode = (byte*)ZenithMarshal.AllocateAndFill(scope, Desc.Bytecode), + BytecodeLength = (uint)Desc.Bytecode.Length + }; + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + } +} diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 76998cae..3f1e3d4d 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -59,4 +59,20 @@ internal DXTextureView DirectX12() return (DXTextureView)textureView; } } + + extension(Sampler sampler) + { + internal DXSampler DirectX12() + { + return (DXSampler)sampler; + } + } + + extension(Shader shader) + { + internal DXShader DirectX12() + { + return (DXShader)shader; + } + } } diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 27d86b43..ad9afb33 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,3 +1,4 @@ global using DxHeapType = Silk.NET.Direct3D12.HeapType; global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxRange = Silk.NET.Direct3D12.Range; +global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; From fdd976b5614892d2c124d4d9852b1c9782c6ab6b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 14:27:12 +0800 Subject: [PATCH 100/466] Add DirectX 12 heap support and placed resource creation Implemented DXHeap for ID3D12Heap management and placed resource creation. Updated DXBuffer and DXTexture to accept existing resources and added static ResourceDesc methods. Modified DXGraphicsContext for heap creation and resource alignment queries. Added heap type mapping, extension methods for type casting, and updated type aliases. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 58 +++++++++------ sources/Zenith.NET.DirectX12/DXFormats.cs | 11 +++ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 20 ++++-- sources/Zenith.NET.DirectX12/DXHeap.cs | 70 +++++++++++++++++++ sources/Zenith.NET.DirectX12/DXTexture.cs | 56 +++++++++------ sources/Zenith.NET.DirectX12/Extensions.cs | 8 +++ sources/Zenith.NET.DirectX12/Usings.cs | 3 +- 7 files changed, 175 insertions(+), 51 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXHeap.cs diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 61dc153d..f7ef2fb0 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -10,31 +10,28 @@ internal unsafe class DXBuffer : Buffer public ulong GPUVirtualAddress; - public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc) + public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr? resource) : base(context, desc) { - ResourceDesc1 resourceDesc = new() + if (resource is null) { - Dimension = ResourceDimension.Buffer, - Width = ZenithHelper.Align(desc.SizeInBytes, 256u), - Height = 1, - DepthOrArraySize = 1, - MipLevels = 1, - SampleDesc = new(1, 0), - Layout = TextureLayout.LayoutRowMajor, - Flags = DXFormats.DirectX12(desc.Usages) - }; - - HeapProperties heapProperties = new(DXFormats.DirectX12(desc.Access)); - - context.Device10.CreateCommittedResource3(&heapProperties, - HeapFlags.None, - &resourceDesc, - BarrierLayout.Undefined, - default, - default(ComPtr), - 0, - default, - out Resource).Success(); + ResourceDesc1 resourceDesc = ResourceDesc(desc); + + HeapProperties heapProperties = new(DXFormats.DirectX12(desc.Access)); + + context.Device10.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default, + default(ComPtr), + 0, + default, + out Resource).Success(); + } + else + { + Resource = resource.Value; + } GPUVirtualAddress = Resource.GetGPUVirtualAddress(); @@ -83,4 +80,19 @@ protected override void Destroy() Resource.Dispose(); } + + public static ResourceDesc1 ResourceDesc(BufferDesc desc) + { + return new() + { + Dimension = ResourceDimension.Buffer, + Width = ZenithHelper.Align(desc.SizeInBytes, 256u), + Height = 1, + DepthOrArraySize = 1, + MipLevels = 1, + SampleDesc = new(1, 0), + Layout = TextureLayout.LayoutRowMajor, + Flags = DXFormats.DirectX12(desc.Usages) + }; + } } diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 2899d183..07a27d08 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -5,6 +5,17 @@ namespace Zenith.NET.DirectX12; internal static class DXFormats { + public static DxHeapType DirectX12(HeapType heapType) + { + return heapType switch + { + HeapType.GpuOnly => DxHeapType.Default, + HeapType.CpuReadOnly => DxHeapType.Readback, + HeapType.CpuWriteOnly => DxHeapType.Upload, + _ => default + }; + } + public static ResourceFlags DirectX12(BufferUsages bufferUsages) { ResourceFlags result = ResourceFlags.None; diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index c0fca984..eb9ee9b1 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -6,6 +6,8 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.DirectX12, useValidationLayer) { + public const ulong DefaultHeapAlignment = 4194304; + public const uint Shader4ComponentMapping = 0x1688; public ComPtr Factory7; @@ -143,22 +145,30 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) protected override Heap CreateHeapImpl(HeapDesc desc) { - throw new NotImplementedException(); + return new DXHeap(this, desc); } protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - throw new NotImplementedException(); + ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); + + ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(Span)); + + return new(info.SizeInBytes, info.Alignment); } protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { - throw new NotImplementedException(); + ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); + + ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(Span)); + + return new(info.SizeInBytes, info.Alignment); } protected override Buffer CreateBufferImpl(BufferDesc desc) { - return new DXBuffer(this, desc); + return new DXBuffer(this, desc, null); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) @@ -168,7 +178,7 @@ protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc) { - return new DXTexture(this, desc); + return new DXTexture(this, desc, null); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs new file mode 100644 index 00000000..0b95e1e4 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -0,0 +1,70 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXHeap : Heap +{ + public ComPtr Heap; + + public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) + { + DxHeapDesc heapDesc = new() + { + SizeInBytes = ZenithHelper.Align(desc.SizeInBytes, DXGraphicsContext.DefaultHeapAlignment), + Properties = new(DXFormats.DirectX12(desc.Type)), + Alignment = DXGraphicsContext.DefaultHeapAlignment + }; + + context.Device10.CreateHeap(&heapDesc, out Heap).Success(); + } + + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) + { + ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); + + Context.Device10.CreatePlacedResource2(Heap, + offsetInBytes, + &resourceDesc, + BarrierLayout.Undefined, + default, + 0, + default, + out ComPtr resource).Success(); + + return new DXBuffer(Context, desc, resource); + } + + protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) + { + ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); + + Context.Device10.CreatePlacedResource2(Heap, + offsetInBytes, + &resourceDesc, + BarrierLayout.Undefined, + default, + 0, + default, + out ComPtr resource).Success(); + + return new DXTexture(Context, desc, resource); + } + + protected override void SetResourceName(string name) + { + Heap.SetName(name).Success(); + } + + protected override void Destroy() + { + Heap.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index e579d05a..4c0a7875 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -7,32 +7,28 @@ internal unsafe class DXTexture : Texture { public ComPtr Resource; - public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, desc) + public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr? resource) : base(context, desc) { - ResourceDesc1 resourceDesc = new() + if (resource is null) { - Dimension = DXFormats.DirectX12(desc.Type), - Width = desc.Width, - Height = desc.Height, - DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : desc.ArrayLayers), - MipLevels = (ushort)desc.MipLevels, - Format = DXFormats.DirectX12(desc.Format), - SampleDesc = DXFormats.DirectX12(desc.SampleCount), - Layout = TextureLayout.LayoutUnknown, - Flags = DXFormats.DirectX12(desc.Usages) - }; + ResourceDesc1 resourceDesc = ResourceDesc(desc); - HeapProperties heapProperties = new(DxHeapType.Default); + HeapProperties heapProperties = new(DxHeapType.Default); - context.Device10.CreateCommittedResource3(&heapProperties, - HeapFlags.None, - &resourceDesc, - BarrierLayout.Undefined, - default, - default(ComPtr), - 0, - default, - out Resource).Success(); + context.Device10.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default, + default(ComPtr), + 0, + default, + out Resource).Success(); + } + else + { + Resource = resource.Value; + } View = new(context, new() { @@ -69,4 +65,20 @@ protected override void Destroy() Resource.Dispose(); } + + public static ResourceDesc1 ResourceDesc(TextureDesc desc) + { + return new() + { + Dimension = DXFormats.DirectX12(desc.Type), + Width = desc.Width, + Height = desc.Height, + DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : desc.ArrayLayers), + MipLevels = (ushort)desc.MipLevels, + Format = DXFormats.DirectX12(desc.Format), + SampleDesc = DXFormats.DirectX12(desc.SampleCount), + Layout = TextureLayout.LayoutUnknown, + Flags = DXFormats.DirectX12(desc.Usages) + }; + } } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 3f1e3d4d..42c54bdb 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -28,6 +28,14 @@ internal bool IsSuccess() } } + extension(Heap heap) + { + internal DXHeap DirectX12() + { + return (DXHeap)heap; + } + } + extension(Buffer buffer) { internal DXBuffer DirectX12() diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index ad9afb33..4ef5f2d7 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,4 +1,5 @@ -global using DxHeapType = Silk.NET.Direct3D12.HeapType; +global using DxHeapDesc = Silk.NET.Direct3D12.HeapDesc; +global using DxHeapType = Silk.NET.Direct3D12.HeapType; global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxRange = Silk.NET.Direct3D12.Range; global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; From 41acbc443e33d72c85d5e9660638fd609dd3aa33 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 14:38:18 +0800 Subject: [PATCH 101/466] Refactor: remove backend-specific implementations Removed all Metal and Vulkan backend-specific logic from resource, pipeline, swapchain, and command buffer classes. Stubbed out all resource creation and initialization in `GraphicsContext` classes. Deleted extension methods for backend type conversions and removed related global usings. This prepares the codebase for future refactoring or new backend integration. --- sources/Zenith.NET.Metal/Extensions.cs | 120 --- .../MTLBottomLevelAccelerationStructure.cs | 126 --- sources/Zenith.NET.Metal/MTLBuffer.cs | 38 - sources/Zenith.NET.Metal/MTLBufferView.cs | 14 - sources/Zenith.NET.Metal/MTLCapabilities.cs | 12 - sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 467 ------------ sources/Zenith.NET.Metal/MTLCommandEncoder.cs | 382 ---------- sources/Zenith.NET.Metal/MTLCommandQueue.cs | 34 - .../Zenith.NET.Metal/MTLComputePipeline.cs | 29 - sources/Zenith.NET.Metal/MTLFence.cs | 31 - sources/Zenith.NET.Metal/MTLFormats.cs | 533 ------------- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 105 +-- .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 122 --- sources/Zenith.NET.Metal/MTLHeap.cs | 60 -- .../MTLMeshShadingPipeline.cs | 100 --- sources/Zenith.NET.Metal/MTLQueryHeap.cs | 53 -- sources/Zenith.NET.Metal/MTLResourceTable.cs | 168 ---- sources/Zenith.NET.Metal/MTLSampler.cs | 39 - sources/Zenith.NET.Metal/MTLShader.cs | 33 - sources/Zenith.NET.Metal/MTLSwapChain.cs | 107 --- .../Zenith.NET.Metal/MTLSwapChainTargets.cs | 71 -- sources/Zenith.NET.Metal/MTLTexture.cs | 33 - sources/Zenith.NET.Metal/MTLTextureView.cs | 33 - .../MTLTopLevelAccelerationStructure.cs | 108 --- sources/Zenith.NET.Metal/NSAutorelease.cs | 20 - sources/Zenith.NET.Metal/Usings.cs | 4 - sources/Zenith.NET.Vulkan/Extensions.cs | 151 ---- sources/Zenith.NET.Vulkan/Usings.cs | 15 - .../VKBottomLevelAccelerationStructure.cs | 177 ----- sources/Zenith.NET.Vulkan/VKBuffer.cs | 127 --- sources/Zenith.NET.Vulkan/VKBufferView.cs | 26 - sources/Zenith.NET.Vulkan/VKCapabilities.cs | 33 - sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 633 --------------- sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 52 -- .../Zenith.NET.Vulkan/VKComputePipeline.cs | 63 -- sources/Zenith.NET.Vulkan/VKDeviceMemory.cs | 131 ---- sources/Zenith.NET.Vulkan/VKFence.cs | 39 - sources/Zenith.NET.Vulkan/VKFormats.cs | 721 ------------------ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 474 +----------- .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 251 ------ .../VKMeshShadingPipeline.cs | 222 ------ sources/Zenith.NET.Vulkan/VKQueryHeap.cs | 59 -- sources/Zenith.NET.Vulkan/VKResourceTable.cs | 169 ---- sources/Zenith.NET.Vulkan/VKSampler.cs | 54 -- sources/Zenith.NET.Vulkan/VKShader.cs | 33 - sources/Zenith.NET.Vulkan/VKSwapChain.cs | 314 -------- .../Zenith.NET.Vulkan/VKSwapChainTargets.cs | 90 --- sources/Zenith.NET.Vulkan/VKTexture.cs | 184 ----- sources/Zenith.NET.Vulkan/VKTextureView.cs | 69 -- .../VKTopLevelAccelerationStructure.cs | 208 ----- .../Zenith.NET.Vulkan/VKValidationLayer.cs | 55 -- 51 files changed, 62 insertions(+), 7130 deletions(-) delete mode 100644 sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs delete mode 100644 sources/Zenith.NET.Metal/MTLBuffer.cs delete mode 100644 sources/Zenith.NET.Metal/MTLBufferView.cs delete mode 100644 sources/Zenith.NET.Metal/MTLCapabilities.cs delete mode 100644 sources/Zenith.NET.Metal/MTLCommandBuffer.cs delete mode 100644 sources/Zenith.NET.Metal/MTLCommandEncoder.cs delete mode 100644 sources/Zenith.NET.Metal/MTLCommandQueue.cs delete mode 100644 sources/Zenith.NET.Metal/MTLComputePipeline.cs delete mode 100644 sources/Zenith.NET.Metal/MTLFence.cs delete mode 100644 sources/Zenith.NET.Metal/MTLFormats.cs delete mode 100644 sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs delete mode 100644 sources/Zenith.NET.Metal/MTLHeap.cs delete mode 100644 sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs delete mode 100644 sources/Zenith.NET.Metal/MTLQueryHeap.cs delete mode 100644 sources/Zenith.NET.Metal/MTLResourceTable.cs delete mode 100644 sources/Zenith.NET.Metal/MTLSampler.cs delete mode 100644 sources/Zenith.NET.Metal/MTLShader.cs delete mode 100644 sources/Zenith.NET.Metal/MTLSwapChain.cs delete mode 100644 sources/Zenith.NET.Metal/MTLSwapChainTargets.cs delete mode 100644 sources/Zenith.NET.Metal/MTLTexture.cs delete mode 100644 sources/Zenith.NET.Metal/MTLTextureView.cs delete mode 100644 sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs delete mode 100644 sources/Zenith.NET.Metal/NSAutorelease.cs delete mode 100644 sources/Zenith.NET.Metal/Usings.cs delete mode 100644 sources/Zenith.NET.Vulkan/Usings.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKBuffer.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKBufferView.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKCapabilities.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKCommandBuffer.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKCommandQueue.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKComputePipeline.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKDeviceMemory.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKFence.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKFormats.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKQueryHeap.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKResourceTable.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKSampler.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKShader.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKSwapChain.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKTexture.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKTextureView.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs delete mode 100644 sources/Zenith.NET.Vulkan/VKValidationLayer.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index f80f28d6..c44f7770 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -25,124 +25,4 @@ internal void Success() } } } - - extension(CommandBuffer commandBuffer) - { - internal MTLCommandBuffer Metal() - { - return (MTLCommandBuffer)commandBuffer; - } - } - - extension(SwapChain swapChain) - { - internal MTLSwapChain Metal() - { - return (MTLSwapChain)swapChain; - } - } - - extension(Shader shader) - { - internal MTLShader Metal() - { - return (MTLShader)shader; - } - } - - extension(Buffer buffer) - { - internal MTLBuffer Metal() - { - return (MTLBuffer)buffer; - } - } - - extension(BufferView bufferView) - { - internal MTLBufferView Metal() - { - return (MTLBufferView)bufferView; - } - } - - extension(Texture texture) - { - internal MTLTexture Metal() - { - return (MTLTexture)texture; - } - } - - extension(TextureView textureView) - { - internal MTLTextureView Metal() - { - return (MTLTextureView)textureView; - } - } - - extension(Sampler sampler) - { - internal MTLSampler Metal() - { - return (MTLSampler)sampler; - } - } - - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) - { - internal MTLBottomLevelAccelerationStructure Metal() - { - return (MTLBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; - } - } - - extension(TopLevelAccelerationStructure topLevelAccelerationStructure) - { - internal MTLTopLevelAccelerationStructure Metal() - { - return (MTLTopLevelAccelerationStructure)topLevelAccelerationStructure; - } - } - - extension(ResourceTable resourceTable) - { - internal MTLResourceTable Metal() - { - return (MTLResourceTable)resourceTable; - } - } - - extension(GraphicsPipeline graphicsPipeline) - { - internal MTLGraphicsPipeline Metal() - { - return (MTLGraphicsPipeline)graphicsPipeline; - } - } - - extension(ComputePipeline computePipeline) - { - internal MTLComputePipeline Metal() - { - return (MTLComputePipeline)computePipeline; - } - } - - extension(MeshShadingPipeline meshShadingPipeline) - { - internal MTLMeshShadingPipeline Metal() - { - return (MTLMeshShadingPipeline)meshShadingPipeline; - } - } - - extension(QueryHeap queryHeap) - { - internal MTLQueryHeap Metal() - { - return (MTLQueryHeap)queryHeap; - } - } } \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs deleted file mode 100644 index 810248e5..00000000 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLBottomLevelAccelerationStructure : BottomLevelAccelerationStructure -{ - public MTLAccelerationStructure AccelerationStructure; - - public unsafe MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, BottomLevelAccelerationStructureDesc desc, MTLCommandBuffer commandBuffer) : base(context, desc) - { - uint geometryCount = (uint)desc.Geometries.Length; - - TransformBuffer = new(context, new() - { - SizeInBytes = (uint)(sizeof(MTLPackedFloat4x3) * geometryCount), - StrideInBytes = (uint)sizeof(MTLPackedFloat4x3), - Flags = BufferUsageFlags.AccelerationStructure | BufferUsageFlags.MapWrite - }); - - MappedMemory mappedMemory = TransformBuffer.Map(); - - MTLPackedFloat4x3[] transforms = desc.Geometries.Select(static item => MTLFormats.Metal(item.Triangles.Transform)).ToArray(); - - if (geometryCount > 0) - { - unsafe - { - transforms.AsSpan().CopyTo(new Span((void*)mappedMemory.Pointer, (int)geometryCount)); - } - } - - TransformBuffer.Unmap(); - - MTL4AccelerationStructureGeometryDescriptor[] geometryDescriptors = new MTL4AccelerationStructureGeometryDescriptor[geometryCount]; - for (uint i = 0; i < geometryCount; i++) - { - RayTracingGeometry geometry = desc.Geometries[i]; - - switch (geometry.Type) - { - case RayTracingGeometryType.Triangles: - geometryDescriptors[i] = new MTL4AccelerationStructureTriangleGeometryDescriptor() - { - VertexBuffer = new() - { - BufferAddress = geometry.Triangles.VertexBuffer.Metal().Buffer.GpuAddress + geometry.Triangles.VertexOffsetInBytes, - Length = geometry.Triangles.VertexStrideInBytes * geometry.Triangles.VertexCount - }, - VertexFormat = MTLFormats.Metal(geometry.Triangles.VertexFormat).AttributeFormat, - VertexStride = geometry.Triangles.VertexStrideInBytes, - IndexBuffer = geometry.Triangles.IndexBuffer is not null ? new() - { - BufferAddress = geometry.Triangles.IndexBuffer.Metal().Buffer.GpuAddress + geometry.Triangles.IndexOffsetInBytes, - Length = (uint)(geometry.Triangles.IndexFormat is IndexFormat.UInt16 ? sizeof(ushort) : sizeof(uint)) * geometry.Triangles.IndexCount - } : default, - IndexType = MTLFormats.Metal(geometry.Triangles.IndexFormat), - TriangleCount = geometry.Triangles.IndexBuffer is not null ? geometry.Triangles.IndexCount / 3 : geometry.Triangles.VertexCount / 3, - TransformationMatrixBuffer = new() - { - BufferAddress = TransformBuffer.GpuAddress + (uint)(sizeof(MTLPackedFloat4x3) * i), - Length = (uint)sizeof(MTLPackedFloat4x3) - }, - TransformationMatrixLayout = MTLMatrixLayout.RowMajor, - Opaque = geometry.Flags.HasFlag(RayTracingGeometryFlags.Opaque) - }; - break; - - case RayTracingGeometryType.AABBs: - geometryDescriptors[i] = new MTL4AccelerationStructureBoundingBoxGeometryDescriptor() - { - BoundingBoxBuffer = new() - { - BufferAddress = geometry.AABBs.Buffer.Metal().Buffer.GpuAddress + geometry.AABBs.OffsetInBytes, - Length = geometry.AABBs.StrideInBytes * geometry.AABBs.Count - }, - BoundingBoxStride = geometry.AABBs.StrideInBytes, - BoundingBoxCount = geometry.AABBs.Count, - Opaque = geometry.Flags.HasFlag(RayTracingGeometryFlags.Opaque) - }; - break; - } - } - - MTL4PrimitiveAccelerationStructureDescriptor descriptor = new() - { - GeometryDescriptors = geometryDescriptors, - Usage = MTLFormats.Metal(desc.Flags) - }; - - MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); - - AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize); - context.AddAllocation(AccelerationStructure); - - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)sizes.BuildScratchBufferSize, - StrideInBytes = (uint)sizes.BuildScratchBufferSize, - Flags = BufferUsageFlags.ShaderResource - }); - - commandBuffer.CommandEncoder.Compute?.Build(AccelerationStructure, descriptor, new(ScratchBuffer.Buffer.GpuAddress, ScratchBuffer.Desc.SizeInBytes)); - commandBuffer.CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); - } - - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - - public MTLBuffer TransformBuffer { get; } - - public MTLBuffer ScratchBuffer { get; } - - protected override void SetResourceName(string name) - { - AccelerationStructure.Label = name; - } - - protected override void Destroy() - { - Context.RemoveAllocation(AccelerationStructure); - - AccelerationStructure.Dispose(); - - ScratchBuffer.Dispose(); - TransformBuffer.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs deleted file mode 100644 index 9c499c77..00000000 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Zenith.NET.Metal; - -internal class MTLBuffer : Buffer -{ - public MtlBuffer Buffer; - - public nuint GpuAddress; - - public MTLBuffer(MTLGraphicsContext context, BufferDesc desc) : base(context, desc) - { - Heap = new(context, desc, out Buffer); - - GpuAddress = Buffer.GpuAddress; - } - - public MTLHeap Heap { get; } - - protected override MappedMemory MapImpl() - { - return new() { Pointer = Buffer.Contents(), SizeInBytes = Desc.SizeInBytes }; - } - - public override void Unmap() - { - } - - protected override void SetResourceName(string name) - { - Buffer.Label = name; - } - - protected override void Destroy() - { - Buffer.Dispose(); - - Heap.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs deleted file mode 100644 index c32ee139..00000000 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Zenith.NET.Metal; - -internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) -{ - public nuint GpuAddress = desc.Buffer.Metal().GpuAddress + desc.OffsetInBytes; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - } -} diff --git a/sources/Zenith.NET.Metal/MTLCapabilities.cs b/sources/Zenith.NET.Metal/MTLCapabilities.cs deleted file mode 100644 index f46a5354..00000000 --- a/sources/Zenith.NET.Metal/MTLCapabilities.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLCapabilities(MTLGraphicsContext context) : Capabilities -{ - public override string DeviceName { get; } = context.Device.Name; - - public override bool RayTracingSupported { get; } = context.Device.SupportsRaytracingFromRender; - - public override bool MeshShadingSupported { get; } = context.Device.SupportsFamily(MTLGPUFamily.Apple7) || context.Device.SupportsFamily(MTLGPUFamily.Mac2); -} diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs deleted file mode 100644 index c9b7c392..00000000 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ /dev/null @@ -1,467 +0,0 @@ -using System.Numerics; -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal unsafe class MTLCommandBuffer : CommandBuffer -{ - private MTL4RenderPassDescriptor? currentRenderPassDescriptor; - - public MTL4CommandAllocator CommandAllocator; - - public MTL4CommandBuffer CommandBuffer; - - public MTLCommandBuffer(MTLGraphicsContext context, CommandQueue queue) : base(context, queue) - { - CommandAllocator = context.Device.MakeCommandAllocator(); - CommandBuffer = NSAutorelease.Own(context.Device.MakeCommandBuffer); - - CommandEncoder = new(context, CommandBuffer); - } - - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - - public MTLCommandEncoder CommandEncoder { get; } - - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) - { - MTLBuffer mtlSrc = src.Metal(); - MTLBuffer mtlDest = dest.Metal(); - - CommandEncoder.Compute?.Copy(mtlSrc.Buffer, srcOffsetInBytes, mtlDest.Buffer, destOffsetInBytes, sizeInBytes); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override void CopyBufferToTextureImpl(Buffer src, - uint srcOffsetInBytes, - TextureDataLayout srcLayout, - Texture dest, - TextureSubresource destSubresource, - Offset3D destOffset, - Extent3D destExtent) - { - MTLBuffer mtlSrc = src.Metal(); - MTLTexture mtlDest = dest.Metal(); - - CommandEncoder.Compute?.Copy(mtlSrc.Buffer, - srcOffsetInBytes, - srcLayout.RowPitchInBytes, - srcLayout.SlicePitchInBytes, - new(destExtent.Width, destExtent.Height, destExtent.Depth), - mtlDest.Texture, - destSubresource.ArrayLayer, - destSubresource.MipLevel, - new(destOffset.X, destOffset.Y, destOffset.Z)); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override void CopyTextureImpl(Texture src, - TextureSubresource srcSubresource, - Offset3D srcOffset, - Texture dest, - TextureSubresource destSubresource, - Offset3D destOffset, - Extent3D extent) - { - MTLTexture mtlSrc = src.Metal(); - MTLTexture mtlDest = dest.Metal(); - - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - srcSubresource.ArrayLayer, - srcSubresource.MipLevel, - new(srcOffset.X, srcOffset.Y, srcOffset.Z), - new(extent.Width, extent.Height, extent.Depth), - mtlDest.Texture, - destSubresource.ArrayLayer, - destSubresource.MipLevel, - new(destOffset.X, destOffset.Y, destOffset.Z)); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override void CopyTextureToBufferImpl(Texture src, - TextureSubresource srcSubresource, - Offset3D srcOffset, - Extent3D srcExtent, - Buffer dest, - uint destOffsetInBytes, - TextureDataLayout destLayout) - { - MTLTexture mtlSrc = src.Metal(); - MTLBuffer mtlDest = dest.Metal(); - - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - srcSubresource.ArrayLayer, - srcSubresource.MipLevel, - new(srcOffset.X, srcOffset.Y, srcOffset.Z), - new(srcExtent.Width, srcExtent.Height, srcExtent.Depth), - mtlDest.Buffer, - destOffsetInBytes, - destLayout.RowPitchInBytes, - destLayout.SlicePitchInBytes); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override TextureDataLayout GetTextureCopyLayout(PixelFormat format, Extent3D extent) - { - uint rowPitchInBytes = ZenithHelper.RowPitchInBytes(format, extent.Width, extent.Height); - uint slicePitchInBytes = ZenithHelper.SlicePitchInBytes(format, extent.Width, extent.Height); - - return new() - { - SizeInBytes = slicePitchInBytes * extent.Depth, - RowPitchInBytes = rowPitchInBytes, - SlicePitchInBytes = slicePitchInBytes - }; - } - - protected override void ResolveTextureImpl(Texture src, - TextureSubresource srcSubresource, - Texture dest, - TextureSubresource destSubresource) - { - MTLTexture mtlSrc = src.Metal(); - MTLTexture mtlDest = dest.Metal(); - - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - srcSubresource.ArrayLayer, - srcSubresource.MipLevel, - mtlDest.Texture, - destSubresource.ArrayLayer, - destSubresource.MipLevel, - 1, - 1); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) - { - return new MTLBottomLevelAccelerationStructure(Context, desc, this); - } - - protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) - { - return new MTLTopLevelAccelerationStructure(Context, desc, this); - } - - protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) - { - accelerationStructure.Metal().Update(this, newDesc); - } - - protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment) - { - currentRenderPassDescriptor?.Dispose(); - - MTL4RenderPassDescriptor descriptor = currentRenderPassDescriptor = new(); - - uint colorAttachmentCount = (uint)colorAttachments.Length; - - for (uint i = 0; i < colorAttachmentCount; i++) - { - ColorAttachment colorRenderTarget = colorAttachments[(int)i]; - MTLRenderPassColorAttachmentDescriptor colorAttachment = descriptor.ColorAttachments[i] = new() - { - Texture = colorRenderTarget.Texture.Metal().Texture, - Level = colorRenderTarget.Subresource.MipLevel, - Slice = colorRenderTarget.Subresource.ArrayLayer, - LoadAction = colorRenderTarget.LoadOp switch - { - LoadOp.Clear => MTLLoadAction.Clear, - LoadOp.DontCare => MTLLoadAction.DontCare, - _ => MTLLoadAction.Load - }, - StoreAction = colorRenderTarget.StoreOp is StoreOp.Store ? MTLStoreAction.Store : MTLStoreAction.DontCare - }; - - if (colorRenderTarget.LoadOp is LoadOp.Clear) - { - Vector4 color = colorRenderTarget.ClearColor; - - colorAttachment.ClearColor = new() - { - Red = color.X, - Green = color.Y, - Blue = color.Z, - Alpha = color.W - }; - } - } - - if (depthStencilAttachment is { } attachment) - { - if (ZenithHelper.HasDepth(attachment.Texture.Desc.Format)) - { - MTLRenderPassDepthAttachmentDescriptor depthAttachment = descriptor.DepthAttachment = new() - { - Texture = attachment.Texture.Metal().Texture, - Level = attachment.Subresource.MipLevel, - Slice = attachment.Subresource.ArrayLayer, - LoadAction = attachment.DepthLoadOp switch - { - LoadOp.Clear => MTLLoadAction.Clear, - LoadOp.DontCare => MTLLoadAction.DontCare, - _ => MTLLoadAction.Load - }, - StoreAction = attachment.DepthStoreOp is StoreOp.Store ? MTLStoreAction.Store : MTLStoreAction.DontCare - }; - - if (attachment.DepthLoadOp is LoadOp.Clear) - { - depthAttachment.ClearDepth = attachment.ClearDepth; - } - } - - if (ZenithHelper.HasStencil(attachment.Texture.Desc.Format)) - { - MTLRenderPassStencilAttachmentDescriptor stencilAttachment = descriptor.StencilAttachment = new() - { - Texture = attachment.Texture.Metal().Texture, - Level = attachment.Subresource.MipLevel, - Slice = attachment.Subresource.ArrayLayer, - LoadAction = attachment.StencilLoadOp switch - { - LoadOp.Clear => MTLLoadAction.Clear, - LoadOp.DontCare => MTLLoadAction.DontCare, - _ => MTLLoadAction.Load - }, - StoreAction = attachment.StencilStoreOp is StoreOp.Store ? MTLStoreAction.Store : MTLStoreAction.DontCare - }; - - if (attachment.StencilLoadOp is LoadOp.Clear) - { - stencilAttachment.ClearStencil = attachment.ClearStencil; - } - } - } - - CommandEncoder.BeginRenderPass(descriptor); - } - - protected override void EndRenderPassImpl() - { - CommandEncoder.EndRenderPass(); - - currentRenderPassDescriptor?.Dispose(); - currentRenderPassDescriptor = null; - } - - protected override void SetScissorsImpl(ReadOnlySpan scissors) - { - CommandEncoder.SetScissors(scissors); - } - - protected override void SetViewportsImpl(ReadOnlySpan viewports) - { - CommandEncoder.SetViewports(viewports); - } - - protected override void SetPipelineImpl(GraphicsPipeline pipeline) - { - CommandEncoder.SetPipeline(pipeline); - } - - protected override void SetPipelineImpl(ComputePipeline pipeline) - { - CommandEncoder.SetPipeline(pipeline); - } - - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) - { - CommandEncoder.SetPipeline(pipeline); - } - - protected override void SetStencilReferenceImpl(uint reference) - { - CommandEncoder.SetStencilReference(reference); - } - - protected override void SetBlendConstantImpl(Vector4 blendConstant) - { - CommandEncoder.SetBlendConstant(blendConstant); - } - - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) - { - CommandEncoder.SetVertexBuffer(buffer, offsetInBytes, index); - } - - protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format) - { - CommandEncoder.SetIndexBuffer(buffer, offsetInBytes, format); - } - - protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) - { - CommandEncoder.PushResourceTable(resourceTable); - } - - protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) - { - CommandEncoder.Bind(); - - CommandEncoder.Render?.DrawPrimitives(CommandEncoder.PrimitiveType, firstVertex, vertexCount, instanceCount, firstInstance); - } - - protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - CommandEncoder.Bind(); - - nuint indirectGpuAddress = indirectBuffer.Metal().GpuAddress + offsetInBytes; - - for (uint i = 0; i < drawCount; i++) - { - CommandEncoder.Render?.DrawPrimitives(CommandEncoder.PrimitiveType, indirectGpuAddress + ((uint)sizeof(IndirectDrawArgs) * i)); - } - } - - protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) - { - CommandEncoder.Bind(); - - CommandEncoder.Render?.DrawIndexedPrimitives(CommandEncoder.PrimitiveType, - indexCount, - CommandEncoder.IndexType, - CommandEncoder.IndexBuffer + (CommandEncoder.IndexStrideInBytes * firstIndex), - CommandEncoder.IndexSizeInBytes - (CommandEncoder.IndexStrideInBytes * firstIndex), - instanceCount, - vertexOffset, - firstInstance); - } - - protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - CommandEncoder.Bind(); - - nuint indirectGpuAddress = indirectBuffer.Metal().GpuAddress + offsetInBytes; - - for (uint i = 0; i < drawCount; i++) - { - CommandEncoder.Render?.DrawIndexedPrimitives(CommandEncoder.PrimitiveType, - CommandEncoder.IndexType, - CommandEncoder.IndexBuffer + (CommandEncoder.IndexStrideInBytes * i), - CommandEncoder.IndexSizeInBytes - (CommandEncoder.IndexStrideInBytes * i), - indirectGpuAddress + ((uint)sizeof(IndirectDrawIndexedArgs) * i)); - } - } - - protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) - { - CommandEncoder.Bind(); - - CommandEncoder.Compute?.DispatchThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), CommandEncoder.ThreadGroupSize); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Dispatch, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) - { - CommandEncoder.Bind(); - - CommandEncoder.Compute?.DispatchThreadgroups(indirectBuffer.Metal().GpuAddress + offsetInBytes, CommandEncoder.ThreadGroupSize); - - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Dispatch, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); - } - - protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) - { - CommandEncoder.Bind(); - - CommandEncoder.Render?.DrawMeshThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), - CommandEncoder.AmplificationThreadGroupSize, - CommandEncoder.MeshThreadGroupSize); - } - - protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) - { - CommandEncoder.Bind(); - - nuint indirectGpuAddress = indirectBuffer.Metal().GpuAddress + offsetInBytes; - - for (uint i = 0; i < dispatchCount; i++) - { - CommandEncoder.Render?.DrawMeshThreadgroups(indirectGpuAddress + ((uint)sizeof(IndirectDispatchMeshArgs) * i), - CommandEncoder.AmplificationThreadGroupSize, - CommandEncoder.MeshThreadGroupSize); - } - } - - protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) - { - CommandEncoder.BeginQuery(queryHeap, index); - } - - protected override void EndQueryImpl(QueryHeap queryHeap, uint index) - { - CommandEncoder.EndQuery(queryHeap, index); - } - - protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) - { - MTLQueryHeap mtlQueryHeap = queryHeap.Metal(); - - CommandBuffer.WriteTimestamp(mtlQueryHeap.CounterHeap, index); - CommandBuffer.ResolveCounterHeap(mtlQueryHeap.CounterHeap, new(index, 1), new(mtlQueryHeap.Buffer.GpuAddress + (sizeof(ulong) * index), sizeof(ulong)), MtlFence.Null, MtlFence.Null); - } - - protected override void BeginDebugEventImpl(string label) - { - CommandEncoder.BeginDebugEvent(label); - } - - protected override void EndDebugEventImpl() - { - CommandEncoder.EndDebugEvent(); - } - - protected override void InsertDebugMarkerImpl(string label) - { - CommandEncoder.InsertDebugMarker(label); - } - - protected override void BeginImpl() - { - CommandBuffer.BeginCommandBuffer(CommandAllocator); - - CommandBuffer.UseResidencySet(Context.ResidencySet); - - CommandEncoder.Begin(); - } - - protected override void EndImpl() - { - CommandEncoder.End(); - - CommandBuffer.EndCommandBuffer(); - } - - protected override void ResetImpl() - { - currentRenderPassDescriptor?.Dispose(); - currentRenderPassDescriptor = null; - - CommandAllocator.Reset(); - } - - protected override void SetResourceName(string name) - { - CommandBuffer.Label = name; - } - - protected override void Destroy() - { - currentRenderPassDescriptor?.Dispose(); - - base.Destroy(); - - CommandEncoder.Dispose(); - - CommandBuffer.Dispose(); - CommandAllocator.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs deleted file mode 100644 index 0ea499d6..00000000 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ /dev/null @@ -1,382 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLCommandEncoder : GraphicsResource -{ - private const uint ArgumentCount = 16; - private const MTLStages RenderStages = MTLStages.Vertex | MTLStages.Fragment | MTLStages.Object | MTLStages.Mesh; - private const MTLStages ComputeStages = MTLStages.Dispatch | MTLStages.Blit | MTLStages.AccelerationStructure; - - private readonly Dictionary todoBeginQueries = []; - private readonly Dictionary todoEndQueries = []; - private readonly Dictionary vertexBuffers = []; - - public MtlFence Fence; - - public MtlBuffer Buffer; - - public MTL4ArgumentTable ArgumentTable; - - private Scissor[]? todoScissors; - private Viewport[]? todoViewports; - private Pipeline? currentPipeline; - private ResourceTable? currentResourceTable; - private bool needsRebind; - - public MTLCommandEncoder(MTLGraphicsContext context, MTL4CommandBuffer commandBuffer) : base(context) - { - CommandBuffer = commandBuffer; - - Fence = context.Device.MakeFence(); - Buffer = context.Device.MakeBuffer(sizeof(ulong) * 128, MTLResourceOptions.StorageModePrivate); - - MTL4ArgumentTableDescriptor descriptor = new() - { - MaxBufferBindCount = ArgumentCount, - MaxTextureBindCount = ArgumentCount, - MaxSamplerStateBindCount = ArgumentCount, - SupportAttributeStrides = true - }; - - ArgumentTable = context.Device.MakeArgumentTable(descriptor, out NSError error); - error.Success(); - } - - public MTL4CommandBuffer CommandBuffer { get; } - - public MTL4RenderCommandEncoder? Render { get; private set; } - - public MTL4ComputeCommandEncoder? Compute { get; private set; } - - public nuint IndexBuffer { get; private set; } - - public uint IndexSizeInBytes { get; private set; } - - public uint IndexStrideInBytes { get; private set; } - - public MTLIndexType IndexType { get; private set; } - - public MTLPrimitiveType PrimitiveType { get; private set; } - - public MTLSize ThreadGroupSize { get; private set; } - - public MTLSize AmplificationThreadGroupSize { get; private set; } - - public MTLSize MeshThreadGroupSize { get; private set; } - - public void Begin() - { - Compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); - } - - public void End() - { - EndRender(); - EndCompute(); - - todoScissors = null; - todoViewports = null; - currentPipeline = null; - currentResourceTable = null; - needsRebind = false; - - todoBeginQueries.Clear(); - todoEndQueries.Clear(); - vertexBuffers.Clear(); - } - - public void BeginRenderPass(MTL4RenderPassDescriptor descriptor) - { - EndCompute(); - - descriptor.VisibilityResultBuffer = Buffer; - - Render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); - Render.WaitForFence(Fence, RenderStages); - - foreach (KeyValuePair beginQuery in todoBeginQueries) - { - BeginQuery(beginQuery.Value, beginQuery.Key); - } - todoBeginQueries.Clear(); - - if (todoScissors is not null) - { - SetScissors(todoScissors); - - todoScissors = null; - } - - if (todoViewports is not null) - { - SetViewports(todoViewports); - - todoViewports = null; - } - } - - public void EndRenderPass() - { - EndRender(); - - Compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); - Compute.WaitForFence(Fence, ComputeStages); - - foreach (KeyValuePair endQuery in todoEndQueries) - { - EndQuery(endQuery.Value, endQuery.Key); - } - todoEndQueries.Clear(); - } - - public void SetScissors(ReadOnlySpan scissors) - { - if (Render is null) - { - todoScissors = scissors.ToArray(); - } - else - { - MTLScissorRect[] mtlScissors = new MTLScissorRect[scissors.Length]; - - for (int i = 0; i < scissors.Length; i++) - { - Scissor scissor = scissors[i]; - - mtlScissors[i] = new((uint)scissor.X, (uint)scissor.Y, scissor.Width, scissor.Height); - } - - Render.SetScissorRects(mtlScissors); - } - } - - public void SetViewports(ReadOnlySpan viewports) - { - if (Render is null) - { - todoViewports = viewports.ToArray(); - } - else - { - MTLViewport[] mtlViewports = new MTLViewport[viewports.Length]; - - for (int i = 0; i < viewports.Length; i++) - { - Viewport viewport = viewports[i]; - - mtlViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); - } - - Render.SetViewports(mtlViewports); - } - } - - public void SetPipeline(GraphicsPipeline pipeline) - { - currentPipeline = pipeline; - - needsRebind = true; - - PrimitiveType = MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type; - } - - public void SetPipeline(ComputePipeline pipeline) - { - currentPipeline = pipeline; - - needsRebind = true; - - ThreadGroupSize = new(pipeline.Desc.ThreadGroupSizeX, pipeline.Desc.ThreadGroupSizeY, pipeline.Desc.ThreadGroupSizeZ); - } - - public void SetPipeline(MeshShadingPipeline pipeline) - { - currentPipeline = pipeline; - - needsRebind = true; - - AmplificationThreadGroupSize = new(pipeline.Desc.AmplificationThreadGroupSizeX, pipeline.Desc.AmplificationThreadGroupSizeY, pipeline.Desc.AmplificationThreadGroupSizeZ); - MeshThreadGroupSize = new(pipeline.Desc.MeshThreadGroupSizeX, pipeline.Desc.MeshThreadGroupSizeY, pipeline.Desc.MeshThreadGroupSizeZ); - } - - public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint index) - { - vertexBuffers[index] = buffer.Metal().GpuAddress + offsetInBytes; - - needsRebind = true; - } - - public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat format) - { - IndexBuffer = buffer.Metal().GpuAddress + offsetInBytes; - IndexSizeInBytes = buffer.Desc.SizeInBytes - offsetInBytes; - IndexStrideInBytes = (uint)(format is IndexFormat.UInt16 ? sizeof(ushort) : sizeof(uint)); - IndexType = MTLFormats.Metal(format); - } - - public void SetStencilReference(uint reference) - { - Render?.SetStencilReferenceValue(reference); - } - - public void SetBlendConstant(System.Numerics.Vector4 blendConstant) - { - Render?.SetBlendColor(blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W); - } - - public void PushResourceTable(ResourceTable resourceTable) - { - currentResourceTable = resourceTable; - - needsRebind = true; - } - - public void Bind() - { - if (!needsRebind) - { - return; - } - - switch (currentPipeline) - { - case MTLGraphicsPipeline graphicsPipeline: - { - BindRenderPipeline(graphicsPipeline.Desc.RenderStates, graphicsPipeline.RenderPipelineState, graphicsPipeline.DepthStencilState); - - if (currentResourceTable is MTLResourceTable resourceTable) - { - Render?.SetArgumentTable(resourceTable.ArgumentTable, MTLRenderStages.Fragment); - - resourceTable.Bind(ArgumentTable, ArgumentCount); - } - - foreach (KeyValuePair vertexBuffer in vertexBuffers) - { - ArgumentTable.SetAddress(vertexBuffer.Value, graphicsPipeline.VertexBufferStartIndex + vertexBuffer.Key); - } - - Render?.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex); - } - break; - - case MTLComputePipeline computePipeline: - { - Compute?.SetComputePipelineState(computePipeline.ComputePipelineState); - - if (currentResourceTable is MTLResourceTable resourceTable) - { - Compute?.SetArgumentTable(resourceTable.ArgumentTable); - } - } - break; - - case MTLMeshShadingPipeline meshShadingPipeline: - { - BindRenderPipeline(meshShadingPipeline.Desc.RenderStates, meshShadingPipeline.RenderPipelineState, meshShadingPipeline.DepthStencilState); - - if (currentResourceTable is MTLResourceTable resourceTable) - { - Render?.SetArgumentTable(resourceTable.ArgumentTable, MTLRenderStages.Object | MTLRenderStages.Mesh | MTLRenderStages.Fragment); - } - } - break; - } - - needsRebind = false; - } - - public void BeginQuery(QueryHeap queryHeap, uint index) - { - if (Render is null) - { - todoBeginQueries[index] = queryHeap; - } - else - { - Render.SetVisibilityResultMode(MTLFormats.Metal(queryHeap.Desc.Type), sizeof(ulong) * index); - } - } - - public void EndQuery(QueryHeap queryHeap, uint index) - { - Render?.SetVisibilityResultMode(MTLVisibilityResultMode.Disabled, sizeof(ulong) * index); - - if (Compute is null) - { - todoEndQueries[index] = queryHeap; - } - else - { - Compute.Copy(Buffer, sizeof(ulong) * index, queryHeap.Metal().Buffer.Buffer, sizeof(ulong) * index, sizeof(ulong)); - } - } - - public void BeginDebugEvent(string label) - { - Render?.PushDebugGroup(label); - Compute?.PushDebugGroup(label); - } - - public void EndDebugEvent() - { - Render?.PopDebugGroup(); - Compute?.PopDebugGroup(); - } - - public void InsertDebugMarker(string label) - { - Render?.InsertDebugSignpost(label); - Compute?.InsertDebugSignpost(label); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Render?.Dispose(); - Render = null; - - Compute?.Dispose(); - Compute = null; - - ArgumentTable.Dispose(); - Buffer.Dispose(); - Fence.Dispose(); - } - - private void EndRender() - { - Render?.UpdateFence(Fence, RenderStages); - Render?.EndEncoding(); - Render?.Dispose(); - Render = null; - } - - private void EndCompute() - { - Compute?.UpdateFence(Fence, ComputeStages); - Compute?.EndEncoding(); - Compute?.Dispose(); - Compute = null; - } - - private void BindRenderPipeline(RenderStates renderStates, MTLRenderPipelineState renderPipelineState, MTLDepthStencilState depthStencilState) - { - Render?.SetRenderPipelineState(renderPipelineState); - - Render?.SetCullMode(MTLFormats.Metal(renderStates.RasterizerState.CullMode)); - - Render?.SetDepthClipMode(renderStates.RasterizerState.DepthClipEnable ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); - Render?.SetDepthBias(renderStates.RasterizerState.DepthBias, renderStates.RasterizerState.SlopeScaledDepthBias, renderStates.RasterizerState.DepthBiasClamp); - - Render?.SetTriangleFillMode(MTLFormats.Metal(renderStates.RasterizerState.FillMode)); - - Render?.SetDepthStencilState(depthStencilState); - - Render?.SetFrontFacing(MTLFormats.Metal(renderStates.RasterizerState.FrontFace)); - } -} diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs deleted file mode 100644 index b7b7f853..00000000 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type, MTL4CommandQueue queue) : CommandQueue(context, type) -{ - private readonly MTLFence fence = new(context); - - protected override CommandBuffer CreateCommandBuffer() - { - return new MTLCommandBuffer(context, this); - } - - protected override void WaitIdleImpl() - { - fence.Wait(queue); - } - - protected override void SubmitImpl(CommandBuffer commandBuffer) - { - queue.Commit([commandBuffer.Metal().CommandBuffer]); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - base.Destroy(); - - fence.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLComputePipeline.cs b/sources/Zenith.NET.Metal/MTLComputePipeline.cs deleted file mode 100644 index 516d2627..00000000 --- a/sources/Zenith.NET.Metal/MTLComputePipeline.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLComputePipeline : ComputePipeline -{ - public MTLComputePipelineState ComputePipelineState; - - public MTLComputePipeline(MTLGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) - { - MTL4ComputePipelineDescriptor descriptor = new() - { - ComputeFunctionDescriptor = desc.Compute.Metal().Descriptor, - RequiredThreadsPerThreadgroup = new(desc.ThreadGroupSizeX, desc.ThreadGroupSizeY, desc.ThreadGroupSizeZ) - }; - - ComputePipelineState = context.Compiler.MakeComputePipelineState(descriptor, MTL4CompilerTaskOptions.Null, out NSError error); - error.Success(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - ComputePipelineState.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLFence.cs b/sources/Zenith.NET.Metal/MTLFence.cs deleted file mode 100644 index a05d4250..00000000 --- a/sources/Zenith.NET.Metal/MTLFence.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLFence(MTLGraphicsContext context) : GraphicsResource(context) -{ - private readonly MTLSharedEvent @event = context.Device.MakeSharedEvent(); - - private ulong currentFenceValue; - - public void Wait(MTL4CommandQueue queue) - { - currentFenceValue++; - - queue.SignalEvent(@event, currentFenceValue); - - if (@event.SignaledValue < currentFenceValue) - { - @event.Wait(currentFenceValue, ulong.MaxValue); - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - @event.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs deleted file mode 100644 index 42842038..00000000 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ /dev/null @@ -1,533 +0,0 @@ -using System.Numerics; -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal static unsafe class MTLFormats -{ - public static MTLResourceOptions Metal(BufferUsageFlags bufferUsageFlags) - { - MTLResourceOptions result = MTLResourceOptions.HazardTrackingModeUntracked; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead) || bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - result |= MTLResourceOptions.StorageModeShared; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - result |= MTLResourceOptions.CPUCacheModeWriteCombined; - } - } - else - { - result |= MTLResourceOptions.StorageModePrivate; - } - - return result; - } - - public static MTLTextureType Metal(TextureType textureType) - { - return textureType switch - { - TextureType.Texture1D => MTLTextureType.MTL1D, - TextureType.Texture1DArray => MTLTextureType.MTL1DArray, - TextureType.Texture2D => MTLTextureType.MTL2D, - TextureType.Texture2DArray => MTLTextureType.MTL2DArray, - TextureType.Texture3D => MTLTextureType.MTL3D, - TextureType.TextureCube => MTLTextureType.MTLCube, - TextureType.TextureCubeArray => MTLTextureType.MTLCubeArray, - _ => MTLTextureType.MTL1D - }; - } - - public static (MTLPixelFormat PixelFormat, MTLAttributeFormat AttributeFormat) Metal(PixelFormat pixelFormat) - { - return pixelFormat switch - { - PixelFormat.R8UNorm => (MTLPixelFormat.R8Unorm, MTLAttributeFormat.UCharNormalized), - PixelFormat.R8SNorm => (MTLPixelFormat.R8Snorm, MTLAttributeFormat.CharNormalized), - PixelFormat.R8UInt => (MTLPixelFormat.R8Uint, MTLAttributeFormat.UChar), - PixelFormat.R8SInt => (MTLPixelFormat.R8Sint, MTLAttributeFormat.Char), - - PixelFormat.R16UNorm => (MTLPixelFormat.R16Unorm, MTLAttributeFormat.UShortNormalized), - PixelFormat.R16SNorm => (MTLPixelFormat.R16Snorm, MTLAttributeFormat.ShortNormalized), - PixelFormat.R16UInt => (MTLPixelFormat.R16Uint, MTLAttributeFormat.UShort), - PixelFormat.R16SInt => (MTLPixelFormat.R16Sint, MTLAttributeFormat.Short), - PixelFormat.R16Float => (MTLPixelFormat.R16Float, MTLAttributeFormat.Half), - - PixelFormat.R32UInt => (MTLPixelFormat.R32Uint, MTLAttributeFormat.UInt), - PixelFormat.R32SInt => (MTLPixelFormat.R32Sint, MTLAttributeFormat.Int), - PixelFormat.R32Float => (MTLPixelFormat.R32Float, MTLAttributeFormat.Float), - - PixelFormat.R8G8UNorm => (MTLPixelFormat.RG8Unorm, MTLAttributeFormat.UChar2Normalized), - PixelFormat.R8G8SNorm => (MTLPixelFormat.RG8Snorm, MTLAttributeFormat.Char2Normalized), - PixelFormat.R8G8UInt => (MTLPixelFormat.RG8Uint, MTLAttributeFormat.UChar2), - PixelFormat.R8G8SInt => (MTLPixelFormat.RG8Sint, MTLAttributeFormat.Char2), - - PixelFormat.R16G16UNorm => (MTLPixelFormat.RG16Unorm, MTLAttributeFormat.UShort2Normalized), - PixelFormat.R16G16SNorm => (MTLPixelFormat.RG16Snorm, MTLAttributeFormat.Short2Normalized), - PixelFormat.R16G16UInt => (MTLPixelFormat.RG16Uint, MTLAttributeFormat.UShort2), - PixelFormat.R16G16SInt => (MTLPixelFormat.RG16Sint, MTLAttributeFormat.Short2), - PixelFormat.R16G16Float => (MTLPixelFormat.RG16Float, MTLAttributeFormat.Half2), - - PixelFormat.R32G32UInt => (MTLPixelFormat.RG32Uint, MTLAttributeFormat.UInt2), - PixelFormat.R32G32SInt => (MTLPixelFormat.RG32Sint, MTLAttributeFormat.Int2), - PixelFormat.R32G32Float => (MTLPixelFormat.RG32Float, MTLAttributeFormat.Float2), - - PixelFormat.R32G32B32UInt => (MTLPixelFormat.Invalid, MTLAttributeFormat.UInt3), - PixelFormat.R32G32B32SInt => (MTLPixelFormat.Invalid, MTLAttributeFormat.Int3), - PixelFormat.R32G32B32Float => (MTLPixelFormat.Invalid, MTLAttributeFormat.Float3), - - PixelFormat.R8G8B8A8UNorm => (MTLPixelFormat.RGBA8Unorm, MTLAttributeFormat.UChar4Normalized), - PixelFormat.R8G8B8A8SNorm => (MTLPixelFormat.RGBA8Snorm, MTLAttributeFormat.Char4Normalized), - PixelFormat.R8G8B8A8UInt => (MTLPixelFormat.RGBA8Uint, MTLAttributeFormat.UChar4), - PixelFormat.R8G8B8A8SInt => (MTLPixelFormat.RGBA8Sint, MTLAttributeFormat.Char4), - PixelFormat.R8G8B8A8SRgb => (MTLPixelFormat.RGBA8Unorm_sRGB, MTLAttributeFormat.UChar4Normalized), - - PixelFormat.R16G16B16A16UNorm => (MTLPixelFormat.RGBA16Unorm, MTLAttributeFormat.UShort4Normalized), - PixelFormat.R16G16B16A16SNorm => (MTLPixelFormat.RGBA16Snorm, MTLAttributeFormat.Short4Normalized), - PixelFormat.R16G16B16A16UInt => (MTLPixelFormat.RGBA16Uint, MTLAttributeFormat.UShort4), - PixelFormat.R16G16B16A16SInt => (MTLPixelFormat.RGBA16Sint, MTLAttributeFormat.Short4), - PixelFormat.R16G16B16A16Float => (MTLPixelFormat.RGBA16Float, MTLAttributeFormat.Half4), - - PixelFormat.R32G32B32A32UInt => (MTLPixelFormat.RGBA32Uint, MTLAttributeFormat.UInt4), - PixelFormat.R32G32B32A32SInt => (MTLPixelFormat.RGBA32Sint, MTLAttributeFormat.Int4), - PixelFormat.R32G32B32A32Float => (MTLPixelFormat.RGBA32Float, MTLAttributeFormat.Float4), - - PixelFormat.B8G8R8A8UNorm => (MTLPixelFormat.BGRA8Unorm, MTLAttributeFormat.UChar4Normalized_BGRA), - PixelFormat.B8G8R8A8SRgb => (MTLPixelFormat.BGRA8Unorm_sRGB, MTLAttributeFormat.UChar4Normalized_BGRA), - - PixelFormat.D16UNorm => (MTLPixelFormat.Depth16Unorm, MTLAttributeFormat.Invalid), - PixelFormat.D24UNormS8UInt => (MTLPixelFormat.Depth24Unorm_Stencil8, MTLAttributeFormat.Invalid), - PixelFormat.D32Float => (MTLPixelFormat.Depth32Float, MTLAttributeFormat.Invalid), - PixelFormat.D32FloatS8UInt => (MTLPixelFormat.Depth32Float_Stencil8, MTLAttributeFormat.Invalid), - - PixelFormat.BC4UNorm => (MTLPixelFormat.BC4_RUnorm, MTLAttributeFormat.Invalid), - PixelFormat.BC4SNorm => (MTLPixelFormat.BC4_RSnorm, MTLAttributeFormat.Invalid), - - PixelFormat.BC5UNorm => (MTLPixelFormat.BC5_RGUnorm, MTLAttributeFormat.Invalid), - PixelFormat.BC5SNorm => (MTLPixelFormat.BC5_RGSnorm, MTLAttributeFormat.Invalid), - - PixelFormat.BC6HUFloat => (MTLPixelFormat.BC6H_RGBUfloat, MTLAttributeFormat.Invalid), - PixelFormat.BC6HSFloat => (MTLPixelFormat.BC6H_RGBFloat, MTLAttributeFormat.Invalid), - - PixelFormat.BC7UNorm => (MTLPixelFormat.BC7_RGBAUnorm, MTLAttributeFormat.Invalid), - PixelFormat.BC7SRgb => (MTLPixelFormat.BC7_RGBAUnorm_sRGB, MTLAttributeFormat.Invalid), - - PixelFormat.ETC2UNorm => (MTLPixelFormat.ETC2_RGB8, MTLAttributeFormat.Invalid), - PixelFormat.ETC2SRgb => (MTLPixelFormat.ETC2_RGB8_sRGB, MTLAttributeFormat.Invalid), - - PixelFormat.ETC2A1UNorm => (MTLPixelFormat.ETC2_RGB8A1, MTLAttributeFormat.Invalid), - PixelFormat.ETC2A1SRgb => (MTLPixelFormat.ETC2_RGB8A1_sRGB, MTLAttributeFormat.Invalid), - - PixelFormat.ETC2A8UNorm => (MTLPixelFormat.EAC_RGBA8, MTLAttributeFormat.Invalid), - PixelFormat.ETC2A8SRgb => (MTLPixelFormat.EAC_RGBA8_sRGB, MTLAttributeFormat.Invalid), - - PixelFormat.ASTC4x4UNorm => (MTLPixelFormat.ASTC_4x4_LDR, MTLAttributeFormat.Invalid), - PixelFormat.ASTC4x4SRgb => (MTLPixelFormat.ASTC_4x4_sRGB, MTLAttributeFormat.Invalid), - PixelFormat.ASTC4x4Float => (MTLPixelFormat.ASTC_4x4_HDR, MTLAttributeFormat.Invalid), - - PixelFormat.ASTC5x5UNorm => (MTLPixelFormat.ASTC_5x5_LDR, MTLAttributeFormat.Invalid), - PixelFormat.ASTC5x5SRgb => (MTLPixelFormat.ASTC_5x5_sRGB, MTLAttributeFormat.Invalid), - PixelFormat.ASTC5x5Float => (MTLPixelFormat.ASTC_5x5_HDR, MTLAttributeFormat.Invalid), - - PixelFormat.ASTC6x6UNorm => (MTLPixelFormat.ASTC_6x6_LDR, MTLAttributeFormat.Invalid), - PixelFormat.ASTC6x6SRgb => (MTLPixelFormat.ASTC_6x6_sRGB, MTLAttributeFormat.Invalid), - PixelFormat.ASTC6x6Float => (MTLPixelFormat.ASTC_6x6_HDR, MTLAttributeFormat.Invalid), - - PixelFormat.ASTC8x8UNorm => (MTLPixelFormat.ASTC_8x8_LDR, MTLAttributeFormat.Invalid), - PixelFormat.ASTC8x8SRgb => (MTLPixelFormat.ASTC_8x8_sRGB, MTLAttributeFormat.Invalid), - PixelFormat.ASTC8x8Float => (MTLPixelFormat.ASTC_8x8_HDR, MTLAttributeFormat.Invalid), - - PixelFormat.ASTC10x10UNorm => (MTLPixelFormat.ASTC_10x10_LDR, MTLAttributeFormat.Invalid), - PixelFormat.ASTC10x10SRgb => (MTLPixelFormat.ASTC_10x10_sRGB, MTLAttributeFormat.Invalid), - PixelFormat.ASTC10x10Float => (MTLPixelFormat.ASTC_10x10_HDR, MTLAttributeFormat.Invalid), - - PixelFormat.ASTC12x12UNorm => (MTLPixelFormat.ASTC_12x12_LDR, MTLAttributeFormat.Invalid), - PixelFormat.ASTC12x12SRgb => (MTLPixelFormat.ASTC_12x12_sRGB, MTLAttributeFormat.Invalid), - PixelFormat.ASTC12x12Float => (MTLPixelFormat.ASTC_12x12_HDR, MTLAttributeFormat.Invalid), - - _ => (MTLPixelFormat.Invalid, MTLAttributeFormat.Invalid) - }; - } - - public static uint Metal(SampleCount sampleCount) - { - return sampleCount switch - { - SampleCount.Count1 => 1, - SampleCount.Count2 => 2, - SampleCount.Count4 => 4, - SampleCount.Count8 => 8, - SampleCount.Count16 => 16, - SampleCount.Count32 => 32, - _ => 1 - }; - } - - public static MTLTextureUsage Metal(TextureUsageFlags textureUsageFlags) - { - MTLTextureUsage result = MTLTextureUsage.Unknown; - - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget) || textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) - { - result |= MTLTextureUsage.RenderTarget; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.ShaderResource)) - { - result |= MTLTextureUsage.ShaderRead; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.UnorderedAccess)) - { - result |= MTLTextureUsage.ShaderWrite; - } - - return result; - } - - public static (MTLSamplerMinMagFilter MinFilter, MTLSamplerMinMagFilter MagFilter, MTLSamplerMipFilter MipFilter) Metal(Filter filter) - { - return filter switch - { - Filter.MinPointMagPointMipPoint => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest), - Filter.MinPointMagPointMipLinear => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Linear), - Filter.MinPointMagLinearMipPoint => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Nearest), - Filter.MinPointMagLinearMipLinear => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), - Filter.MinLinearMagPointMipPoint => (MTLSamplerMinMagFilter.Linear, MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest), - Filter.MinLinearMagPointMipLinear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Linear), - Filter.MinLinearMagLinearMipPoint => (MTLSamplerMinMagFilter.Linear, MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Nearest), - Filter.MinLinearMagLinearMipLinear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), - Filter.Anisotropic => (MTLSamplerMinMagFilter.Linear, MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), - _ => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.NotMipmapped) - }; - } - - public static MTLSamplerAddressMode Metal(AddressMode addressMode) - { - return addressMode switch - { - AddressMode.Wrap => MTLSamplerAddressMode.Repeat, - AddressMode.Mirror => MTLSamplerAddressMode.MirrorRepeat, - AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, - AddressMode.Border => MTLSamplerAddressMode.ClampToBorderColor, - _ => MTLSamplerAddressMode.ClampToEdge - }; - } - - public static MTLCompareFunction Metal(ComparisonFunc comparisonFunc) - { - return comparisonFunc switch - { - ComparisonFunc.Never => MTLCompareFunction.Never, - ComparisonFunc.Less => MTLCompareFunction.Less, - ComparisonFunc.Equal => MTLCompareFunction.Equal, - ComparisonFunc.LessEqual => MTLCompareFunction.LessEqual, - ComparisonFunc.Greater => MTLCompareFunction.Greater, - ComparisonFunc.NotEqual => MTLCompareFunction.NotEqual, - ComparisonFunc.GreaterEqual => MTLCompareFunction.GreaterEqual, - ComparisonFunc.Always => MTLCompareFunction.Always, - _ => MTLCompareFunction.Never - }; - } - - public static MTLSamplerBorderColor Metal(BorderColor borderColor) - { - return borderColor switch - { - BorderColor.TransparentBlack => MTLSamplerBorderColor.TransparentBlack, - BorderColor.OpaqueBlack => MTLSamplerBorderColor.OpaqueBlack, - BorderColor.OpaqueWhite => MTLSamplerBorderColor.OpaqueWhite, - _ => MTLSamplerBorderColor.TransparentBlack - }; - } - - public static (MTLPrimitiveTopologyClass TopologyClass, MTLPrimitiveType Type) Metal(PrimitiveTopology primitiveTopology) - { - return - ( - primitiveTopology switch - { - PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, - - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, - - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, - - _ => MTLPrimitiveTopologyClass.Unspecified - }, - primitiveTopology switch - { - PrimitiveTopology.PointList => MTLPrimitiveType.Point, - PrimitiveTopology.LineList => MTLPrimitiveType.Line, - PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip, - PrimitiveTopology.TriangleList => MTLPrimitiveType.Triangle, - PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip, - _ => MTLPrimitiveType.Point - } - ); - } - - public static MTLStencilOperation Metal(StencilOp stencilOp) - { - return stencilOp switch - { - StencilOp.Keep => MTLStencilOperation.Keep, - StencilOp.Zero => MTLStencilOperation.Zero, - StencilOp.Replace => MTLStencilOperation.Replace, - StencilOp.IncrementAndClamp => MTLStencilOperation.IncrementClamp, - StencilOp.DecrementAndClamp => MTLStencilOperation.DecrementClamp, - StencilOp.Invert => MTLStencilOperation.Invert, - StencilOp.IncrementAndWrap => MTLStencilOperation.IncrementWrap, - StencilOp.DecrementAndWrap => MTLStencilOperation.DecrementWrap, - _ => MTLStencilOperation.Keep - }; - } - - public static MTLBlendFactor Metal(Blend blend) - { - return blend switch - { - Blend.Zero => MTLBlendFactor.Zero, - Blend.One => MTLBlendFactor.One, - Blend.SrcAlpha => MTLBlendFactor.SourceAlpha, - Blend.InverseSrcAlpha => MTLBlendFactor.OneMinusSourceAlpha, - Blend.DestAlpha => MTLBlendFactor.DestinationAlpha, - Blend.InverseDestAlpha => MTLBlendFactor.OneMinusDestinationAlpha, - Blend.SrcColor => MTLBlendFactor.SourceColor, - Blend.InverseSrcColor => MTLBlendFactor.OneMinusSourceColor, - Blend.DestColor => MTLBlendFactor.DestinationColor, - Blend.InverseDestColor => MTLBlendFactor.OneMinusDestinationColor, - Blend.BlendFactor => MTLBlendFactor.BlendColor, - Blend.InverseBlendFactor => MTLBlendFactor.OneMinusBlendColor, - _ => MTLBlendFactor.Zero - }; - } - - public static MTLBlendOperation Metal(BlendOp blendOp) - { - return blendOp switch - { - BlendOp.Add => MTLBlendOperation.Add, - BlendOp.Subtract => MTLBlendOperation.Subtract, - BlendOp.ReverseSubtract => MTLBlendOperation.ReverseSubtract, - BlendOp.Min => MTLBlendOperation.Min, - BlendOp.Max => MTLBlendOperation.Max, - _ => MTLBlendOperation.Add - }; - } - - public static MTLColorWriteMask Metal(ColorWriteMask writeMask) - { - MTLColorWriteMask result = MTLColorWriteMask.None; - - if (writeMask.HasFlag(ColorWriteMask.Red)) - { - result |= MTLColorWriteMask.Red; - } - - if (writeMask.HasFlag(ColorWriteMask.Green)) - { - result |= MTLColorWriteMask.Green; - } - - if (writeMask.HasFlag(ColorWriteMask.Blue)) - { - result |= MTLColorWriteMask.Blue; - } - - if (writeMask.HasFlag(ColorWriteMask.Alpha)) - { - result |= MTLColorWriteMask.Alpha; - } - - return result; - } - - public static MTLVertexFormat Metal(ElementFormat elementFormat) - { - return elementFormat switch - { - ElementFormat.UByte1 => MTLVertexFormat.UChar, - ElementFormat.UByte2 => MTLVertexFormat.UChar2, - ElementFormat.UByte4 => MTLVertexFormat.UChar4, - ElementFormat.Byte1 => MTLVertexFormat.Char, - ElementFormat.Byte2 => MTLVertexFormat.Char2, - ElementFormat.Byte4 => MTLVertexFormat.Char4, - - ElementFormat.UByte1Normalized => MTLVertexFormat.UCharNormalized, - ElementFormat.UByte2Normalized => MTLVertexFormat.UChar2Normalized, - ElementFormat.UByte4Normalized => MTLVertexFormat.UChar4Normalized, - ElementFormat.Byte1Normalized => MTLVertexFormat.CharNormalized, - ElementFormat.Byte2Normalized => MTLVertexFormat.Char2Normalized, - ElementFormat.Byte4Normalized => MTLVertexFormat.Char4Normalized, - - ElementFormat.UShort1 => MTLVertexFormat.UShort, - ElementFormat.UShort2 => MTLVertexFormat.UShort2, - ElementFormat.UShort4 => MTLVertexFormat.UShort4, - ElementFormat.Short1 => MTLVertexFormat.Short, - ElementFormat.Short2 => MTLVertexFormat.Short2, - ElementFormat.Short4 => MTLVertexFormat.Short4, - - ElementFormat.UShort1Normalized => MTLVertexFormat.UShortNormalized, - ElementFormat.UShort2Normalized => MTLVertexFormat.UShort2Normalized, - ElementFormat.UShort4Normalized => MTLVertexFormat.UShort4Normalized, - ElementFormat.Short1Normalized => MTLVertexFormat.ShortNormalized, - ElementFormat.Short2Normalized => MTLVertexFormat.Short2Normalized, - ElementFormat.Short4Normalized => MTLVertexFormat.Short4Normalized, - - ElementFormat.Half1 => MTLVertexFormat.Half, - ElementFormat.Half2 => MTLVertexFormat.Half2, - ElementFormat.Half4 => MTLVertexFormat.Half4, - - ElementFormat.Float1 => MTLVertexFormat.Float, - ElementFormat.Float2 => MTLVertexFormat.Float2, - ElementFormat.Float3 => MTLVertexFormat.Float3, - ElementFormat.Float4 => MTLVertexFormat.Float4, - - ElementFormat.UInt1 => MTLVertexFormat.UInt, - ElementFormat.UInt2 => MTLVertexFormat.UInt2, - ElementFormat.UInt3 => MTLVertexFormat.UInt3, - ElementFormat.UInt4 => MTLVertexFormat.UInt4, - ElementFormat.Int1 => MTLVertexFormat.Int, - ElementFormat.Int2 => MTLVertexFormat.Int2, - ElementFormat.Int3 => MTLVertexFormat.Int3, - ElementFormat.Int4 => MTLVertexFormat.Int4, - - _ => MTLVertexFormat.Invalid - }; - } - - public static MTLCullMode Metal(CullMode cullMode) - { - return cullMode switch - { - CullMode.None => MTLCullMode.None, - CullMode.Front => MTLCullMode.Front, - CullMode.Back => MTLCullMode.Back, - _ => MTLCullMode.None - }; - } - - public static MTLTriangleFillMode Metal(FillMode fillMode) - { - return fillMode switch - { - FillMode.Solid => MTLTriangleFillMode.Fill, - FillMode.Wireframe => MTLTriangleFillMode.Lines, - _ => MTLTriangleFillMode.Fill - }; - } - - public static MTLWinding Metal(FrontFace frontFace) - { - return frontFace switch - { - FrontFace.CounterClockwise => MTLWinding.CounterClockwise, - FrontFace.Clockwise => MTLWinding.Clockwise, - _ => MTLWinding.Clockwise - }; - } - - public static MTLIndexType Metal(IndexFormat indexFormat) - { - return indexFormat switch - { - IndexFormat.UInt16 => MTLIndexType.UInt16, - IndexFormat.UInt32 => MTLIndexType.UInt32, - _ => MTLIndexType.UInt16 - }; - } - - public static MTLVisibilityResultMode Metal(QueryType queryType) - { - return queryType switch - { - QueryType.Occlusion => MTLVisibilityResultMode.Counting, - QueryType.BinaryOcclusion => MTLVisibilityResultMode.Boolean, - _ => MTLVisibilityResultMode.Disabled - }; - } - - public static MTLPackedFloat4x3 Metal(Matrix4x4 matrix4x4) - { - MTLPackedFloat4x3 result; - - float* pResult = (float*)&result; - - pResult[0] = matrix4x4.M11; - pResult[1] = matrix4x4.M21; - pResult[2] = matrix4x4.M31; - pResult[3] = matrix4x4.M41; - - pResult[4] = matrix4x4.M12; - pResult[5] = matrix4x4.M22; - pResult[6] = matrix4x4.M32; - pResult[7] = matrix4x4.M42; - - pResult[8] = matrix4x4.M13; - pResult[9] = matrix4x4.M23; - pResult[10] = matrix4x4.M33; - pResult[11] = matrix4x4.M43; - - return result; - } - - public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) - { - MTLAccelerationStructureUsage result = MTLAccelerationStructureUsage.None; - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate) || accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PerformUpdate)) - { - result |= MTLAccelerationStructureUsage.Refit; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) - { - result |= MTLAccelerationStructureUsage.PreferFastIntersection; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) - { - result |= MTLAccelerationStructureUsage.PreferFastBuild; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) - { - result |= MTLAccelerationStructureUsage.MinimizeMemory; - } - - return result; - } - - public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) - { - MTLAccelerationStructureInstanceOptions result = MTLAccelerationStructureInstanceOptions.None; - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleCullDisable)) - { - result |= MTLAccelerationStructureInstanceOptions.DisableTriangleCulling; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleFrontCounterClockwise)) - { - result |= MTLAccelerationStructureInstanceOptions.TriangleFrontFacingWindingCounterClockwise; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) - { - result |= MTLAccelerationStructureInstanceOptions.Opaque; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNoOpaque)) - { - result |= MTLAccelerationStructureInstanceOptions.NonOpaque; - } - - return result; - } -} diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 6b4af04d..7f9e8ca1 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -2,142 +2,99 @@ namespace Zenith.NET.Metal; -internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(Backend.Metal, useValidationLayer) +internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Metal, useValidationLayer) { - public MTLDevice Device = MTLDevice.Null; + public MTLDevice Device { get; } = MTLDevice.CreateSystemDefaultDevice(); - public MTL4Compiler Compiler = MTL4Compiler.Null; - - public MTLResidencySet ResidencySet = MTLResidencySet.Null; - - public MTL4CommandQueue GraphicsQueue = MTL4CommandQueue.Null; - - public MTL4CommandQueue ComputeQueue = MTL4CommandQueue.Null; - - public MTL4CommandQueue CopyQueue = MTL4CommandQueue.Null; - - public void AddAllocation(MTLAllocation allocation) - { - ResidencySet.AddAllocation(allocation); - ResidencySet.Commit(); - } - - public void RemoveAllocation(MTLAllocation allocation) + public override nint GetNativeObject(NativeObjectType type) { - ResidencySet.RemoveAllocation(allocation); - ResidencySet.Commit(); + return 0; } protected override void Initialize(bool useValidationLayer, out Capabilities capabilities, - out CommandQueue graphics, - out CommandQueue compute, - out CommandQueue copy, + out CommandQueue graphicsQueue, + out CommandQueue computeQueue, + out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - Device = MTLDevice.CreateSystemDefaultDevice(); - - if (!Device.SupportsFamily(MTLGPUFamily.Metal4)) - { - throw new NotSupportedException("Metal 4 is not supported on system default device."); - } - - Compiler = Device.MakeCompiler(new(), out NSError error); - error.Success(); - - ResidencySet = Device.MakeResidencySet(new(), out error); - error.Success(); - - GraphicsQueue = Device.MakeMTL4CommandQueue(); - ComputeQueue = Device.MakeMTL4CommandQueue(); - CopyQueue = Device.MakeMTL4CommandQueue(); + throw new NotImplementedException(); + } - GraphicsQueue.AddResidencySet(ResidencySet); - ComputeQueue.AddResidencySet(ResidencySet); - CopyQueue.AddResidencySet(ResidencySet); + protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) + { + throw new NotImplementedException(); + } - capabilities = new MTLCapabilities(this); - graphics = new MTLCommandQueue(this, CommandQueueType.Graphics, GraphicsQueue); - compute = new MTLCommandQueue(this, CommandQueueType.Compute, ComputeQueue); - copy = new MTLCommandQueue(this, CommandQueueType.Copy, CopyQueue); - validationLayer = null; + protected override Heap CreateHeapImpl(HeapDesc desc) + { + throw new NotImplementedException(); } - protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - return new MTLSwapChain(this, desc); + throw new NotImplementedException(); } - protected override Shader CreateShaderImpl(ShaderDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { - return new MTLShader(this, desc); + throw new NotImplementedException(); } protected override Buffer CreateBufferImpl(BufferDesc desc) { - return new MTLBuffer(this, desc); + throw new NotImplementedException(); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) { - return new MTLBufferView(this, desc); + throw new NotImplementedException(); } protected override Texture CreateTextureImpl(TextureDesc desc) { - return new MTLTexture(this, desc); + throw new NotImplementedException(); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { - return new MTLTextureView(this, desc); + throw new NotImplementedException(); } protected override Sampler CreateSamplerImpl(SamplerDesc desc) { - return new MTLSampler(this, desc); + throw new NotImplementedException(); } - protected override ResourceTable CreateResourceTableImpl(ResourceTableDesc desc) + protected override Shader CreateShaderImpl(ShaderDesc desc) { - return new MTLResourceTable(this, desc); + throw new NotImplementedException(); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) { - return new MTLGraphicsPipeline(this, desc); + throw new NotImplementedException(); } protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) { - return new MTLComputePipeline(this, desc); + throw new NotImplementedException(); } protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) { - return new MTLMeshShadingPipeline(this, desc); + throw new NotImplementedException(); } protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { - return new MTLQueryHeap(this, desc); + throw new NotImplementedException(); } protected override void Destroy() { base.Destroy(); - CopyQueue.RemoveResidencySet(ResidencySet); - ComputeQueue.RemoveResidencySet(ResidencySet); - GraphicsQueue.RemoveResidencySet(ResidencySet); - - CopyQueue.Dispose(); - ComputeQueue.Dispose(); - GraphicsQueue.Dispose(); - - ResidencySet.Dispose(); - Compiler.Dispose(); - Device.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs deleted file mode 100644 index d2786ef3..00000000 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLGraphicsPipeline : GraphicsPipeline -{ - public MTLRenderPipelineState RenderPipelineState; - - public MTLDepthStencilState DepthStencilState; - - public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) - { - VertexBufferStartIndex = (uint)desc.ResourceBindings.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); - - MTL4RenderPipelineDescriptor descriptor = new() - { - VertexFunctionDescriptor = desc.Vertex.Metal().Descriptor, - FragmentFunctionDescriptor = desc.Pixel.Metal().Descriptor, - InputPrimitiveTopology = MTLFormats.Metal(desc.PrimitiveTopology).TopologyClass - }; - - // RenderStates - Output - { - descriptor.AlphaToCoverageState = desc.RenderStates.BlendState.AlphaToCoverageEnable ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; - - BlendStateRenderTarget[] blendStateRenderTargets = - [ - desc.RenderStates.BlendState.RenderTarget0, - desc.RenderStates.BlendState.RenderTarget1, - desc.RenderStates.BlendState.RenderTarget2, - desc.RenderStates.BlendState.RenderTarget3, - desc.RenderStates.BlendState.RenderTarget4, - desc.RenderStates.BlendState.RenderTarget5, - desc.RenderStates.BlendState.RenderTarget6, - desc.RenderStates.BlendState.RenderTarget7 - ]; - - for (int i = 0; i < blendStateRenderTargets.Length; i++) - { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; - - descriptor.ColorAttachments[(uint)i] = new() - { - PixelFormat = i < desc.Output.ColorAttachments.Length ? MTLFormats.Metal(desc.Output.ColorAttachments[i]).PixelFormat : MTLPixelFormat.Invalid, - BlendingState = target.BlendEnable ? MTL4BlendState.Enabled : MTL4BlendState.Disabled, - SourceRGBBlendFactor = MTLFormats.Metal(target.SrcBlend), - DestinationRGBBlendFactor = MTLFormats.Metal(target.DestBlend), - RgbBlendOperation = MTLFormats.Metal(target.BlendOp), - SourceAlphaBlendFactor = MTLFormats.Metal(target.SrcBlendAlpha), - DestinationAlphaBlendFactor = MTLFormats.Metal(target.DestBlendAlpha), - AlphaBlendOperation = MTLFormats.Metal(target.BlendOpAlpha), - WriteMask = MTLFormats.Metal(target.Flags) - }; - } - - descriptor.RasterSampleCount = MTLFormats.Metal(desc.Output.SampleCount); - - DepthStencilState = context.Device.MakeDepthStencilState(new() - { - DepthCompareFunction = desc.RenderStates.DepthStencilState.DepthEnable ? MTLFormats.Metal(desc.RenderStates.DepthStencilState.DepthFunc) : MTLCompareFunction.Always, - IsDepthWriteEnabled = desc.RenderStates.DepthStencilState.DepthWriteEnable, - FrontFaceStencil = desc.RenderStates.DepthStencilState.StencilEnable ? new() - { - StencilCompareFunction = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilFunc), - StencilFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilFailOp), - DepthFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilDepthFailOp), - DepthStencilPassOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilPassOp), - ReadMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask - } : MTLStencilDescriptor.Null, - BackFaceStencil = desc.RenderStates.DepthStencilState.StencilEnable ? new() - { - StencilCompareFunction = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilFunc), - StencilFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilFailOp), - DepthFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilDepthFailOp), - DepthStencilPassOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilPassOp), - ReadMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask - } : MTLStencilDescriptor.Null - }); - } - - // InputLayouts - { - uint binding = VertexBufferStartIndex; - uint attribute = 0; - for (int i = 0; i < desc.InputLayouts.Length; i++) - { - InputLayout inputLayout = desc.InputLayouts[i]; - - descriptor.VertexDescriptor.Layouts[binding].Stride = inputLayout.StrideInBytes; - - foreach (InputElement element in inputLayout.Elements) - { - descriptor.VertexDescriptor.Attributes[attribute++] = new() - { - Format = MTLFormats.Metal(element.Format), - Offset = element.OffsetInBytes, - BufferIndex = binding - }; - } - - binding++; - } - } - - RenderPipelineState = context.Compiler.MakeRenderPipelineState(descriptor, MTL4CompilerTaskOptions.Null, out NSError error); - error.Success(); - } - - public uint VertexBufferStartIndex { get; } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DepthStencilState.Dispose(); - RenderPipelineState.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs deleted file mode 100644 index fc3caa19..00000000 --- a/sources/Zenith.NET.Metal/MTLHeap.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLHeap : GraphicsResource -{ - public MtlHeap Heap; - - public MTLHeap(MTLGraphicsContext context, BufferDesc desc, out MtlBuffer buffer) : base(context) - { - context.AddAllocation(Heap = context.Device.MakeHeap(new() - { - Size = context.Device.HeapBufferSizeAndAlign(desc.SizeInBytes, MTLFormats.Metal(desc.Flags)).Size, - ResourceOptions = MTLFormats.Metal(desc.Flags), - Type = MTLHeapType.Automatic - })); - - buffer = Heap.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Flags)); - } - - public MTLHeap(MTLGraphicsContext context, TextureDesc desc, out MtlTexture texture) : base(context) - { - MTLTextureDescriptor descriptor = new() - { - TextureType = MTLFormats.Metal(desc.Type), - PixelFormat = MTLFormats.Metal(desc.Format).PixelFormat, - Width = desc.Width, - Height = desc.Height, - Depth = desc.Depth, - MipmapLevelCount = desc.MipLevels, - SampleCount = MTLFormats.Metal(desc.SampleCount), - ArrayLength = desc.ArrayLayers, - ResourceOptions = MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, - Usage = MTLFormats.Metal(desc.Flags), - AllowGPUOptimizedContents = true - }; - - context.AddAllocation(Heap = context.Device.MakeHeap(new() - { - Size = context.Device.HeapTextureSizeAndAlign(descriptor).Size, - ResourceOptions = descriptor.ResourceOptions, - Type = MTLHeapType.Automatic - })); - - texture = Heap.MakeTexture(descriptor); - } - - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Context.RemoveAllocation(Heap); - - Heap.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs deleted file mode 100644 index cc84af40..00000000 --- a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLMeshShadingPipeline : MeshShadingPipeline -{ - public MTLRenderPipelineState RenderPipelineState; - - public MTLDepthStencilState DepthStencilState; - - public MTLMeshShadingPipeline(MTLGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) - { - MTL4MeshRenderPipelineDescriptor descriptor = new() - { - MeshFunctionDescriptor = desc.Mesh.Metal().Descriptor, - FragmentFunctionDescriptor = desc.Pixel.Metal().Descriptor, - RequiredThreadsPerMeshThreadgroup = new(desc.MeshThreadGroupSizeX, desc.MeshThreadGroupSizeY, desc.MeshThreadGroupSizeZ) - }; - - if (desc.Amplification is not null) - { - descriptor.ObjectFunctionDescriptor = desc.Amplification.Metal().Descriptor; - descriptor.RequiredThreadsPerObjectThreadgroup = new(desc.AmplificationThreadGroupSizeX, desc.AmplificationThreadGroupSizeY, desc.AmplificationThreadGroupSizeZ); - } - - // RenderStates - Output - { - descriptor.AlphaToCoverageState = desc.RenderStates.BlendState.AlphaToCoverageEnable ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; - - BlendStateRenderTarget[] blendStateRenderTargets = - [ - desc.RenderStates.BlendState.RenderTarget0, - desc.RenderStates.BlendState.RenderTarget1, - desc.RenderStates.BlendState.RenderTarget2, - desc.RenderStates.BlendState.RenderTarget3, - desc.RenderStates.BlendState.RenderTarget4, - desc.RenderStates.BlendState.RenderTarget5, - desc.RenderStates.BlendState.RenderTarget6, - desc.RenderStates.BlendState.RenderTarget7 - ]; - - for (int i = 0; i < blendStateRenderTargets.Length; i++) - { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; - - descriptor.ColorAttachments[(uint)i] = new() - { - PixelFormat = i < desc.Output.ColorAttachments.Length ? MTLFormats.Metal(desc.Output.ColorAttachments[i]).PixelFormat : MTLPixelFormat.Invalid, - BlendingState = target.BlendEnable ? MTL4BlendState.Enabled : MTL4BlendState.Disabled, - SourceRGBBlendFactor = MTLFormats.Metal(target.SrcBlend), - DestinationRGBBlendFactor = MTLFormats.Metal(target.DestBlend), - RgbBlendOperation = MTLFormats.Metal(target.BlendOp), - SourceAlphaBlendFactor = MTLFormats.Metal(target.SrcBlendAlpha), - DestinationAlphaBlendFactor = MTLFormats.Metal(target.DestBlendAlpha), - AlphaBlendOperation = MTLFormats.Metal(target.BlendOpAlpha), - WriteMask = MTLFormats.Metal(target.Flags) - }; - } - - descriptor.RasterSampleCount = MTLFormats.Metal(desc.Output.SampleCount); - - DepthStencilState = context.Device.MakeDepthStencilState(new() - { - DepthCompareFunction = desc.RenderStates.DepthStencilState.DepthEnable ? MTLFormats.Metal(desc.RenderStates.DepthStencilState.DepthFunc) : MTLCompareFunction.Always, - IsDepthWriteEnabled = desc.RenderStates.DepthStencilState.DepthWriteEnable, - FrontFaceStencil = desc.RenderStates.DepthStencilState.StencilEnable ? new() - { - StencilCompareFunction = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilFunc), - StencilFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilFailOp), - DepthFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilDepthFailOp), - DepthStencilPassOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.FrontFace.StencilPassOp), - ReadMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask - } : MTLStencilDescriptor.Null, - BackFaceStencil = desc.RenderStates.DepthStencilState.StencilEnable ? new() - { - StencilCompareFunction = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilFunc), - StencilFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilFailOp), - DepthFailureOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilDepthFailOp), - DepthStencilPassOperation = MTLFormats.Metal(desc.RenderStates.DepthStencilState.BackFace.StencilPassOp), - ReadMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask - } : MTLStencilDescriptor.Null - }); - } - - RenderPipelineState = context.Compiler.MakeRenderPipelineState(descriptor, MTL4CompilerTaskOptions.Null, out NSError error); - error.Success(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DepthStencilState.Dispose(); - RenderPipelineState.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLQueryHeap.cs b/sources/Zenith.NET.Metal/MTLQueryHeap.cs deleted file mode 100644 index 71c80ba6..00000000 --- a/sources/Zenith.NET.Metal/MTLQueryHeap.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal unsafe class MTLQueryHeap : QueryHeap -{ - public MTL4CounterHeap CounterHeap; - - public MTLQueryHeap(MTLGraphicsContext context, QueryHeapDesc desc) : base(context, desc) - { - MTL4CounterHeapDescriptor descriptor = new() - { - Type = MTL4CounterHeapType.Timestamp, - Count = desc.Count - }; - - CounterHeap = context.Device.MakeCounterHeap(descriptor, out NSError error); - error.Success(); - - Buffer = new(context, new() - { - SizeInBytes = sizeof(ulong) * desc.Count, - StrideInBytes = sizeof(ulong), - Flags = BufferUsageFlags.MapRead - }); - } - - public MTLBuffer Buffer { get; } - - protected override void GetResultsImpl(Span results, uint startIndex) - { - MappedMemory mappedMemory = Buffer.Map(); - - unsafe - { - new ReadOnlySpan((void*)(mappedMemory.Pointer + (nint)(sizeof(ulong) * startIndex)), results.Length).CopyTo(results); - } - - Buffer.Unmap(); - } - - protected override void SetResourceName(string name) - { - CounterHeap.Label = name; - } - - protected override void Destroy() - { - Buffer.Dispose(); - - CounterHeap.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLResourceTable.cs b/sources/Zenith.NET.Metal/MTLResourceTable.cs deleted file mode 100644 index 18790fdf..00000000 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ /dev/null @@ -1,168 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLResourceTable : ResourceTable -{ - private readonly Binding?[] bufferBindings; - private readonly Binding?[] textureBindings; - private readonly Binding?[] samplerBindings; - - public MTL4ArgumentTable ArgumentTable; - - public MTLResourceTable(MTLGraphicsContext context, ResourceTableDesc desc) : base(context, desc) - { - bufferBindings = new Binding?[desc.Bindings.Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count)]; - textureBindings = new Binding?[desc.Bindings.Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count)]; - samplerBindings = new Binding?[desc.Bindings.Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count)]; - - MTL4ArgumentTableDescriptor descriptor = new() - { - MaxBufferBindCount = (uint)bufferBindings.Length, - MaxTextureBindCount = (uint)textureBindings.Length, - MaxSamplerStateBindCount = (uint)samplerBindings.Length - }; - - ArgumentTable = context.Device.MakeArgumentTable(descriptor, out NSError error); - error.Success(); - } - - public void Bind(MTL4ArgumentTable argumentTable, uint argumentCount) - { - foreach (Binding? binding in bufferBindings) - { - binding?.Buffer(argumentTable, argumentCount); - } - - foreach (Binding? binding in textureBindings) - { - binding?.Texture(argumentTable, argumentCount); - } - - foreach (Binding? binding in samplerBindings) - { - binding?.Sampler(argumentTable, argumentCount); - } - } - - protected override void WriteImpl(uint binding, IBindableResource[] resources) - { - switch (Desc.Bindings[binding].Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - case ResourceType.AccelerationStructure: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.ConstantBuffer or ResourceType.StructuredBuffer or ResourceType.StructuredBufferReadWrite or ResourceType.AccelerationStructure).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Buffer buffer) - { - bufferBindings[index] = new(buffer.Metal().GpuAddress, default, index); - } - else if (resource is BufferView bufferView) - { - bufferBindings[index] = new(bufferView.Metal().GpuAddress, default, index); - } - else if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - bufferBindings[index] = new(default, topLevelAccelerationStructure.Metal().AccelerationStructure.GpuResourceID, index); - } - - index++; - } - } - break; - - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.Texture or ResourceType.TextureReadWrite).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Texture texture) - { - textureBindings[index] = new(default, texture.Metal().Texture.GpuResourceID, index); - } - else if (resource is TextureView textureView) - { - textureBindings[index] = new(default, textureView.Metal().Texture.GpuResourceID, index); - } - - index++; - } - } - break; - - case ResourceType.Sampler: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Where(static item => item.Type is ResourceType.Sampler).Sum(static item => item.Count); - - foreach (IBindableResource resource in resources) - { - if (resource is Sampler sampler) - { - samplerBindings[index] = new(default, sampler.Metal().SamplerState.GpuResourceID, index); - } - - index++; - } - } - break; - } - - Bind(ArgumentTable, uint.MaxValue); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - ArgumentTable.Dispose(); - } - - private readonly struct Binding(nuint gpuAddress, MTLResourceID resourceID, uint index) - { - public void Buffer(MTL4ArgumentTable argumentTable, uint argumentCount) - { - if (index >= argumentCount) - { - return; - } - - if (gpuAddress != default) - { - argumentTable.SetAddress(gpuAddress, index); - } - else - { - argumentTable.SetResource(resourceID, index); - } - } - - public void Texture(MTL4ArgumentTable argumentTable, uint argumentCount) - { - if (index >= argumentCount) - { - return; - } - - argumentTable.SetTexture(resourceID, index); - } - - public void Sampler(MTL4ArgumentTable argumentTable, uint argumentCount) - { - if (index >= argumentCount) - { - return; - } - - argumentTable.SetSamplerState(resourceID, index); - } - } -} diff --git a/sources/Zenith.NET.Metal/MTLSampler.cs b/sources/Zenith.NET.Metal/MTLSampler.cs deleted file mode 100644 index 2acdba25..00000000 --- a/sources/Zenith.NET.Metal/MTLSampler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLSampler : Sampler -{ - public MTLSamplerState SamplerState; - - public MTLSampler(MTLGraphicsContext context, SamplerDesc desc) : base(context, desc) - { - MTLSamplerDescriptor descriptor = new() - { - MinFilter = MTLFormats.Metal(desc.Filter).MinFilter, - MagFilter = MTLFormats.Metal(desc.Filter).MagFilter, - MipFilter = MTLFormats.Metal(desc.Filter).MipFilter, - MaxAnisotropy = desc.Filter is Filter.Anisotropic ? desc.MaxAnisotropy : 1, - SAddressMode = MTLFormats.Metal(desc.U), - TAddressMode = MTLFormats.Metal(desc.V), - RAddressMode = MTLFormats.Metal(desc.W), - BorderColor = MTLFormats.Metal(desc.BorderColor), - LodMinClamp = desc.MinLod, - LodMaxClamp = desc.MaxLod, - LodBias = desc.LodBias, - CompareFunction = MTLFormats.Metal(desc.ComparisonFunc), - SupportArgumentBuffers = true - }; - - SamplerState = context.Device.MakeSamplerState(descriptor); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - SamplerState.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLShader.cs b/sources/Zenith.NET.Metal/MTLShader.cs deleted file mode 100644 index 17729069..00000000 --- a/sources/Zenith.NET.Metal/MTLShader.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLShader : Shader -{ - public MTLLibrary Library; - - public MTL4LibraryFunctionDescriptor Descriptor; - - public MTLShader(MTLGraphicsContext context, ShaderDesc desc) : base(context, desc) - { - Library = context.Device.MakeLibrary(DispatchData.Create(desc.ShaderBytes), out NSError error); - error.Success(); - - Descriptor = new() - { - Name = desc.EntryPoint, - Library = Library - }; - } - - protected override void SetResourceName(string name) - { - Library.Label = name; - } - - protected override void Destroy() - { - Descriptor.Dispose(); - Library.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs deleted file mode 100644 index 2a8846e7..00000000 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLSwapChain : SwapChain -{ - private readonly MTLSwapChainTargets swapChainTargets; - - public CAMetalLayer Layer = CAMetalLayer.Null; - - public CAMetalDrawable Drawable = CAMetalDrawable.Null; - - public MTLSwapChain(MTLGraphicsContext context, SwapChainDesc desc) : base(context, desc) - { - swapChainTargets = new(context, this); - - CreateSwapChain(); - } - - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - - public override uint Width => CurrentColorTarget.Desc.Width; - - public override uint Height => CurrentColorTarget.Desc.Height; - - public override Texture CurrentColorTarget - { - get - { - swapChainTargets.EnsureTargets(Desc.Surface.Width, Desc.Surface.Height, Drawable); - - return swapChainTargets.ColorTarget; - } - } - - public override Texture? CurrentDepthStencilTarget - { - get - { - swapChainTargets.EnsureTargets(Desc.Surface.Width, Desc.Surface.Height, Drawable); - - return swapChainTargets.DepthStencilTarget; - } - } - - public override Output Output => new() - { - ColorAttachments = [Desc.ColorTargetFormat], - DepthStencilAttachment = Desc.DepthStencilTargetFormat, - SampleCount = SampleCount.Count1 - }; - - public override void Present() - { - Drawable.Present(); - Drawable.Dispose(); - - Drawable = NSAutorelease.Own(Layer.NextDrawable); - } - - protected override void ResizeImpl() - { - Drawable.Dispose(); - swapChainTargets.DestroyTargets(); - - Layer.DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height); - - Drawable = NSAutorelease.Own(Layer.NextDrawable); - } - - protected override void RefreshImpl() - { - CreateSwapChain(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroySwapChain(); - - swapChainTargets.Dispose(); - } - - private void CreateSwapChain() - { - DestroySwapChain(); - - Layer = new(Desc.Surface.Handles[0], NativeObjectOwnership.Borrowed) - { - Device = Context.Device, - PixelFormat = MTLFormats.Metal(Desc.ColorTargetFormat).PixelFormat, - FramebufferOnly = false, - DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height) - }; - - Drawable = NSAutorelease.Own(Layer.NextDrawable); - } - - private void DestroySwapChain() - { - Drawable.Dispose(); - Layer.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLSwapChainTargets.cs b/sources/Zenith.NET.Metal/MTLSwapChainTargets.cs deleted file mode 100644 index ac3e5f21..00000000 --- a/sources/Zenith.NET.Metal/MTLSwapChainTargets.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLSwapChainTargets(MTLGraphicsContext context, MTLSwapChain swapChain) : GraphicsResource(context) -{ - private MTLTexture? colorTarget; - private MTLTexture? depthStencilTarget; - - public MTLTexture ColorTarget => colorTarget!; - - public MTLTexture? DepthStencilTarget => depthStencilTarget; - - public void EnsureTargets(uint width, uint height, CAMetalDrawable drawable) - { - colorTarget ??= new(context, new() - { - Type = TextureType.Texture2D, - Format = swapChain.Desc.ColorTargetFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.RenderTarget - }, drawable.Texture); - - depthStencilTarget ??= swapChain.Desc.DepthStencilTargetFormat is not null ? new(context, new() - { - Type = TextureType.Texture2D, - Format = swapChain.Desc.DepthStencilTargetFormat.Value, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil - }) : null; - - if (colorTarget.Desc.Width != width || colorTarget.Desc.Height != height) - { - DestroyTargets(); - - EnsureTargets(width, height, drawable); - - return; - } - - colorTarget.Texture = drawable.Texture; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroyTargets(); - } - - public void DestroyTargets() - { - depthStencilTarget?.Dispose(); - depthStencilTarget = null; - - colorTarget?.Dispose(); - colorTarget = null; - } -} diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs deleted file mode 100644 index 34609036..00000000 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Zenith.NET.Metal; - -internal class MTLTexture : Texture -{ - public MtlTexture Texture; - - public MTLTexture(MTLGraphicsContext context, TextureDesc desc) : base(context, desc) - { - Heap = new(context, desc, out Texture); - } - - public MTLTexture(MTLGraphicsContext context, TextureDesc desc, MtlTexture texture) : base(context, desc) - { - Texture = texture; - } - - public MTLHeap? Heap { get; } - - protected override void SetResourceName(string name) - { - Texture.Label = name; - } - - protected override void Destroy() - { - if (Heap is not null) - { - Texture.Dispose(); - - Heap.Dispose(); - } - } -} diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs deleted file mode 100644 index 94d0572f..00000000 --- a/sources/Zenith.NET.Metal/MTLTextureView.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLTextureView : TextureView -{ - public MtlTexture Texture; - - public MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : base(context, desc) - { - TextureSubresourceRange range = desc.Range; - - MTLTextureViewDescriptor descriptor = new() - { - PixelFormat = MTLFormats.Metal(desc.Format).PixelFormat, - TextureType = MTLFormats.Metal(desc.Type), - LevelRange = new(range.BaseMipLevel, range.LevelCount), - SliceRange = new(range.BaseArrayLayer, range.LayerCount) - }; - - Texture = desc.Texture.Metal().Texture.MakeTextureView(descriptor); - } - - protected override void SetResourceName(string name) - { - Texture.Label = name; - } - - protected override void Destroy() - { - Texture.Dispose(); - } -} diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs deleted file mode 100644 index 04d703dd..00000000 --- a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal unsafe class MTLTopLevelAccelerationStructure : TopLevelAccelerationStructure -{ - public MTLAccelerationStructure AccelerationStructure; - - public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, TopLevelAccelerationStructureDesc desc, MTLCommandBuffer commandBuffer) : base(context, desc) - { - uint instanceCount = (uint)desc.Instances.Length; - - InstanceBuffer = new(context, new() - { - SizeInBytes = (uint)(sizeof(MTLIndirectAccelerationStructureInstanceDescriptor) * instanceCount), - StrideInBytes = (uint)sizeof(MTLIndirectAccelerationStructureInstanceDescriptor), - Flags = BufferUsageFlags.MapWrite - }); - - FillInstanceBuffer(desc); - - MTL4InstanceAccelerationStructureDescriptor descriptor = new() - { - InstanceDescriptorBuffer = new(InstanceBuffer.Metal().GpuAddress, InstanceBuffer.Desc.SizeInBytes), - InstanceDescriptorStride = (uint)sizeof(MTLIndirectAccelerationStructureInstanceDescriptor), - InstanceCount = instanceCount, - InstanceDescriptorType = MTLAccelerationStructureInstanceDescriptorType.Indirect, - InstanceTransformationMatrixLayout = MTLMatrixLayout.RowMajor, - Usage = MTLFormats.Metal(desc.Flags) - }; - - MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); - - AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize); - context.AddAllocation(AccelerationStructure); - - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)sizes.BuildScratchBufferSize, - StrideInBytes = (uint)sizes.BuildScratchBufferSize, - Flags = BufferUsageFlags.ShaderResource - }); - - commandBuffer.CommandEncoder.Compute?.Build(AccelerationStructure, descriptor, new(ScratchBuffer.Buffer.GpuAddress, ScratchBuffer.Desc.SizeInBytes)); - } - - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - - public MTLBuffer InstanceBuffer { get; } - - public MTLBuffer ScratchBuffer { get; } - - public void Update(MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) - { - FillInstanceBuffer(newDesc); - - MTL4InstanceAccelerationStructureDescriptor descriptor = new() - { - InstanceDescriptorBuffer = new(InstanceBuffer.Metal().GpuAddress, InstanceBuffer.Desc.SizeInBytes), - InstanceDescriptorStride = (uint)sizeof(MTLIndirectAccelerationStructureInstanceDescriptor), - InstanceCount = (uint)newDesc.Instances.Length, - InstanceDescriptorType = MTLAccelerationStructureInstanceDescriptorType.Indirect, - InstanceTransformationMatrixLayout = MTLMatrixLayout.RowMajor, - Usage = MTLFormats.Metal(newDesc.Flags) - }; - - commandBuffer.CommandEncoder.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(ScratchBuffer.Buffer.GpuAddress, ScratchBuffer.Desc.SizeInBytes)); - } - - protected override void SetResourceName(string name) - { - AccelerationStructure.Label = name; - } - - protected override void Destroy() - { - Context.RemoveAllocation(AccelerationStructure); - - AccelerationStructure.Dispose(); - - ScratchBuffer.Dispose(); - InstanceBuffer.Dispose(); - } - - private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc) - { - uint instanceCount = (uint)desc.Instances.Length; - - MappedMemory mappedMemory = InstanceBuffer.Map(); - - MTLIndirectAccelerationStructureInstanceDescriptor* instances = (MTLIndirectAccelerationStructureInstanceDescriptor*)mappedMemory.Pointer; - for (uint i = 0; i < instanceCount; i++) - { - RayTracingInstance instance = desc.Instances[i]; - - instances[i] = new() - { - TransformationMatrix = MTLFormats.Metal(instance.Transform), - Options = MTLFormats.Metal(instance.Flags), - Mask = instance.Mask, - UserID = instance.ID, - AccelerationStructureID = instance.AccelerationStructure.Metal().AccelerationStructure.GpuResourceID - }; - } - - InstanceBuffer.Unmap(); - } -} diff --git a/sources/Zenith.NET.Metal/NSAutorelease.cs b/sources/Zenith.NET.Metal/NSAutorelease.cs deleted file mode 100644 index 31cab3d8..00000000 --- a/sources/Zenith.NET.Metal/NSAutorelease.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal static class NSAutorelease -{ - public static T Own(Func func) where T : NSObject, INativeObject - { - using NSAutoreleasePool _ = new(); - - return func().Retain(); - } - - public static T Own(Func func, T1 arg1) where T : NSObject, INativeObject - { - using NSAutoreleasePool _ = new(); - - return func(arg1).Retain(); - } -} diff --git a/sources/Zenith.NET.Metal/Usings.cs b/sources/Zenith.NET.Metal/Usings.cs deleted file mode 100644 index 6bc1dc32..00000000 --- a/sources/Zenith.NET.Metal/Usings.cs +++ /dev/null @@ -1,4 +0,0 @@ -global using MtlBuffer = Metal.NET.MTLBuffer; -global using MtlFence = Metal.NET.MTLFence; -global using MtlHeap = Metal.NET.MTLHeap; -global using MtlTexture = Metal.NET.MTLTexture; diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 65ea79f9..3bd90b22 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -41,155 +41,4 @@ internal void AddNext(out TNext next) current->PNext = (BaseInStructure*)Unsafe.AsPointer(ref next); } } - - extension(ResourceBinding[] resourceBindings) - { - internal DescriptorSetLayout DescriptorSetLayout(VKGraphicsContext context) - { - using ZenithMarshal.Scope scope = new(); - - DescriptorSetLayoutCreateInfo createInfo = new() - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)resourceBindings.Length, - PBindings = (DescriptorSetLayoutBinding*)ZenithMarshal.AllocateAndFill(scope, [.. resourceBindings.Select(Vulkan)]), - Flags = DescriptorSetLayoutCreateFlags.PushDescriptorBit - }; - - context.Vk.CreateDescriptorSetLayout(context.Device, &createInfo, null, out DescriptorSetLayout descriptorSetLayout).Success(); - - return descriptorSetLayout; - - static DescriptorSetLayoutBinding Vulkan(ResourceBinding resourceBinding, int index) - { - return new DescriptorSetLayoutBinding - { - Binding = (uint)index, - DescriptorType = VKFormats.Vulkan(resourceBinding.Type), - DescriptorCount = resourceBinding.Count, - StageFlags = VkShaderStageFlags.All - }; - } - } - } - - extension(CommandBuffer commandBuffer) - { - internal VKCommandBuffer Vulkan() - { - return (VKCommandBuffer)commandBuffer; - } - } - - extension(SwapChain swapChain) - { - internal VKSwapChain Vulkan() - { - return (VKSwapChain)swapChain; - } - } - - extension(Shader shader) - { - internal VKShader Vulkan() - { - return (VKShader)shader; - } - } - - extension(Buffer buffer) - { - internal VKBuffer Vulkan() - { - return (VKBuffer)buffer; - } - } - - extension(BufferView bufferView) - { - internal VKBufferView Vulkan() - { - return (VKBufferView)bufferView; - } - } - - extension(Texture texture) - { - internal VKTexture Vulkan() - { - return (VKTexture)texture; - } - } - - extension(TextureView textureView) - { - internal VKTextureView Vulkan() - { - return (VKTextureView)textureView; - } - } - - extension(Sampler sampler) - { - internal VKSampler Vulkan() - { - return (VKSampler)sampler; - } - } - - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) - { - internal VKBottomLevelAccelerationStructure Vulkan() - { - return (VKBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; - } - } - - extension(TopLevelAccelerationStructure topLevelAccelerationStructure) - { - internal VKTopLevelAccelerationStructure Vulkan() - { - return (VKTopLevelAccelerationStructure)topLevelAccelerationStructure; - } - } - - extension(ResourceTable resourceTable) - { - internal VKResourceTable Vulkan() - { - return (VKResourceTable)resourceTable; - } - } - - extension(GraphicsPipeline graphicsPipeline) - { - internal VKGraphicsPipeline Vulkan() - { - return (VKGraphicsPipeline)graphicsPipeline; - } - } - - extension(ComputePipeline computePipeline) - { - internal VKComputePipeline Vulkan() - { - return (VKComputePipeline)computePipeline; - } - } - - extension(MeshShadingPipeline meshShadingPipeline) - { - internal VKMeshShadingPipeline Vulkan() - { - return (VKMeshShadingPipeline)meshShadingPipeline; - } - } - - extension(QueryHeap queryHeap) - { - internal VKQueryHeap Vulkan() - { - return (VKQueryHeap)queryHeap; - } - } } diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs deleted file mode 100644 index b101a612..00000000 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ /dev/null @@ -1,15 +0,0 @@ -global using VkBlendOp = Silk.NET.Vulkan.BlendOp; -global using VkBorderColor = Silk.NET.Vulkan.BorderColor; -global using VkBuffer = Silk.NET.Vulkan.Buffer; -global using VkBufferUsageFlags = Silk.NET.Vulkan.BufferUsageFlags; -global using VkColorComponentFlags = Silk.NET.Vulkan.ColorComponentFlags; -global using VkCommandBuffer = Silk.NET.Vulkan.CommandBuffer; -global using VkFilter = Silk.NET.Vulkan.Filter; -global using VkFrontFace = Silk.NET.Vulkan.FrontFace; -global using VkPipeline = Silk.NET.Vulkan.Pipeline; -global using VkPrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology; -global using VkQueryType = Silk.NET.Vulkan.QueryType; -global using VkSampler = Silk.NET.Vulkan.Sampler; -global using VkShaderStageFlags = Silk.NET.Vulkan.ShaderStageFlags; -global using VkStencilOp = Silk.NET.Vulkan.StencilOp; -global using VkViewport = Silk.NET.Vulkan.Viewport; diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs deleted file mode 100644 index c1a76ec7..00000000 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ /dev/null @@ -1,177 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKBottomLevelAccelerationStructure : BottomLevelAccelerationStructure -{ - public AccelerationStructureKHR AccelerationStructure; - - public ulong DeviceAddress; - - public VKBottomLevelAccelerationStructure(VKGraphicsContext context, BottomLevelAccelerationStructureDesc desc, VKCommandBuffer commandBuffer) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - uint geometryCount = (uint)desc.Geometries.Length; - - TransformBuffer = new(context, new() - { - SizeInBytes = (uint)(sizeof(TransformMatrixKHR) * geometryCount), - StrideInBytes = (uint)sizeof(TransformMatrixKHR), - Flags = BufferUsageFlags.AccelerationStructure | BufferUsageFlags.MapWrite - }); - - MappedMemory mappedMemory = TransformBuffer.Map(); - - TransformMatrixKHR[] transforms = desc.Geometries.Select(static item => VKFormats.Vulkan(item.Triangles.Transform)).ToArray(); - - if (geometryCount > 0) - { - unsafe - { - transforms.AsSpan().CopyTo(new Span((void*)mappedMemory.Pointer, (int)geometryCount)); - } - } - - TransformBuffer.Unmap(); - - AccelerationStructureGeometryKHR* geometries = (AccelerationStructureGeometryKHR*)ZenithMarshal.Allocate(scope, geometryCount); - uint* maxPrimitiveCounts = (uint*)ZenithMarshal.Allocate(scope, geometryCount); - AccelerationStructureBuildRangeInfoKHR* buildRangeInfos = (AccelerationStructureBuildRangeInfoKHR*)ZenithMarshal.Allocate(scope, geometryCount); - for (uint i = 0; i < geometryCount; i++) - { - RayTracingGeometry geometry = desc.Geometries[i]; - - geometries[i] = new() - { - SType = StructureType.AccelerationStructureGeometryKhr, - GeometryType = VKFormats.Vulkan(geometry.Type), - Geometry = new - ( - triangles: geometry.Type is RayTracingGeometryType.Triangles ? new() - { - SType = StructureType.AccelerationStructureGeometryTrianglesDataKhr, - VertexFormat = VKFormats.Vulkan(geometry.Triangles.VertexFormat), - VertexData = new() { DeviceAddress = geometry.Triangles.VertexBuffer.Vulkan().DeviceAddress + geometry.Triangles.VertexOffsetInBytes }, - VertexStride = geometry.Triangles.VertexStrideInBytes, - MaxVertex = geometry.Triangles.VertexCount, - IndexType = geometry.Triangles.IndexBuffer is not null ? VKFormats.Vulkan(geometry.Triangles.IndexFormat) : IndexType.NoneKhr, - IndexData = new() { DeviceAddress = geometry.Triangles.IndexBuffer is not null ? geometry.Triangles.IndexBuffer.Vulkan().DeviceAddress + geometry.Triangles.IndexOffsetInBytes : 0 }, - TransformData = new() { DeviceAddress = TransformBuffer.DeviceAddress + (uint)(sizeof(TransformMatrixKHR) * i) } - } : null, - aabbs: geometry.Type is RayTracingGeometryType.AABBs ? new() - { - SType = StructureType.AccelerationStructureGeometryAabbsDataKhr, - Data = new() { DeviceAddress = geometry.AABBs.Buffer.Vulkan().DeviceAddress + geometry.AABBs.OffsetInBytes }, - Stride = geometry.AABBs.StrideInBytes - } : null - ), - Flags = VKFormats.Vulkan(geometry.Flags) - }; - maxPrimitiveCounts[i] = geometry.Type is RayTracingGeometryType.Triangles ? geometry.Triangles.IndexBuffer is not null ? geometry.Triangles.IndexCount / 3 : geometry.Triangles.VertexCount / 3 : geometry.AABBs.Count; - buildRangeInfos[i] = new() { PrimitiveCount = maxPrimitiveCounts[i] }; - } - - AccelerationStructureBuildGeometryInfoKHR buildInfo = new() - { - SType = StructureType.AccelerationStructureBuildGeometryInfoKhr, - Type = AccelerationStructureTypeKHR.BottomLevelKhr, - Flags = VKFormats.Vulkan(desc.Flags), - Mode = BuildAccelerationStructureModeKHR.BuildKhr, - GeometryCount = geometryCount, - PGeometries = geometries - }; - - AccelerationStructureBuildSizesInfoKHR sizeInfo = new() { SType = StructureType.AccelerationStructureBuildSizesInfoKhr }; - - context.AccelerationStructure?.GetAccelerationStructureBuildSizes(context.Device, AccelerationStructureBuildTypeKHR.DeviceKhr, &buildInfo, maxPrimitiveCounts, &sizeInfo); - - BufferDesc accelerationStructureBufferDesc = new() - { - SizeInBytes = (uint)sizeInfo.AccelerationStructureSize, - StrideInBytes = (uint)sizeInfo.AccelerationStructureSize - }; - - AccelerationStructureBuffer = new(context, accelerationStructureBufferDesc, VkBufferUsageFlags.AccelerationStructureStorageBitKhr); - - AccelerationStructureCreateInfoKHR createInfo = new() - { - SType = StructureType.AccelerationStructureCreateInfoKhr, - Buffer = AccelerationStructureBuffer.Buffer, - Size = sizeInfo.AccelerationStructureSize, - Type = AccelerationStructureTypeKHR.BottomLevelKhr - }; - - context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, null, out AccelerationStructure).Success(); - - AccelerationStructureDeviceAddressInfoKHR addressInfo = new() - { - SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, - AccelerationStructure = AccelerationStructure - }; - - DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &addressInfo) ?? 0; - - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)sizeInfo.BuildScratchSize, - StrideInBytes = (uint)sizeInfo.BuildScratchSize, - Flags = BufferUsageFlags.ShaderResource - }); - - buildInfo.DstAccelerationStructure = AccelerationStructure; - buildInfo.ScratchData = new() { DeviceAddress = ScratchBuffer.DeviceAddress }; - - context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &buildInfo, &buildRangeInfos); - - MemoryBarrier barrier = new() - { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.AccelerationStructureWriteBitKhr, - DstAccessMask = AccessFlags.AccelerationStructureReadBitKhr - }; - - context.Vk.CmdPipelineBarrier(commandBuffer.CommandBuffer, - PipelineStageFlags.AccelerationStructureBuildBitKhr, - PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit, - 0, - 1, - &barrier, - 0, - null, - 0, - null); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKBuffer TransformBuffer { get; } - - public VKBuffer AccelerationStructureBuffer { get; } - - public VKBuffer ScratchBuffer { get; } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.AccelerationStructureKhr, - ObjectHandle = AccelerationStructure.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, null); - - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - TransformBuffer.Dispose(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs deleted file mode 100644 index 3010d266..00000000 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKBuffer : Buffer -{ - public VkBuffer Buffer; - - public ulong DeviceAddress; - - public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); - - BufferCreateInfo createInfo = new() - { - SType = StructureType.BufferCreateInfo, - Size = desc.SizeInBytes, - Usage = VKFormats.Vulkan(desc.Flags).UsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices - }; - - context.Vk.CreateBuffer(context.Device, &createInfo, null, out Buffer).Success(); - - DeviceMemory = new(context, this); - - BufferDeviceAddressInfo addressInfo = new() - { - SType = StructureType.BufferDeviceAddressInfo, - Buffer = Buffer - }; - - DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &addressInfo); - - View = new(context, new() - { - Buffer = this, - OffsetInBytes = 0, - SizeInBytes = desc.SizeInBytes, - StrideInBytes = desc.StrideInBytes - }); - } - - public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBufferUsageFlags otherUsageFlags) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); - - BufferCreateInfo createInfo = new() - { - SType = StructureType.BufferCreateInfo, - Size = desc.SizeInBytes, - Usage = VKFormats.Vulkan(desc.Flags).UsageFlags | otherUsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices - }; - - context.Vk.CreateBuffer(context.Device, &createInfo, null, out Buffer).Success(); - - DeviceMemory = new(context, this); - - BufferDeviceAddressInfo addressInfo = new() - { - SType = StructureType.BufferDeviceAddressInfo, - Buffer = Buffer - }; - - DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &addressInfo); - - View = new(context, new() - { - Buffer = this, - OffsetInBytes = 0, - SizeInBytes = desc.SizeInBytes, - StrideInBytes = desc.StrideInBytes - }); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKDeviceMemory DeviceMemory { get; } - - public VKBufferView View { get; } - - protected override MappedMemory MapImpl() - { - void* pointer; - Context.Vk.MapMemory(Context.Device, DeviceMemory.DeviceMemory, 0, Desc.SizeInBytes, 0, &pointer).Success(); - - return new() { Pointer = (nint)pointer, SizeInBytes = Desc.SizeInBytes }; - } - - public override void Unmap() - { - Context.Vk.UnmapMemory(Context.Device, DeviceMemory.DeviceMemory); - } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Buffer, - ObjectHandle = Buffer.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - View.Dispose(); - - Context.Vk.DestroyBuffer(Context.Device, Buffer, null); - - DeviceMemory.Dispose(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKBufferView.cs b/sources/Zenith.NET.Vulkan/VKBufferView.cs deleted file mode 100644 index 83d2e473..00000000 --- a/sources/Zenith.NET.Vulkan/VKBufferView.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal class VKBufferView : BufferView -{ - public VKBufferView(VKGraphicsContext context, BufferViewDesc desc) : base(context, desc) - { - BufferInfo = new() - { - Buffer = desc.Buffer.Vulkan().Buffer, - Offset = desc.OffsetInBytes, - Range = desc.SizeInBytes - }; - } - - public DescriptorBufferInfo BufferInfo { get; } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - } -} diff --git a/sources/Zenith.NET.Vulkan/VKCapabilities.cs b/sources/Zenith.NET.Vulkan/VKCapabilities.cs deleted file mode 100644 index b129ad59..00000000 --- a/sources/Zenith.NET.Vulkan/VKCapabilities.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.EXT; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKCapabilities : Capabilities -{ - public VKCapabilities(VKGraphicsContext context) - { - using ZenithMarshal.Scope scope = new(); - - PhysicalDeviceProperties properties; - context.Vk.GetPhysicalDeviceProperties(context.PhysicalDevice, &properties); - - uint extensionCount = 0; - context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, (byte*)null, &extensionCount, (ExtensionProperties*)null).Success(); - - ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); - context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, (byte*)null, &extensionCount, extensions).Success(); - - string[] supportedExtensions = [.. new ReadOnlySpan(extensions, (int)extensionCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.ExtensionName, StringEncoding.UTF8))]; - - DeviceName = ZenithMarshal.StringFromPointer((nint)properties.DeviceName, StringEncoding.UTF8); - RayTracingSupported = supportedExtensions.Contains(KhrRayQuery.ExtensionName); - MeshShadingSupported = supportedExtensions.Contains(ExtMeshShader.ExtensionName); - } - - public override string DeviceName { get; } - - public override bool RayTracingSupported { get; } - - public override bool MeshShadingSupported { get; } -} diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs deleted file mode 100644 index a2d0a913..00000000 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ /dev/null @@ -1,633 +0,0 @@ -using System.Numerics; -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKCommandBuffer : CommandBuffer -{ - private readonly List renderPassImageViews = []; - - public CommandPool CommandPool; - - public VkCommandBuffer CommandBuffer; - - public VKCommandBuffer(VKGraphicsContext context, VKCommandQueue queue) : base(context, queue) - { - CommandPoolCreateInfo createInfo = new() - { - SType = StructureType.CommandPoolCreateInfo, - Flags = CommandPoolCreateFlags.ResetCommandBufferBit, - QueueFamilyIndex = queue.QueueFamilyIndex - }; - - context.Vk.CreateCommandPool(context.Device, &createInfo, null, out CommandPool).Success(); - - CommandBufferAllocateInfo allocateInfo = new() - { - SType = StructureType.CommandBufferAllocateInfo, - CommandPool = CommandPool, - Level = CommandBufferLevel.Primary, - CommandBufferCount = 1 - }; - - context.Vk.AllocateCommandBuffers(context.Device, &allocateInfo, out CommandBuffer).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) - { - VKBuffer vkSrc = src.Vulkan(); - VKBuffer vkDest = dest.Vulkan(); - - BufferCopy copyRegion = new() - { - SrcOffset = srcOffsetInBytes, - DstOffset = destOffsetInBytes, - Size = sizeInBytes - }; - - Context.Vk.CmdCopyBuffer(CommandBuffer, vkSrc.Buffer, vkDest.Buffer, 1, ©Region); - - MemoryBarrier barrier = new() - { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.TransferWriteBit, - DstAccessMask = AccessFlags.TransferReadBit - }; - - Context.Vk.CmdPipelineBarrier(CommandBuffer, PipelineStageFlags.TransferBit, PipelineStageFlags.TransferBit, 0, 1, &barrier, 0, null, 0, null); - } - - protected override void CopyBufferToTextureImpl(Buffer src, - uint srcOffsetInBytes, - TextureDataLayout srcLayout, - Texture dest, - TextureSubresource destSubresource, - Offset3D destOffset, - Extent3D destExtent) - { - VKBuffer vkSrc = src.Vulkan(); - VKTexture vkDest = dest.Vulkan(); - - (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkDest.Desc.Format, destExtent.Width, destExtent.Height); - - uint formatSizeInBytes = ZenithHelper.SizeInBytes(vkDest.Desc.Format); - - BufferImageCopy bufferImageCopy = new() - { - BufferOffset = srcOffsetInBytes, - BufferRowLength = srcLayout.RowPitchInBytes / formatSizeInBytes * blockWidth, - BufferImageHeight = srcLayout.SlicePitchInBytes / srcLayout.RowPitchInBytes * blockHeight, - ImageSubresource = new() - { - AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSubresource.MipLevel, - BaseArrayLayer = destSubresource.ArrayLayer, - LayerCount = 1 - }, - ImageOffset = new() - { - X = (int)destOffset.X, - Y = (int)destOffset.Y, - Z = (int)destOffset.Z - }, - ImageExtent = new() - { - Width = destExtent.Width, - Height = destExtent.Height, - Depth = destExtent.Depth - } - }; - - Context.Vk.CmdCopyBufferToImage(CommandBuffer, vkSrc.Buffer, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &bufferImageCopy); - } - - protected override void CopyTextureImpl(Texture src, - TextureSubresource srcSubresource, - Offset3D srcOffset, - Texture dest, - TextureSubresource destSubresource, - Offset3D destOffset, - Extent3D extent) - { - VKTexture vkSrc = src.Vulkan(); - VKTexture vkDest = dest.Vulkan(); - - ImageCopy imageCopy = new() - { - SrcSubresource = new() - { - AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSubresource.MipLevel, - BaseArrayLayer = srcSubresource.ArrayLayer, - LayerCount = 1 - }, - SrcOffset = new() - { - X = (int)srcOffset.X, - Y = (int)srcOffset.Y, - Z = (int)srcOffset.Z - }, - DstSubresource = new() - { - AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSubresource.MipLevel, - BaseArrayLayer = destSubresource.ArrayLayer, - LayerCount = 1 - }, - DstOffset = new() - { - X = (int)destOffset.X, - Y = (int)destOffset.Y, - Z = (int)destOffset.Z - }, - Extent = new() - { - Width = extent.Width, - Height = extent.Height, - Depth = extent.Depth - } - }; - - Context.Vk.CmdCopyImage(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &imageCopy); - } - - protected override void CopyTextureToBufferImpl(Texture src, - TextureSubresource srcSubresource, - Offset3D srcOffset, - Extent3D srcExtent, - Buffer dest, - uint destOffsetInBytes, - TextureDataLayout destLayout) - { - VKTexture vkSrc = src.Vulkan(); - - (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkSrc.Desc.Format, srcExtent.Width, srcExtent.Height); - - uint formatSizeInBytes = ZenithHelper.SizeInBytes(vkSrc.Desc.Format); - - BufferImageCopy bufferImageCopy = new() - { - BufferOffset = destOffsetInBytes, - BufferRowLength = destLayout.RowPitchInBytes / formatSizeInBytes * blockWidth, - BufferImageHeight = destLayout.SlicePitchInBytes / destLayout.RowPitchInBytes * blockHeight, - ImageSubresource = new() - { - AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSubresource.MipLevel, - BaseArrayLayer = srcSubresource.ArrayLayer, - LayerCount = 1 - }, - ImageOffset = new() - { - X = (int)srcOffset.X, - Y = (int)srcOffset.Y, - Z = (int)srcOffset.Z - }, - ImageExtent = new() - { - Width = srcExtent.Width, - Height = srcExtent.Height, - Depth = srcExtent.Depth - } - }; - - Context.Vk.CmdCopyImageToBuffer(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, dest.Vulkan().Buffer, 1, &bufferImageCopy); - } - - protected override TextureDataLayout GetTextureCopyLayout(PixelFormat format, Extent3D extent) - { - uint rowPitchInBytes = ZenithHelper.RowPitchInBytes(format, extent.Width, extent.Height); - uint slicePitchInBytes = ZenithHelper.SlicePitchInBytes(format, extent.Width, extent.Height); - - return new() - { - SizeInBytes = slicePitchInBytes * extent.Depth, - RowPitchInBytes = rowPitchInBytes, - SlicePitchInBytes = slicePitchInBytes - }; - } - - protected override void ResolveTextureImpl(Texture src, - TextureSubresource srcSubresource, - Texture dest, - TextureSubresource destSubresource) - { - VKTexture vkSrc = src.Vulkan(); - VKTexture vkDest = dest.Vulkan(); - - ZenithHelper.MipDimensions(vkDest.Desc.Width, vkDest.Desc.Height, vkDest.Desc.Depth, destSubresource.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); - - ImageResolve imageResolve = new() - { - SrcSubresource = new() - { - AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSubresource.MipLevel, - BaseArrayLayer = srcSubresource.ArrayLayer, - LayerCount = 1 - }, - DstSubresource = new() - { - AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSubresource.MipLevel, - BaseArrayLayer = destSubresource.ArrayLayer, - LayerCount = 1 - }, - Extent = new() - { - Width = mipWidth, - Height = mipHeight, - Depth = mipDepth - } - }; - - Context.Vk.CmdResolveImage(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &imageResolve); - } - - protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) - { - return new VKBottomLevelAccelerationStructure(Context, desc, this); - } - - protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) - { - return new VKTopLevelAccelerationStructure(Context, desc, this); - } - - protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) - { - accelerationStructure.Vulkan().Update(this, newDesc); - } - - protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, - DepthStencilAttachment? depthStencilAttachment) - { - using ZenithMarshal.Scope scope = new(); - - uint colorAttachmentCount = (uint)colorAttachments.Length; - RenderingAttachmentInfo* colorAttachmentInfos = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - - uint width = 0; - uint height = 0; - - for (uint i = 0; i < colorAttachmentCount; i++) - { - ColorAttachment attachment = colorAttachments[(int)i]; - - if (i is 0) - { - ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, - attachment.Texture.Desc.Height, - 0, - attachment.Subresource.MipLevel, - out width, - out height, - out _); - } - - ImageView imageView = attachment.Texture.Vulkan().CreateAttachmentView(attachment.Subresource); - renderPassImageViews.Add(imageView); - - colorAttachmentInfos[i] = new() - { - SType = StructureType.RenderingAttachmentInfo, - ImageView = imageView, - ImageLayout = ImageLayout.AttachmentOptimal, - LoadOp = attachment.LoadOp switch - { - LoadOp.Clear => AttachmentLoadOp.Clear, - LoadOp.DontCare => AttachmentLoadOp.DontCare, - _ => AttachmentLoadOp.Load - }, - StoreOp = attachment.StoreOp is StoreOp.Store ? AttachmentStoreOp.Store : AttachmentStoreOp.DontCare - }; - - if (attachment.LoadOp is LoadOp.Clear) - { - Vector4 color = attachment.ClearColor; - - colorAttachmentInfos[i].ClearValue.Color = new() - { - Float32_0 = color.X, - Float32_1 = color.Y, - Float32_2 = color.Z, - Float32_3 = color.W - }; - } - } - - RenderingAttachmentInfo* depthAttachment = null; - RenderingAttachmentInfo* stencilAttachment = null; - - if (depthStencilAttachment is { } depthStencilRenderTarget) - { - if (colorAttachmentCount is 0) - { - ZenithHelper.MipDimensions(depthStencilRenderTarget.Texture.Desc.Width, - depthStencilRenderTarget.Texture.Desc.Height, - 0, - depthStencilRenderTarget.Subresource.MipLevel, - out width, - out height, - out _); - } - - ImageView imageView = depthStencilRenderTarget.Texture.Vulkan().CreateAttachmentView(depthStencilRenderTarget.Subresource); - renderPassImageViews.Add(imageView); - - if (ZenithHelper.HasDepth(depthStencilRenderTarget.Texture.Desc.Format)) - { - depthAttachment = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, 1); - depthAttachment[0] = new() - { - SType = StructureType.RenderingAttachmentInfo, - ImageView = imageView, - ImageLayout = ImageLayout.AttachmentOptimal, - LoadOp = depthStencilRenderTarget.DepthLoadOp switch - { - LoadOp.Clear => AttachmentLoadOp.Clear, - LoadOp.DontCare => AttachmentLoadOp.DontCare, - _ => AttachmentLoadOp.Load - }, - StoreOp = depthStencilRenderTarget.DepthStoreOp is StoreOp.Store ? AttachmentStoreOp.Store : AttachmentStoreOp.DontCare - }; - - if (depthStencilRenderTarget.DepthLoadOp is LoadOp.Clear) - { - depthAttachment[0].ClearValue.DepthStencil.Depth = depthStencilRenderTarget.ClearDepth; - } - } - - if (ZenithHelper.HasStencil(depthStencilRenderTarget.Texture.Desc.Format)) - { - stencilAttachment = (RenderingAttachmentInfo*)ZenithMarshal.Allocate(scope, 1); - stencilAttachment[0] = new() - { - SType = StructureType.RenderingAttachmentInfo, - ImageView = imageView, - ImageLayout = ImageLayout.AttachmentOptimal, - LoadOp = depthStencilRenderTarget.StencilLoadOp switch - { - LoadOp.Clear => AttachmentLoadOp.Clear, - LoadOp.DontCare => AttachmentLoadOp.DontCare, - _ => AttachmentLoadOp.Load - }, - StoreOp = depthStencilRenderTarget.StencilStoreOp is StoreOp.Store ? AttachmentStoreOp.Store : AttachmentStoreOp.DontCare - }; - - if (depthStencilRenderTarget.StencilLoadOp is LoadOp.Clear) - { - stencilAttachment[0].ClearValue.DepthStencil.Stencil = depthStencilRenderTarget.ClearStencil; - } - } - } - - RenderingInfo renderingInfo = new() - { - SType = StructureType.RenderingInfo, - RenderArea = new() { Extent = new() { Width = width, Height = height } }, - LayerCount = 1, - ColorAttachmentCount = colorAttachmentCount, - PColorAttachments = colorAttachmentInfos, - PDepthAttachment = depthAttachment, - PStencilAttachment = stencilAttachment - }; - - Context.Vk.CmdBeginRendering(CommandBuffer, ref renderingInfo); - } - - protected override void EndRenderPassImpl() - { - Context.Vk.CmdEndRendering(CommandBuffer); - } - - protected override void SetScissorsImpl(ReadOnlySpan scissors) - { - Span vkScissors = scissors.Length <= 8 ? stackalloc Rect2D[scissors.Length] : new Rect2D[scissors.Length]; - - for (int i = 0; i < scissors.Length; i++) - { - Scissor scissor = scissors[i]; - - vkScissors[i] = new(new(scissor.X, scissor.Y), new(scissor.Width, scissor.Height)); - } - - Context.Vk.CmdSetScissor(CommandBuffer, 0, (uint)vkScissors.Length, ref vkScissors[0]); - } - - protected override void SetViewportsImpl(ReadOnlySpan viewports) - { - Span vkViewports = viewports.Length <= 8 ? stackalloc VkViewport[viewports.Length] : new VkViewport[viewports.Length]; - - for (int i = 0; i < viewports.Length; i++) - { - Viewport viewport = viewports[i]; - - vkViewports[i] = new(viewport.X, viewport.Y + viewport.Height, viewport.Width, -viewport.Height, viewport.MinDepth, viewport.MaxDepth); - } - - Context.Vk.CmdSetViewport(CommandBuffer, 0, (uint)vkViewports.Length, ref vkViewports[0]); - } - - protected override void SetPipelineImpl(GraphicsPipeline pipeline) - { - Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); - } - - protected override void SetPipelineImpl(ComputePipeline pipeline) - { - Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Compute, pipeline.Vulkan().Pipeline); - } - - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) - { - Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); - } - - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) - { - VkBuffer vkBuffer = buffer.Vulkan().Buffer; - ulong vkOffset = offsetInBytes; - - Context.Vk.CmdBindVertexBuffers(CommandBuffer, index, 1, ref vkBuffer, ref vkOffset); - } - - protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format) - { - Context.Vk.CmdBindIndexBuffer(CommandBuffer, buffer.Vulkan().Buffer, offsetInBytes, VKFormats.Vulkan(format)); - } - - protected override void SetStencilReferenceImpl(uint reference) - { - Context.Vk.CmdSetStencilReference(CommandBuffer, StencilFaceFlags.FrontAndBackBit, reference); - } - - protected override void SetBlendConstantImpl(Vector4 blendConstant) - { - Span values = stackalloc float[4] { blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W }; - - Context.Vk.CmdSetBlendConstants(CommandBuffer, values); - } - - protected override void PushResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) - { - (PipelineBindPoint pipelineBindPoint, PipelineLayout pipelineLayout, uint descriptorWriteCount) = pipeline switch - { - GraphicsPipeline graphicsPipeline => (PipelineBindPoint.Graphics, graphicsPipeline.Vulkan().PipelineLayout, (uint)graphicsPipeline.Desc.ResourceBindings.Length), - ComputePipeline computePipeline => (PipelineBindPoint.Compute, computePipeline.Vulkan().PipelineLayout, (uint)computePipeline.Desc.ResourceBindings.Length), - MeshShadingPipeline meshShadingPipeline => (PipelineBindPoint.Graphics, meshShadingPipeline.Vulkan().PipelineLayout, (uint)meshShadingPipeline.Desc.ResourceBindings.Length), - _ => (PipelineBindPoint.Graphics, default, 0) - }; - - Context.Vk.CmdPushDescriptorSet(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, descriptorWriteCount, resourceTable.Vulkan().Sets); - } - - protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) - { - Context.Vk.CmdDraw(CommandBuffer, vertexCount, instanceCount, firstVertex, firstInstance); - } - - protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - Context.Vk.CmdDrawIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes, drawCount, (uint)sizeof(IndirectDrawArgs)); - } - - protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) - { - Context.Vk.CmdDrawIndexed(CommandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); - } - - protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) - { - Context.Vk.CmdDrawIndexedIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes, drawCount, (uint)sizeof(IndirectDrawIndexedArgs)); - } - - protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) - { - Context.Vk.CmdDispatch(CommandBuffer, groupCountX, groupCountY, groupCountZ); - } - - protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) - { - Context.Vk.CmdDispatchIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes); - } - - protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) - { - Context.MeshShader?.CmdDrawMeshTask(CommandBuffer, groupCountX, groupCountY, groupCountZ); - } - - protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) - { - Context.MeshShader?.CmdDrawMeshTasksIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes, dispatchCount, (uint)sizeof(IndirectDispatchMeshArgs)); - } - - protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) - { - Context.Vk.CmdResetQueryPool(CommandBuffer, queryHeap.Vulkan().QueryPool, index, 1); - - Context.Vk.CmdBeginQuery(CommandBuffer, queryHeap.Vulkan().QueryPool, index, queryHeap.Desc.Type is QueryType.Occlusion ? QueryControlFlags.PreciseBit : QueryControlFlags.None); - } - - protected override void EndQueryImpl(QueryHeap queryHeap, uint index) - { - Context.Vk.CmdEndQuery(CommandBuffer, queryHeap.Vulkan().QueryPool, index); - } - - protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) - { - Context.Vk.CmdResetQueryPool(CommandBuffer, queryHeap.Vulkan().QueryPool, index, 1); - - Context.Vk.CmdWriteTimestamp(CommandBuffer, PipelineStageFlags.BottomOfPipeBit, queryHeap.Vulkan().QueryPool, index); - } - - protected override void BeginDebugEventImpl(string label) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsLabelEXT labelInfo = new() - { - SType = StructureType.DebugUtilsLabelExt, - PLabelName = (byte*)ZenithMarshal.StringToPointer(scope, label, StringEncoding.UTF8) - }; - - Context.DebugUtils?.CmdBeginDebugUtilsLabel(CommandBuffer, &labelInfo); - } - - protected override void EndDebugEventImpl() - { - Context.DebugUtils?.CmdEndDebugUtilsLabel(CommandBuffer); - } - - protected override void InsertDebugMarkerImpl(string label) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsLabelEXT labelInfo = new() - { - SType = StructureType.DebugUtilsLabelExt, - PLabelName = (byte*)ZenithMarshal.StringToPointer(scope, label, StringEncoding.UTF8) - }; - - Context.DebugUtils?.CmdInsertDebugUtilsLabel(CommandBuffer, &labelInfo); - } - - protected override void BeginImpl() - { - CommandBufferBeginInfo beginInfo = new() - { - SType = StructureType.CommandBufferBeginInfo, - Flags = CommandBufferUsageFlags.OneTimeSubmitBit - }; - - Context.Vk.BeginCommandBuffer(CommandBuffer, &beginInfo).Success(); - } - - protected override void EndImpl() - { - Context.Vk.EndCommandBuffer(CommandBuffer).Success(); - } - - protected override void ResetImpl() - { - ReleaseRenderPassImageViews(); - - Context.Vk.ResetCommandBuffer(CommandBuffer, CommandBufferResetFlags.None).Success(); - } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.CommandBuffer, - ObjectHandle = (ulong)CommandBuffer.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - ReleaseRenderPassImageViews(); - - base.Destroy(); - - Context.Vk.DestroyCommandPool(Context.Device, CommandPool, null); - } - - private void ReleaseRenderPassImageViews() - { - foreach (ImageView imageView in renderPassImageViews) - { - Context.Vk.DestroyImageView(Context.Device, imageView, null); - } - - renderPassImageViews.Clear(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs deleted file mode 100644 index 2b7a7c9a..00000000 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : CommandQueue(context, type) -{ - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public uint QueueFamilyIndex => queueFamilyIndex; - - protected override CommandBuffer CreateCommandBuffer() - { - return new VKCommandBuffer(Context, this); - } - - protected override void WaitIdleImpl() - { - Context.Vk.QueueWaitIdle(queue).Success(); - } - - protected override void SubmitImpl(CommandBuffer commandBuffer) - { - VKCommandBuffer vkCommandBuffer = commandBuffer.Vulkan(); - - fixed (VkCommandBuffer* pCommandBuffers = &vkCommandBuffer.CommandBuffer) - { - SubmitInfo submitInfo = new() - { - SType = StructureType.SubmitInfo, - CommandBufferCount = 1, - PCommandBuffers = pCommandBuffers - }; - - Context.Vk.QueueSubmit(queue, 1, &submitInfo, default).Success(); - } - } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Queue, - ObjectHandle = (ulong)queue.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs deleted file mode 100644 index 657f1c85..00000000 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKComputePipeline : ComputePipeline -{ - public DescriptorSetLayout DescriptorSetLayout; - - public PipelineLayout PipelineLayout; - - public VkPipeline Pipeline; - - public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - ComputePipelineCreateInfo createInfo = new() - { - SType = StructureType.ComputePipelineCreateInfo, - Stage = desc.Compute.Vulkan().GetPipelineShaderStageCreateInfo(scope) - }; - - // ResourceBindings - { - PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() - { - SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout = desc.ResourceBindings.DescriptorSetLayout(context)]) - }; - - context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - - createInfo.Layout = PipelineLayout; - } - - context.Vk.CreateComputePipelines(context.Device, default, 1, &createInfo, null, out Pipeline).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Pipeline, - ObjectHandle = Pipeline.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); - Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); - Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKDeviceMemory.cs b/sources/Zenith.NET.Vulkan/VKDeviceMemory.cs deleted file mode 100644 index 2b86b804..00000000 --- a/sources/Zenith.NET.Vulkan/VKDeviceMemory.cs +++ /dev/null @@ -1,131 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKDeviceMemory : GraphicsResource -{ - public DeviceMemory DeviceMemory; - - public VKDeviceMemory(VKGraphicsContext context, VKBuffer buffer) : base(context) - { - BufferMemoryRequirementsInfo2 requirementsInfo2 = new() - { - SType = StructureType.BufferMemoryRequirementsInfo2, - Buffer = buffer.Buffer - }; - - MemoryRequirements2 requirements2 = new() - { - SType = StructureType.MemoryRequirements2 - }; - - requirements2.AddNext(out MemoryDedicatedRequirements requirements); - - context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - - MemoryAllocateInfo allocateInfo = new() - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = requirements2.MemoryRequirements.Size, - MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, VKFormats.Vulkan(buffer.Desc.Flags).PropertyFlags) - }; - - if (requirements.PrefersDedicatedAllocation || requirements.RequiresDedicatedAllocation) - { - allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); - dedicatedAllocateInfo.Buffer = buffer.Buffer; - } - - allocateInfo.AddNext(out MemoryAllocateFlagsInfo flagsInfo); - flagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; - - context.Vk.AllocateMemory(context.Device, &allocateInfo, null, out DeviceMemory).Success(); - - context.Vk.BindBufferMemory(context.Device, buffer.Buffer, DeviceMemory, 0).Success(); - } - - public VKDeviceMemory(VKGraphicsContext context, VKTexture texture) : base(context) - { - ImageMemoryRequirementsInfo2 requirementsInfo2 = new() - { - SType = StructureType.ImageMemoryRequirementsInfo2, - Image = texture.Image - }; - - MemoryRequirements2 requirements2 = new() - { - SType = StructureType.MemoryRequirements2 - }; - - requirements2.AddNext(out MemoryDedicatedRequirements requirements); - - context.Vk.GetImageMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - - MemoryAllocateInfo allocateInfo = new() - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = requirements2.MemoryRequirements.Size, - MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit) - }; - - if (requirements.PrefersDedicatedAllocation || requirements.RequiresDedicatedAllocation) - { - allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); - dedicatedAllocateInfo.Image = texture.Image; - } - - context.Vk.AllocateMemory(context.Device, &allocateInfo, null, out DeviceMemory).Success(); - - context.Vk.BindImageMemory(context.Device, texture.Image, DeviceMemory, 0).Success(); - } - - public VKDeviceMemory(VKGraphicsContext context, VKTexture texture, ExternalMemoryHandleTypeFlags handleTypes, nint handle) : base(context) - { - ImageMemoryRequirementsInfo2 requirementsInfo2 = new() - { - SType = StructureType.ImageMemoryRequirementsInfo2, - Image = texture.Image - }; - - MemoryRequirements2 requirements2 = new() - { - SType = StructureType.MemoryRequirements2 - }; - - requirements2.AddNext(out MemoryDedicatedRequirements requirements); - - context.Vk.GetImageMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - - MemoryAllocateInfo allocateInfo = new() - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = requirements2.MemoryRequirements.Size, - MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit) - }; - - if (requirements.PrefersDedicatedAllocation || requirements.RequiresDedicatedAllocation) - { - allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); - dedicatedAllocateInfo.Image = texture.Image; - } - - allocateInfo.AddNext(out ImportMemoryWin32HandleInfoKHR handleInfo); - handleInfo.HandleType = handleTypes; - handleInfo.Handle = handle; - - context.Vk.AllocateMemory(context.Device, &allocateInfo, null, out DeviceMemory).Success(); - - context.Vk.BindImageMemory(context.Device, texture.Image, DeviceMemory, 0).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Context.Vk.FreeMemory(Context.Device, DeviceMemory, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKFence.cs b/sources/Zenith.NET.Vulkan/VKFence.cs deleted file mode 100644 index 0da4efd0..00000000 --- a/sources/Zenith.NET.Vulkan/VKFence.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKFence : GraphicsResource -{ - public Fence Fence; - - public VKFence(VKGraphicsContext context) : base(context) - { - FenceCreateInfo createInfo = new() - { - SType = StructureType.FenceCreateInfo, - Flags = FenceCreateFlags.SignaledBit - }; - - context.Vk.CreateFence(context.Device, &createInfo, null, out Fence).Success(); - - context.Vk.ResetFences(context.Device, 1, ref Fence).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public void Wait() - { - Context.Vk.WaitForFences(Context.Device, 1, ref Fence, true, ulong.MaxValue).Success(); - - Context.Vk.ResetFences(Context.Device, 1, ref Fence).Success(); - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Context.Vk.DestroyFence(Context.Device, Fence, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs deleted file mode 100644 index 515a9f05..00000000 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ /dev/null @@ -1,721 +0,0 @@ -using System.Numerics; -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal static unsafe class VKFormats -{ - public static VkShaderStageFlags Vulkan(ShaderStageFlags shaderStageFlags) - { - VkShaderStageFlags result = VkShaderStageFlags.None; - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Vertex)) - { - result |= VkShaderStageFlags.VertexBit; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Pixel)) - { - result |= VkShaderStageFlags.FragmentBit; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Compute)) - { - result |= VkShaderStageFlags.ComputeBit; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Amplification)) - { - result |= VkShaderStageFlags.TaskBitExt; - } - - if (shaderStageFlags.HasFlag(ShaderStageFlags.Mesh)) - { - result |= VkShaderStageFlags.MeshBitExt; - } - - return result; - } - - public static (VkBufferUsageFlags UsageFlags, MemoryPropertyFlags PropertyFlags) Vulkan(BufferUsageFlags bufferUsageFlags) - { - VkBufferUsageFlags usageFlags = VkBufferUsageFlags.TransferSrcBit | VkBufferUsageFlags.TransferDstBit | VkBufferUsageFlags.ShaderDeviceAddressBit; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Vertex)) - { - usageFlags |= VkBufferUsageFlags.VertexBufferBit; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Index)) - { - usageFlags |= VkBufferUsageFlags.IndexBufferBit; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Indirect)) - { - usageFlags |= VkBufferUsageFlags.IndirectBufferBit; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.AccelerationStructure)) - { - usageFlags |= VkBufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Constant)) - { - usageFlags |= VkBufferUsageFlags.UniformBufferBit; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.ShaderResource) || bufferUsageFlags.HasFlag(BufferUsageFlags.UnorderedAccess)) - { - usageFlags |= VkBufferUsageFlags.StorageBufferBit; - } - - MemoryPropertyFlags propertyFlags = MemoryPropertyFlags.DeviceLocalBit; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead) || bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - propertyFlags = MemoryPropertyFlags.HostVisibleBit; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead)) - { - propertyFlags |= MemoryPropertyFlags.HostCachedBit; - } - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - propertyFlags |= MemoryPropertyFlags.HostCoherentBit; - } - } - - return (usageFlags, propertyFlags); - } - - public static (ImageType Type, ImageViewType ViewType) Vulkan(TextureType textureType) - { - return - ( - textureType switch - { - TextureType.Texture1D or - TextureType.Texture1DArray => ImageType.Type1D, - - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ImageType.Type2D, - - TextureType.Texture3D => ImageType.Type3D, - - _ => ImageType.Type1D - }, - textureType switch - { - TextureType.Texture1D => ImageViewType.Type1D, - TextureType.Texture1DArray => ImageViewType.Type1DArray, - TextureType.Texture2D => ImageViewType.Type2D, - TextureType.Texture2DArray => ImageViewType.Type2DArray, - TextureType.Texture3D => ImageViewType.Type3D, - TextureType.TextureCube => ImageViewType.TypeCube, - TextureType.TextureCubeArray => ImageViewType.TypeCubeArray, - _ => ImageViewType.Type1D - } - ); - } - - public static Format Vulkan(PixelFormat pixelFormat) - { - return pixelFormat switch - { - PixelFormat.R8UNorm => Format.R8Unorm, - PixelFormat.R8SNorm => Format.R8SNorm, - PixelFormat.R8UInt => Format.R8Uint, - PixelFormat.R8SInt => Format.R8Sint, - - PixelFormat.R16UNorm => Format.R16Unorm, - PixelFormat.R16SNorm => Format.R16SNorm, - PixelFormat.R16UInt => Format.R16Uint, - PixelFormat.R16SInt => Format.R16Sint, - PixelFormat.R16Float => Format.R16Sfloat, - - PixelFormat.R32UInt => Format.R32Uint, - PixelFormat.R32SInt => Format.R32Sint, - PixelFormat.R32Float => Format.R32Sfloat, - - PixelFormat.R8G8UNorm => Format.R8G8Unorm, - PixelFormat.R8G8SNorm => Format.R8G8SNorm, - PixelFormat.R8G8UInt => Format.R8G8Uint, - PixelFormat.R8G8SInt => Format.R8G8Sint, - - PixelFormat.R16G16UNorm => Format.R16G16Unorm, - PixelFormat.R16G16SNorm => Format.R16G16SNorm, - PixelFormat.R16G16UInt => Format.R16G16Uint, - PixelFormat.R16G16SInt => Format.R16G16Sint, - PixelFormat.R16G16Float => Format.R16G16Sfloat, - - PixelFormat.R32G32UInt => Format.R32G32Uint, - PixelFormat.R32G32SInt => Format.R32G32Sint, - PixelFormat.R32G32Float => Format.R32G32Sfloat, - - PixelFormat.R32G32B32UInt => Format.R32G32B32Uint, - PixelFormat.R32G32B32SInt => Format.R32G32B32Sint, - PixelFormat.R32G32B32Float => Format.R32G32B32Sfloat, - - PixelFormat.R8G8B8A8UNorm => Format.R8G8B8A8Unorm, - PixelFormat.R8G8B8A8SNorm => Format.R8G8B8A8SNorm, - PixelFormat.R8G8B8A8UInt => Format.R8G8B8A8Uint, - PixelFormat.R8G8B8A8SInt => Format.R8G8B8A8Sint, - PixelFormat.R8G8B8A8SRgb => Format.R8G8B8A8Srgb, - - PixelFormat.R16G16B16A16UNorm => Format.R16G16B16A16Unorm, - PixelFormat.R16G16B16A16SNorm => Format.R16G16B16A16SNorm, - PixelFormat.R16G16B16A16UInt => Format.R16G16B16A16Uint, - PixelFormat.R16G16B16A16SInt => Format.R16G16B16A16Sint, - PixelFormat.R16G16B16A16Float => Format.R16G16B16A16Sfloat, - - PixelFormat.R32G32B32A32UInt => Format.R32G32B32A32Uint, - PixelFormat.R32G32B32A32SInt => Format.R32G32B32A32Sint, - PixelFormat.R32G32B32A32Float => Format.R32G32B32A32Sfloat, - - PixelFormat.B8G8R8A8UNorm => Format.B8G8R8A8Unorm, - PixelFormat.B8G8R8A8SRgb => Format.B8G8R8A8Srgb, - - PixelFormat.D16UNorm => Format.D16Unorm, - PixelFormat.D24UNormS8UInt => Format.D24UnormS8Uint, - PixelFormat.D32Float => Format.D32Sfloat, - PixelFormat.D32FloatS8UInt => Format.D32SfloatS8Uint, - - PixelFormat.BC4UNorm => Format.BC4UnormBlock, - PixelFormat.BC4SNorm => Format.BC4SNormBlock, - - PixelFormat.BC5UNorm => Format.BC5UnormBlock, - PixelFormat.BC5SNorm => Format.BC5SNormBlock, - - PixelFormat.BC6HUFloat => Format.BC6HUfloatBlock, - PixelFormat.BC6HSFloat => Format.BC6HSfloatBlock, - - PixelFormat.BC7UNorm => Format.BC7UnormBlock, - PixelFormat.BC7SRgb => Format.BC7SrgbBlock, - - PixelFormat.ETC2UNorm => Format.Etc2R8G8B8UnormBlock, - PixelFormat.ETC2SRgb => Format.Etc2R8G8B8SrgbBlock, - - PixelFormat.ETC2A1UNorm => Format.Etc2R8G8B8A1UnormBlock, - PixelFormat.ETC2A1SRgb => Format.Etc2R8G8B8A1SrgbBlock, - - PixelFormat.ETC2A8UNorm => Format.Etc2R8G8B8A8UnormBlock, - PixelFormat.ETC2A8SRgb => Format.Etc2R8G8B8A8SrgbBlock, - - PixelFormat.ASTC4x4UNorm => Format.Astc4x4UnormBlock, - PixelFormat.ASTC4x4SRgb => Format.Astc4x4SrgbBlock, - PixelFormat.ASTC4x4Float => Format.Astc4x4SfloatBlock, - - PixelFormat.ASTC5x5UNorm => Format.Astc5x5UnormBlock, - PixelFormat.ASTC5x5SRgb => Format.Astc5x5SrgbBlock, - PixelFormat.ASTC5x5Float => Format.Astc5x5SfloatBlock, - - PixelFormat.ASTC6x6UNorm => Format.Astc6x6UnormBlock, - PixelFormat.ASTC6x6SRgb => Format.Astc6x6SrgbBlock, - PixelFormat.ASTC6x6Float => Format.Astc6x6SfloatBlock, - - PixelFormat.ASTC8x8UNorm => Format.Astc8x8UnormBlock, - PixelFormat.ASTC8x8SRgb => Format.Astc8x8SrgbBlock, - PixelFormat.ASTC8x8Float => Format.Astc8x8SfloatBlock, - - PixelFormat.ASTC10x10UNorm => Format.Astc10x10UnormBlock, - PixelFormat.ASTC10x10SRgb => Format.Astc10x10SrgbBlock, - PixelFormat.ASTC10x10Float => Format.Astc10x10SfloatBlock, - - PixelFormat.ASTC12x12UNorm => Format.Astc12x12UnormBlock, - PixelFormat.ASTC12x12SRgb => Format.Astc12x12SrgbBlock, - PixelFormat.ASTC12x12Float => Format.Astc12x12SfloatBlock, - - _ => Format.Undefined - }; - } - - public static SampleCountFlags Vulkan(SampleCount sampleCount) - { - return sampleCount switch - { - SampleCount.Count1 => SampleCountFlags.Count1Bit, - SampleCount.Count2 => SampleCountFlags.Count2Bit, - SampleCount.Count4 => SampleCountFlags.Count4Bit, - SampleCount.Count8 => SampleCountFlags.Count8Bit, - SampleCount.Count16 => SampleCountFlags.Count16Bit, - SampleCount.Count32 => SampleCountFlags.Count32Bit, - _ => SampleCountFlags.None - }; - } - - public static (ImageUsageFlags UsageFlags, ImageAspectFlags AspectFlags) Vulkan(PixelFormat pixelFormat, TextureUsageFlags textureUsageFlags) - { - ImageUsageFlags usageFlags = ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit; - - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget)) - { - usageFlags |= ImageUsageFlags.ColorAttachmentBit; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) - { - usageFlags |= ImageUsageFlags.DepthStencilAttachmentBit; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.ShaderResource)) - { - usageFlags |= ImageUsageFlags.SampledBit; - } - - if (textureUsageFlags.HasFlag(TextureUsageFlags.UnorderedAccess)) - { - usageFlags |= ImageUsageFlags.StorageBit; - } - - ImageAspectFlags aspectFlags = ImageAspectFlags.None; - - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) - { - if (ZenithHelper.HasDepth(pixelFormat)) - { - aspectFlags |= ImageAspectFlags.DepthBit; - } - - if (ZenithHelper.HasStencil(pixelFormat)) - { - aspectFlags |= ImageAspectFlags.StencilBit; - } - } - else - { - aspectFlags |= ImageAspectFlags.ColorBit; - } - - return (usageFlags, aspectFlags); - } - - public static (VkFilter MinFilter, VkFilter MagFilter, SamplerMipmapMode MipmapMode) Vulkan(Filter filter) - { - VkFilter minFilter = VkFilter.Nearest; - VkFilter magFilter = VkFilter.Nearest; - SamplerMipmapMode mipmapMode = SamplerMipmapMode.Nearest; - - switch (filter) - { - case Filter.MinPointMagPointMipPoint: - minFilter = VkFilter.Nearest; - magFilter = VkFilter.Nearest; - mipmapMode = SamplerMipmapMode.Nearest; - break; - - case Filter.MinPointMagPointMipLinear: - minFilter = VkFilter.Nearest; - magFilter = VkFilter.Nearest; - mipmapMode = SamplerMipmapMode.Linear; - break; - - case Filter.MinPointMagLinearMipPoint: - minFilter = VkFilter.Nearest; - magFilter = VkFilter.Linear; - mipmapMode = SamplerMipmapMode.Nearest; - break; - - case Filter.MinPointMagLinearMipLinear: - minFilter = VkFilter.Nearest; - magFilter = VkFilter.Linear; - mipmapMode = SamplerMipmapMode.Linear; - break; - - case Filter.MinLinearMagPointMipPoint: - minFilter = VkFilter.Linear; - magFilter = VkFilter.Nearest; - mipmapMode = SamplerMipmapMode.Nearest; - break; - - case Filter.MinLinearMagPointMipLinear: - minFilter = VkFilter.Linear; - magFilter = VkFilter.Nearest; - mipmapMode = SamplerMipmapMode.Linear; - break; - - case Filter.MinLinearMagLinearMipPoint: - minFilter = VkFilter.Linear; - magFilter = VkFilter.Linear; - mipmapMode = SamplerMipmapMode.Nearest; - break; - - case Filter.MinLinearMagLinearMipLinear: - minFilter = VkFilter.Linear; - magFilter = VkFilter.Linear; - mipmapMode = SamplerMipmapMode.Linear; - break; - - case Filter.Anisotropic: - minFilter = VkFilter.Linear; - magFilter = VkFilter.Linear; - mipmapMode = SamplerMipmapMode.Linear; - break; - } - - return (minFilter, magFilter, mipmapMode); - } - - public static SamplerAddressMode Vulkan(AddressMode addressMode) - { - return addressMode switch - { - AddressMode.Wrap => SamplerAddressMode.Repeat, - AddressMode.Mirror => SamplerAddressMode.MirroredRepeat, - AddressMode.Clamp => SamplerAddressMode.ClampToEdge, - AddressMode.Border => SamplerAddressMode.ClampToBorder, - _ => SamplerAddressMode.Repeat - }; - } - - public static CompareOp Vulkan(ComparisonFunc comparisonFunc) - { - return comparisonFunc switch - { - ComparisonFunc.Never => CompareOp.Never, - ComparisonFunc.Less => CompareOp.Less, - ComparisonFunc.Equal => CompareOp.Equal, - ComparisonFunc.LessEqual => CompareOp.LessOrEqual, - ComparisonFunc.Greater => CompareOp.Greater, - ComparisonFunc.NotEqual => CompareOp.NotEqual, - ComparisonFunc.GreaterEqual => CompareOp.GreaterOrEqual, - ComparisonFunc.Always => CompareOp.Always, - _ => CompareOp.Never - }; - } - - public static VkBorderColor Vulkan(BorderColor borderColor) - { - return borderColor switch - { - BorderColor.TransparentBlack => VkBorderColor.FloatTransparentBlack, - BorderColor.OpaqueBlack => VkBorderColor.FloatOpaqueBlack, - BorderColor.OpaqueWhite => VkBorderColor.FloatOpaqueWhite, - _ => VkBorderColor.FloatTransparentBlack - }; - } - - public static DescriptorType Vulkan(ResourceType type) - { - return type switch - { - ResourceType.ConstantBuffer => DescriptorType.UniformBuffer, - - ResourceType.StructuredBuffer or - ResourceType.StructuredBufferReadWrite => DescriptorType.StorageBuffer, - - ResourceType.Texture => DescriptorType.SampledImage, - - ResourceType.TextureReadWrite => DescriptorType.StorageImage, - - ResourceType.Sampler => DescriptorType.Sampler, - - ResourceType.AccelerationStructure => DescriptorType.AccelerationStructureKhr, - - _ => DescriptorType.Sampler - }; - } - - public static PolygonMode Vulkan(FillMode fillMode) - { - return fillMode switch - { - FillMode.Solid => PolygonMode.Fill, - FillMode.Wireframe => PolygonMode.Line, - _ => PolygonMode.Fill - }; - } - - public static CullModeFlags Vulkan(CullMode cullMode) - { - return cullMode switch - { - CullMode.None => CullModeFlags.None, - CullMode.Front => CullModeFlags.FrontBit, - CullMode.Back => CullModeFlags.BackBit, - _ => CullModeFlags.None - }; - } - - public static VkFrontFace Vulkan(FrontFace frontFace) - { - return frontFace switch - { - FrontFace.CounterClockwise => VkFrontFace.CounterClockwise, - FrontFace.Clockwise => VkFrontFace.Clockwise, - _ => VkFrontFace.CounterClockwise - }; - } - - public static VkStencilOp Vulkan(StencilOp stencilOp) - { - return stencilOp switch - { - StencilOp.Keep => VkStencilOp.Keep, - StencilOp.Zero => VkStencilOp.Zero, - StencilOp.Replace => VkStencilOp.Replace, - StencilOp.IncrementAndClamp => VkStencilOp.IncrementAndClamp, - StencilOp.DecrementAndClamp => VkStencilOp.DecrementAndClamp, - StencilOp.Invert => VkStencilOp.Invert, - StencilOp.IncrementAndWrap => VkStencilOp.IncrementAndWrap, - StencilOp.DecrementAndWrap => VkStencilOp.DecrementAndWrap, - _ => VkStencilOp.Keep - }; - } - - public static BlendFactor Vulkan(Blend blend) - { - return blend switch - { - Blend.Zero => BlendFactor.Zero, - Blend.One => BlendFactor.One, - Blend.SrcAlpha => BlendFactor.SrcAlpha, - Blend.InverseSrcAlpha => BlendFactor.OneMinusSrcAlpha, - Blend.DestAlpha => BlendFactor.DstAlpha, - Blend.InverseDestAlpha => BlendFactor.OneMinusDstAlpha, - Blend.SrcColor => BlendFactor.SrcColor, - Blend.InverseSrcColor => BlendFactor.OneMinusSrcColor, - Blend.DestColor => BlendFactor.DstColor, - Blend.InverseDestColor => BlendFactor.OneMinusDstColor, - Blend.BlendFactor => BlendFactor.ConstantColor, - Blend.InverseBlendFactor => BlendFactor.OneMinusConstantColor, - _ => BlendFactor.Zero - }; - } - - public static VkBlendOp Vulkan(BlendOp blendOp) - { - return blendOp switch - { - BlendOp.Add => VkBlendOp.Add, - BlendOp.Subtract => VkBlendOp.Subtract, - BlendOp.ReverseSubtract => VkBlendOp.ReverseSubtract, - BlendOp.Min => VkBlendOp.Min, - BlendOp.Max => VkBlendOp.Max, - _ => VkBlendOp.Add - }; - } - - public static VkColorComponentFlags Vulkan(ColorWriteMask writeMask) - { - VkColorComponentFlags result = VkColorComponentFlags.None; - - if (writeMask.HasFlag(ColorWriteMask.Red)) - { - result |= VkColorComponentFlags.RBit; - } - - if (writeMask.HasFlag(ColorWriteMask.Green)) - { - result |= VkColorComponentFlags.GBit; - } - - if (writeMask.HasFlag(ColorWriteMask.Blue)) - { - result |= VkColorComponentFlags.BBit; - } - - if (writeMask.HasFlag(ColorWriteMask.Alpha)) - { - result |= VkColorComponentFlags.ABit; - } - - return result; - } - - public static Format Vulkan(ElementFormat elementFormat) - { - return elementFormat switch - { - ElementFormat.UByte1 => Format.R8Uint, - ElementFormat.UByte2 => Format.R8G8Uint, - ElementFormat.UByte4 => Format.R8G8B8A8Uint, - ElementFormat.Byte1 => Format.R8Sint, - ElementFormat.Byte2 => Format.R8G8Sint, - ElementFormat.Byte4 => Format.R8G8B8A8Sint, - - ElementFormat.UByte1Normalized => Format.R8Unorm, - ElementFormat.UByte2Normalized => Format.R8G8Unorm, - ElementFormat.UByte4Normalized => Format.R8G8B8A8Unorm, - ElementFormat.Byte1Normalized => Format.R8SNorm, - ElementFormat.Byte2Normalized => Format.R8G8SNorm, - ElementFormat.Byte4Normalized => Format.R8G8B8A8SNorm, - - ElementFormat.UShort1 => Format.R16Uint, - ElementFormat.UShort2 => Format.R16G16Uint, - ElementFormat.UShort4 => Format.R16G16B16A16Uint, - ElementFormat.Short1 => Format.R16Sint, - ElementFormat.Short2 => Format.R16G16Sint, - ElementFormat.Short4 => Format.R16G16B16A16Sint, - - ElementFormat.UShort1Normalized => Format.R16Unorm, - ElementFormat.UShort2Normalized => Format.R16G16Unorm, - ElementFormat.UShort4Normalized => Format.R16G16B16A16Unorm, - ElementFormat.Short1Normalized => Format.R16SNorm, - ElementFormat.Short2Normalized => Format.R16G16SNorm, - ElementFormat.Short4Normalized => Format.R16G16B16A16SNorm, - - ElementFormat.Half1 => Format.R16Sfloat, - ElementFormat.Half2 => Format.R16G16Sfloat, - ElementFormat.Half4 => Format.R16G16B16A16Sfloat, - - ElementFormat.Float1 => Format.R32Sfloat, - ElementFormat.Float2 => Format.R32G32Sfloat, - ElementFormat.Float3 => Format.R32G32B32Sfloat, - ElementFormat.Float4 => Format.R32G32B32A32Sfloat, - - ElementFormat.UInt1 => Format.R32Uint, - ElementFormat.UInt2 => Format.R32G32Uint, - ElementFormat.UInt3 => Format.R32G32B32Uint, - ElementFormat.UInt4 => Format.R32G32B32A32Uint, - ElementFormat.Int1 => Format.R32Sint, - ElementFormat.Int2 => Format.R32G32Sint, - ElementFormat.Int3 => Format.R32G32B32Sint, - ElementFormat.Int4 => Format.R32G32B32A32Sint, - - _ => Format.Undefined - }; - } - - public static VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) - { - return primitiveTopology switch - { - PrimitiveTopology.PointList => VkPrimitiveTopology.PointList, - PrimitiveTopology.LineList => VkPrimitiveTopology.LineList, - PrimitiveTopology.LineStrip => VkPrimitiveTopology.LineStrip, - PrimitiveTopology.TriangleList => VkPrimitiveTopology.TriangleList, - PrimitiveTopology.TriangleStrip => VkPrimitiveTopology.TriangleStrip, - _ => VkPrimitiveTopology.PointList - }; - } - - public static VkQueryType Vulkan(QueryType queryType) - { - return queryType switch - { - QueryType.Occlusion or QueryType.BinaryOcclusion => VkQueryType.Occlusion, - QueryType.Timestamp => VkQueryType.Timestamp, - _ => VkQueryType.Occlusion - }; - } - - public static IndexType Vulkan(IndexFormat indexFormat) - { - return indexFormat switch - { - IndexFormat.UInt16 => IndexType.Uint16, - IndexFormat.UInt32 => IndexType.Uint32, - _ => IndexType.Uint16 - }; - } - - public static TransformMatrixKHR Vulkan(Matrix4x4 matrix4x4) - { - TransformMatrixKHR result; - - float* pResult = (float*)&result; - - pResult[0] = matrix4x4.M11; - pResult[1] = matrix4x4.M21; - pResult[2] = matrix4x4.M31; - pResult[3] = matrix4x4.M41; - - pResult[4] = matrix4x4.M12; - pResult[5] = matrix4x4.M22; - pResult[6] = matrix4x4.M32; - pResult[7] = matrix4x4.M42; - - pResult[8] = matrix4x4.M13; - pResult[9] = matrix4x4.M23; - pResult[10] = matrix4x4.M33; - pResult[11] = matrix4x4.M43; - - return result; - } - - public static GeometryTypeKHR Vulkan(RayTracingGeometryType rayTracingGeometryType) - { - return rayTracingGeometryType switch - { - RayTracingGeometryType.Triangles => GeometryTypeKHR.TrianglesKhr, - RayTracingGeometryType.AABBs => GeometryTypeKHR.AabbsKhr, - _ => GeometryTypeKHR.TrianglesKhr - }; - } - - public static GeometryFlagsKHR Vulkan(RayTracingGeometryFlags rayTracingGeometryFlags) - { - GeometryFlagsKHR result = GeometryFlagsKHR.None; - - if (rayTracingGeometryFlags.HasFlag(RayTracingGeometryFlags.Opaque)) - { - result |= GeometryFlagsKHR.OpaqueBitKhr; - } - - return result; - } - - public static BuildAccelerationStructureFlagsKHR Vulkan(AccelerationStructureBuildFlags accelerationStructureBuildFlags) - { - BuildAccelerationStructureFlagsKHR result = BuildAccelerationStructureFlagsKHR.None; - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate) || accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PerformUpdate)) - { - result |= BuildAccelerationStructureFlagsKHR.AllowUpdateBitKhr; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) - { - result |= BuildAccelerationStructureFlagsKHR.AllowCompactionBitKhr; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) - { - result |= BuildAccelerationStructureFlagsKHR.PreferFastTraceBitKhr; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) - { - result |= BuildAccelerationStructureFlagsKHR.PreferFastBuildBitKhr; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) - { - result |= BuildAccelerationStructureFlagsKHR.LowMemoryBitKhr; - } - - return result; - } - - public static GeometryInstanceFlagsKHR Vulkan(RayTracingInstanceFlags rayTracingInstanceFlags) - { - GeometryInstanceFlagsKHR result = GeometryInstanceFlagsKHR.None; - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleCullDisable)) - { - result |= GeometryInstanceFlagsKHR.TriangleFacingCullDisableBitKhr; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleFrontCounterClockwise)) - { - result |= GeometryInstanceFlagsKHR.TriangleFrontCounterclockwiseBitKhr; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) - { - result |= GeometryInstanceFlagsKHR.ForceOpaqueBitKhr; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNoOpaque)) - { - result |= GeometryInstanceFlagsKHR.ForceNoOpaqueBitKhr; - } - - return result; - } -} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 5243b720..dc2f4c76 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -1,512 +1,100 @@ -using Silk.NET.Core; -using Silk.NET.Core.Contexts; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.EXT; -using Silk.NET.Vulkan.Extensions.KHR; +using Silk.NET.Vulkan; namespace Zenith.NET.Vulkan; -internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(Backend.Vulkan, useValidationLayer) +internal class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Vulkan, useValidationLayer) { - private static readonly string[] InstanceLayers = - [ - "VK_LAYER_KHRONOS_validation" - ]; - - private static readonly string[] InstanceExtensions = - [ - ExtDebugUtils.ExtensionName, - KhrSurface.ExtensionName, - KhrWin32Surface.ExtensionName, - KhrWaylandSurface.ExtensionName, - KhrXlibSurface.ExtensionName, - KhrAndroidSurface.ExtensionName, - ExtMetalSurface.ExtensionName - ]; - - private static readonly string[] DeviceExtensions = - [ - KhrSwapchain.ExtensionName, - KhrExternalMemoryWin32.ExtensionName, - KhrRayQuery.ExtensionName, - KhrAccelerationStructure.ExtensionName, - KhrDeferredHostOperations.ExtensionName, - ExtMeshShader.ExtensionName - ]; - - public Instance Instance; - - public PhysicalDevice PhysicalDevice; - - public Device Device; - - public Queue GraphicsQueue; - - public Queue ComputeQueue; - - public Queue CopyQueue; - public Vk Vk { get; } = Vk.GetApi(); - public ExtDebugUtils? DebugUtils { get; private set; } - - public KhrSurface? Surface { get; private set; } - - public KhrWin32Surface? Win32Surface { get; private set; } - - public KhrWaylandSurface? WaylandSurface { get; private set; } - - public KhrXlibSurface? XlibSurface { get; private set; } - - public KhrAndroidSurface? AndroidSurface { get; private set; } - - public ExtMetalSurface? MetalSurface { get; private set; } - - public uint[] QueueFamilyIndices { get; private set; } = []; - - public KhrSwapchain? Swapchain { get; private set; } - - public KhrExternalMemoryWin32? ExternalMemoryWin32 { get; private set; } - - public KhrAccelerationStructure? AccelerationStructure { get; private set; } - - public KhrDeferredHostOperations? DeferredHostOperations { get; private set; } - - public ExtMeshShader? MeshShader { get; private set; } - - public (SharingMode SharingMode, uint QueueFamilyIndexCount, nint PQueueFamilyIndices) GetSharingModeInfo(ZenithMarshal.Scope scope) + public override nint GetNativeObject(NativeObjectType type) { - if (QueueFamilyIndices.Length is 1) - { - return (SharingMode.Exclusive, 0, 0); - } - else - { - return (SharingMode.Concurrent, (uint)QueueFamilyIndices.Length, ZenithMarshal.AllocateAndFill(scope, QueueFamilyIndices)); - } - } - - public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryPropertyFlags flags) - { - PhysicalDeviceMemoryProperties properties; - Vk.GetPhysicalDeviceMemoryProperties(PhysicalDevice, &properties); - - uint index = 0; - foreach (MemoryType memoryType in properties.MemoryTypes.AsSpan()) - { - if ((memoryTypeBits & (1 << (int)index)) is not 0 && memoryType.PropertyFlags.HasFlag(flags)) - { - break; - } - - index++; - } - - return index; + return 0; } protected override void Initialize(bool useValidationLayer, out Capabilities capabilities, - out CommandQueue graphics, - out CommandQueue compute, - out CommandQueue copy, + out CommandQueue graphicsQueue, + out CommandQueue computeQueue, + out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - Version32 apiVersion = new(1, 4, 0); - - using ZenithMarshal.Scope scope = new(); - - // Create instance - { - uint extensionCount = 0; - Vk.EnumerateInstanceExtensionProperties((byte*)null, &extensionCount, (ExtensionProperties*)null).Success(); - - ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); - Vk.EnumerateInstanceExtensionProperties((byte*)null, &extensionCount, extensions).Success(); - - string[] enabledExtensions = [.. new ReadOnlySpan(extensions, (int)extensionCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.ExtensionName, StringEncoding.UTF8))]; - enabledExtensions = [.. enabledExtensions.Intersect(InstanceExtensions)]; - - ApplicationInfo applicationInfo = new() - { - SType = StructureType.ApplicationInfo, - PApplicationName = (byte*)ZenithMarshal.StringToPointer(scope, AppDomain.CurrentDomain.FriendlyName, StringEncoding.UTF8), - ApplicationVersion = new Version32(1, 0, 0), - PEngineName = (byte*)ZenithMarshal.StringToPointer(scope, "Zenith.NET", StringEncoding.UTF8), - EngineVersion = new Version32(1, 0, 0), - ApiVersion = apiVersion - }; - - InstanceCreateInfo createInfo = new() - { - SType = StructureType.InstanceCreateInfo, - PApplicationInfo = &applicationInfo, - EnabledExtensionCount = (uint)enabledExtensions.Length, - PpEnabledExtensionNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledExtensions, StringEncoding.UTF8) - }; - - if (useValidationLayer) - { - uint layerCount = 0; - Vk.EnumerateInstanceLayerProperties(&layerCount, (LayerProperties*)null).Success(); - - LayerProperties* layers = (LayerProperties*)ZenithMarshal.Allocate(scope, layerCount); - Vk.EnumerateInstanceLayerProperties(&layerCount, layers).Success(); - - string[] enabledLayers = [.. new ReadOnlySpan(layers, (int)layerCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.LayerName, StringEncoding.UTF8))]; - enabledLayers = [.. enabledLayers.Intersect(InstanceLayers)]; - - createInfo.EnabledLayerCount = (uint)enabledLayers.Length; - createInfo.PpEnabledLayerNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledLayers, StringEncoding.UTF8); - } - - Vk.CreateInstance(&createInfo, null, out Instance).Success(); - - LamdaNativeContext context = new(proc => Vk.GetInstanceProcAddr(Instance, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); - - DebugUtils = enabledExtensions.Contains(ExtDebugUtils.ExtensionName) ? new(context) : null; - Surface = enabledExtensions.Contains(KhrSurface.ExtensionName) ? new(context) : null; - Win32Surface = enabledExtensions.Contains(KhrWin32Surface.ExtensionName) ? new(context) : null; - WaylandSurface = enabledExtensions.Contains(KhrWaylandSurface.ExtensionName) ? new(context) : null; - XlibSurface = enabledExtensions.Contains(KhrXlibSurface.ExtensionName) ? new(context) : null; - AndroidSurface = enabledExtensions.Contains(KhrAndroidSurface.ExtensionName) ? new(context) : null; - MetalSurface = enabledExtensions.Contains(ExtMetalSurface.ExtensionName) ? new(context) : null; - } - - // Select physical device and create logical device - { - uint physicalDeviceCount = 0; - Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, (PhysicalDevice*)null).Success(); - - PhysicalDevice* physicalDevices = (PhysicalDevice*)ZenithMarshal.Allocate(scope, physicalDeviceCount); - Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, physicalDevices).Success(); - - ulong bestScore = 0; - foreach (PhysicalDevice physicalDevice in new ReadOnlySpan(physicalDevices, (int)physicalDeviceCount)) - { - PhysicalDeviceProperties properties; - Vk.GetPhysicalDeviceProperties(physicalDevice, &properties); - - PhysicalDeviceFeatures features; - Vk.GetPhysicalDeviceFeatures(physicalDevice, &features); - - if (properties.ApiVersion < apiVersion) - { - continue; - } - - ulong score = 0; - - if (properties.DeviceType == PhysicalDeviceType.DiscreteGpu) - { - score += 100000; - } - else if (properties.DeviceType == PhysicalDeviceType.IntegratedGpu) - { - score += 10000; - } - else if (properties.DeviceType == PhysicalDeviceType.VirtualGpu) - { - score += 1000; - } - - score += properties.Limits.MaxImageDimension2D / 1000; - score += properties.Limits.MaxMemoryAllocationCount / 1000; - score += properties.Limits.MaxComputeSharedMemorySize / 1024; - score += properties.Limits.MaxComputeWorkGroupInvocations / 64; - score += properties.Limits.MaxComputeWorkGroupCount[0] / 1024; - score += properties.Limits.MaxComputeWorkGroupCount[1] / 1024; - score += properties.Limits.MaxComputeWorkGroupCount[2] / 1024; - score += properties.Limits.MaxComputeWorkGroupSize[0] / 64; - score += properties.Limits.MaxComputeWorkGroupSize[1] / 64; - score += properties.Limits.MaxComputeWorkGroupSize[2] / 64; - - if (score > bestScore) - { - bestScore = score; - - PhysicalDevice = physicalDevice; - } - } - - if (PhysicalDevice.Handle is 0) - { - throw new NotSupportedException("No suitable Vulkan physical device found."); - } - - uint graphicsQueueFamilyIndex = 0; - uint graphicsQueueFamilyCount = 0; - - uint computeQueueFamilyIndex = 0; - uint computeQueueFamilyCount = 0; - - uint copyQueueFamilyIndex = 0; - uint copyQueueFamilyCount = 0; - - uint queueFamilyCount = 0; - Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, (QueueFamilyProperties*)null); - - QueueFamilyProperties* queueFamilies = (QueueFamilyProperties*)ZenithMarshal.Allocate(scope, queueFamilyCount); - Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, queueFamilies); - - uint index = 0; - foreach (QueueFamilyProperties queueFamilyProperties in new ReadOnlySpan(queueFamilies, (int)queueFamilyCount)) - { - if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.GraphicsBit) && queueFamilyProperties.QueueCount > graphicsQueueFamilyCount) - { - graphicsQueueFamilyIndex = index; - graphicsQueueFamilyCount = queueFamilyProperties.QueueCount; - } - else if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.ComputeBit) && queueFamilyProperties.QueueCount > computeQueueFamilyCount) - { - computeQueueFamilyIndex = index; - computeQueueFamilyCount = queueFamilyProperties.QueueCount; - } - else if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.TransferBit) && queueFamilyProperties.QueueCount > copyQueueFamilyCount) - { - copyQueueFamilyIndex = index; - copyQueueFamilyCount = queueFamilyProperties.QueueCount; - } - - index++; - } - - HashSet queueFamilyIndices = [graphicsQueueFamilyIndex, computeQueueFamilyIndex, copyQueueFamilyIndex]; - - uint queueCreateInfoCount; - DeviceQueueCreateInfo* queueCreateInfos; - Func<(Queue GraphicsQueue, Queue ComputeQueue, Queue CopyQueue, uint[] QueueFamilyIndices)> getQueues; - if (queueFamilyIndices.Count is 3) - { - queueCreateInfoCount = 3; - - float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); - queuePriorities[0] = 1.0f; - - queueCreateInfos = (DeviceQueueCreateInfo*)ZenithMarshal.Allocate(scope, 3); - - queueCreateInfos[0] = new() - { - SType = StructureType.DeviceQueueCreateInfo, - QueueFamilyIndex = graphicsQueueFamilyIndex, - QueueCount = 1, - PQueuePriorities = queuePriorities - }; - - queueCreateInfos[1] = new() - { - SType = StructureType.DeviceQueueCreateInfo, - QueueFamilyIndex = computeQueueFamilyIndex, - QueueCount = 1, - PQueuePriorities = queuePriorities - }; - - queueCreateInfos[2] = new() - { - SType = StructureType.DeviceQueueCreateInfo, - QueueFamilyIndex = copyQueueFamilyIndex, - QueueCount = 1, - PQueuePriorities = queuePriorities - }; - - getQueues = () => - { - Queue graphicsQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - - Queue computeQueue = default; - Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); - - Queue copyQueue = default; - Vk.GetDeviceQueue(Device, copyQueueFamilyIndex, 0, ©Queue); - - return (graphicsQueue, computeQueue, copyQueue, [graphicsQueueFamilyIndex, computeQueueFamilyIndex, copyQueueFamilyIndex]); - }; - } - else if (graphicsQueueFamilyCount >= 3) - { - queueCreateInfoCount = 1; - - float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 3); - queuePriorities[0] = 1.0f; - queuePriorities[1] = 1.0f; - queuePriorities[2] = 1.0f; - - queueCreateInfos = (DeviceQueueCreateInfo*)ZenithMarshal.Allocate(scope, 1); - queueCreateInfos[0] = new() - { - SType = StructureType.DeviceQueueCreateInfo, - QueueFamilyIndex = graphicsQueueFamilyIndex, - QueueCount = 3, - PQueuePriorities = queuePriorities - }; - - getQueues = () => - { - Queue graphicsQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - - Queue computeQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); - - Queue copyQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, ©Queue); - - return (graphicsQueue, computeQueue, copyQueue, [graphicsQueueFamilyIndex]); - }; - } - else - { - queueCreateInfoCount = 1; - - float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); - queuePriorities[0] = 1.0f; - - queueCreateInfos = (DeviceQueueCreateInfo*)ZenithMarshal.Allocate(scope, 1); - queueCreateInfos[0] = new() - { - SType = StructureType.DeviceQueueCreateInfo, - QueueFamilyIndex = graphicsQueueFamilyIndex, - QueueCount = 1, - PQueuePriorities = queuePriorities - }; - - getQueues = () => - { - Queue graphicsQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - - return (graphicsQueue, graphicsQueue, graphicsQueue, [graphicsQueueFamilyIndex]); - }; - } - - uint extensionCount = 0; - Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, (byte*)null, &extensionCount, (ExtensionProperties*)null).Success(); - - ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); - Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, (byte*)null, &extensionCount, extensions).Success(); - - string[] enabledExtensions = [.. new ReadOnlySpan(extensions, (int)extensionCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.ExtensionName, StringEncoding.UTF8))]; - enabledExtensions = [.. enabledExtensions.Intersect(DeviceExtensions)]; - - DeviceCreateInfo createInfo = new() - { - SType = StructureType.DeviceCreateInfo, - QueueCreateInfoCount = queueCreateInfoCount, - PQueueCreateInfos = queueCreateInfos, - EnabledExtensionCount = (uint)enabledExtensions.Length, - PpEnabledExtensionNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledExtensions, StringEncoding.UTF8) - }; - - createInfo.AddNext(out PhysicalDeviceFeatures2 features2); - createInfo.AddNext(out PhysicalDeviceVulkan14Features _); - createInfo.AddNext(out PhysicalDeviceVulkan13Features _); - createInfo.AddNext(out PhysicalDeviceVulkan12Features _); - createInfo.AddNext(out PhysicalDeviceVulkan11Features _); - - if (enabledExtensions.Contains(KhrRayQuery.ExtensionName)) - { - createInfo.AddNext(out PhysicalDeviceRayQueryFeaturesKHR _); - createInfo.AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR _); - } - - if (enabledExtensions.Contains(ExtMeshShader.ExtensionName)) - { - createInfo.AddNext(out PhysicalDeviceMeshShaderFeaturesEXT _); - createInfo.AddNext(out PhysicalDeviceFragmentShadingRateFeaturesKHR _); - } - - Vk.GetPhysicalDeviceFeatures2(PhysicalDevice, &features2); - - Vk.CreateDevice(PhysicalDevice, &createInfo, null, out Device).Success(); - - (GraphicsQueue, ComputeQueue, CopyQueue, QueueFamilyIndices) = getQueues(); - - LamdaNativeContext context = new(proc => Vk.GetDeviceProcAddr(Device, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); + throw new NotImplementedException(); + } - Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; - ExternalMemoryWin32 = enabledExtensions.Contains(KhrExternalMemoryWin32.ExtensionName) ? new(context) : null; - AccelerationStructure = enabledExtensions.Contains(KhrAccelerationStructure.ExtensionName) ? new(context) : null; - DeferredHostOperations = enabledExtensions.Contains(KhrDeferredHostOperations.ExtensionName) ? new(context) : null; - MeshShader = enabledExtensions.Contains(ExtMeshShader.ExtensionName) ? new(context) : null; - } + protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) + { + throw new NotImplementedException(); + } - capabilities = new VKCapabilities(this); - graphics = new VKCommandQueue(this, CommandQueueType.Graphics, GraphicsQueue, QueueFamilyIndices[0]); - compute = new VKCommandQueue(this, CommandQueueType.Compute, ComputeQueue, QueueFamilyIndices.Length > 1 ? QueueFamilyIndices[1] : QueueFamilyIndices[0]); - copy = new VKCommandQueue(this, CommandQueueType.Copy, CopyQueue, QueueFamilyIndices.Length > 2 ? QueueFamilyIndices[2] : QueueFamilyIndices[0]); - validationLayer = useValidationLayer ? new VKValidationLayer(this) : null; + protected override Heap CreateHeapImpl(HeapDesc desc) + { + throw new NotImplementedException(); } - protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - return new VKSwapChain(this, desc); + throw new NotImplementedException(); } - protected override Shader CreateShaderImpl(ShaderDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { - return new VKShader(this, desc); + throw new NotImplementedException(); } protected override Buffer CreateBufferImpl(BufferDesc desc) { - return new VKBuffer(this, desc); + throw new NotImplementedException(); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) { - return new VKBufferView(this, desc); + throw new NotImplementedException(); } protected override Texture CreateTextureImpl(TextureDesc desc) { - return new VKTexture(this, desc); + throw new NotImplementedException(); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { - return new VKTextureView(this, desc); + throw new NotImplementedException(); } protected override Sampler CreateSamplerImpl(SamplerDesc desc) { - return new VKSampler(this, desc); + throw new NotImplementedException(); } - protected override ResourceTable CreateResourceTableImpl(ResourceTableDesc desc) + protected override Shader CreateShaderImpl(ShaderDesc desc) { - return new VKResourceTable(this, desc); + throw new NotImplementedException(); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) { - return new VKGraphicsPipeline(this, desc); + throw new NotImplementedException(); } protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) { - return new VKComputePipeline(this, desc); + throw new NotImplementedException(); } protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) { - return new VKMeshShadingPipeline(this, desc); + throw new NotImplementedException(); } protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { - return new VKQueryHeap(this, desc); + throw new NotImplementedException(); } protected override void Destroy() { - Vk.DeviceWaitIdle(Device).Success(); - base.Destroy(); - Vk.DestroyDevice(Device, null); - Vk.DestroyInstance(Instance, null); - Vk.Dispose(); } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs deleted file mode 100644 index 98974aa2..00000000 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ /dev/null @@ -1,251 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKGraphicsPipeline : GraphicsPipeline -{ - public DescriptorSetLayout DescriptorSetLayout; - - public PipelineLayout PipelineLayout; - - public VkPipeline Pipeline; - - public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - GraphicsPipelineCreateInfo createInfo = new() - { - SType = StructureType.GraphicsPipelineCreateInfo, - StageCount = 2, - PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, - [ - desc.Vertex.Vulkan().GetPipelineShaderStageCreateInfo(scope), - desc.Pixel.Vulkan().GetPipelineShaderStageCreateInfo(scope) - ]) - }; - - // RenderStates - Output - { - BlendStateRenderTarget[] blendStateRenderTargets = - [ - desc.RenderStates.BlendState.RenderTarget0, - desc.RenderStates.BlendState.RenderTarget1, - desc.RenderStates.BlendState.RenderTarget2, - desc.RenderStates.BlendState.RenderTarget3, - desc.RenderStates.BlendState.RenderTarget4, - desc.RenderStates.BlendState.RenderTarget5, - desc.RenderStates.BlendState.RenderTarget6, - desc.RenderStates.BlendState.RenderTarget7 - ]; - - uint colorAttachmentCount = (uint)desc.Output.ColorAttachments.Length; - - PipelineColorBlendAttachmentState* attachments = (PipelineColorBlendAttachmentState*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - Format* colorAttachmentFormats = (Format*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - for (uint i = 0; i < colorAttachmentCount; i++) - { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; - - attachments[i] = new() - { - BlendEnable = target.BlendEnable, - SrcColorBlendFactor = VKFormats.Vulkan(target.SrcBlend), - DstColorBlendFactor = VKFormats.Vulkan(target.DestBlend), - ColorBlendOp = VKFormats.Vulkan(target.BlendOp), - SrcAlphaBlendFactor = VKFormats.Vulkan(target.SrcBlendAlpha), - DstAlphaBlendFactor = VKFormats.Vulkan(target.DestBlendAlpha), - AlphaBlendOp = VKFormats.Vulkan(target.BlendOpAlpha), - ColorWriteMask = VKFormats.Vulkan(target.Flags) - }; - - colorAttachmentFormats[i] = VKFormats.Vulkan(desc.Output.ColorAttachments[i]); - } - - Format depthStencilAttachmentFormat = VKFormats.Vulkan(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown); - - PipelineRasterizationStateCreateInfo rasterizationState = new() - { - SType = StructureType.PipelineRasterizationStateCreateInfo, - DepthClampEnable = desc.RenderStates.RasterizerState.DepthClipEnable, - PolygonMode = VKFormats.Vulkan(desc.RenderStates.RasterizerState.FillMode), - CullMode = VKFormats.Vulkan(desc.RenderStates.RasterizerState.CullMode), - FrontFace = VKFormats.Vulkan(desc.RenderStates.RasterizerState.FrontFace), - DepthBiasEnable = true, - DepthBiasConstantFactor = desc.RenderStates.RasterizerState.DepthBias, - DepthBiasClamp = desc.RenderStates.RasterizerState.DepthBiasClamp, - DepthBiasSlopeFactor = desc.RenderStates.RasterizerState.SlopeScaledDepthBias, - LineWidth = 1.0f - }; - PipelineDepthStencilStateCreateInfo depthStencilState = new() - { - SType = StructureType.PipelineDepthStencilStateCreateInfo, - DepthTestEnable = desc.RenderStates.DepthStencilState.DepthEnable, - DepthWriteEnable = desc.RenderStates.DepthStencilState.DepthWriteEnable, - DepthCompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.DepthFunc), - StencilTestEnable = desc.RenderStates.DepthStencilState.StencilEnable, - Front = new() - { - FailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilFailOp), - PassOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilPassOp), - DepthFailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilDepthFailOp), - CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilFunc), - CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = 0 - }, - Back = new() - { - FailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilFailOp), - PassOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilPassOp), - DepthFailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilDepthFailOp), - CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilFunc), - CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = 0 - }, - MinDepthBounds = 0.0f, - MaxDepthBounds = 1.0f - }; - PipelineColorBlendStateCreateInfo colorBlendState = new() - { - SType = StructureType.PipelineColorBlendStateCreateInfo, - AttachmentCount = colorAttachmentCount, - PAttachments = attachments - }; - PipelineViewportStateCreateInfo viewportState = new() - { - SType = StructureType.PipelineViewportStateCreateInfo, - ViewportCount = Math.Max(colorAttachmentCount, 1), - ScissorCount = Math.Max(colorAttachmentCount, 1) - }; - PipelineMultisampleStateCreateInfo multisampleState = new() - { - SType = StructureType.PipelineMultisampleStateCreateInfo, - RasterizationSamples = VKFormats.Vulkan(desc.Output.SampleCount), - AlphaToCoverageEnable = desc.RenderStates.BlendState.AlphaToCoverageEnable - }; - PipelineRenderingCreateInfo rendering = new() - { - SType = StructureType.PipelineRenderingCreateInfo, - ColorAttachmentCount = colorAttachmentCount, - PColorAttachmentFormats = colorAttachmentFormats, - DepthAttachmentFormat = ZenithHelper.HasDepth(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown) ? depthStencilAttachmentFormat : Format.Undefined, - StencilAttachmentFormat = ZenithHelper.HasStencil(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown) ? depthStencilAttachmentFormat : Format.Undefined - }; - - createInfo.PRasterizationState = &rasterizationState; - createInfo.PDepthStencilState = &depthStencilState; - createInfo.PColorBlendState = &colorBlendState; - createInfo.PViewportState = &viewportState; - createInfo.PMultisampleState = &multisampleState; - createInfo.PNext = &rendering; - } - - // ResourceBindings - { - PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() - { - SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout = desc.ResourceBindings.DescriptorSetLayout(context)]) - }; - - context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - - createInfo.Layout = PipelineLayout; - } - - // InputLayouts - { - uint vertexBindingDescriptionCount = (uint)desc.InputLayouts.Length; - uint vertexAttributeDescriptionCount = (uint)desc.InputLayouts.Sum(static item => item.Elements.Length); - - VertexInputBindingDescription* vertexBindingDescriptions = (VertexInputBindingDescription*)ZenithMarshal.Allocate(scope, vertexBindingDescriptionCount); - VertexInputAttributeDescription* vertexAttributeDescriptions = (VertexInputAttributeDescription*)ZenithMarshal.Allocate(scope, vertexAttributeDescriptionCount); - - uint binding = 0; - uint attribute = 0; - foreach (InputLayout inputLayout in desc.InputLayouts) - { - vertexBindingDescriptions[binding] = new() - { - Binding = binding, - Stride = inputLayout.StrideInBytes - }; - - foreach (InputElement element in inputLayout.Elements) - { - vertexAttributeDescriptions[attribute] = new() - { - Location = attribute, - Binding = binding, - Format = VKFormats.Vulkan(element.Format), - Offset = element.OffsetInBytes - }; - - attribute++; - } - - binding++; - } - - PipelineVertexInputStateCreateInfo vertexInputState = new() - { - SType = StructureType.PipelineVertexInputStateCreateInfo, - VertexBindingDescriptionCount = vertexBindingDescriptionCount, - PVertexBindingDescriptions = vertexBindingDescriptions, - VertexAttributeDescriptionCount = vertexAttributeDescriptionCount, - PVertexAttributeDescriptions = vertexAttributeDescriptions - }; - - createInfo.PVertexInputState = &vertexInputState; - } - - // PrimitiveTopology - { - PipelineInputAssemblyStateCreateInfo inputAssemblyState = new() - { - SType = StructureType.PipelineInputAssemblyStateCreateInfo, - Topology = VKFormats.Vulkan(desc.PrimitiveTopology) - }; - - createInfo.PInputAssemblyState = &inputAssemblyState; - } - - PipelineDynamicStateCreateInfo dynamicState = new() - { - SType = StructureType.PipelineDynamicStateCreateInfo, - DynamicStateCount = 4, - PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.StencilReference, DynamicState.BlendConstants]) - }; - - createInfo.PDynamicState = &dynamicState; - - context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, null, out Pipeline).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Pipeline, - ObjectHandle = Pipeline.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); - Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); - Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs deleted file mode 100644 index bb9c8859..00000000 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKMeshShadingPipeline : MeshShadingPipeline -{ - public DescriptorSetLayout DescriptorSetLayout; - - public PipelineLayout PipelineLayout; - - public VkPipeline Pipeline; - - public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - GraphicsPipelineCreateInfo createInfo = new() - { - SType = StructureType.GraphicsPipelineCreateInfo - }; - - // RenderStates - Output - { - BlendStateRenderTarget[] blendStateRenderTargets = - [ - desc.RenderStates.BlendState.RenderTarget0, - desc.RenderStates.BlendState.RenderTarget1, - desc.RenderStates.BlendState.RenderTarget2, - desc.RenderStates.BlendState.RenderTarget3, - desc.RenderStates.BlendState.RenderTarget4, - desc.RenderStates.BlendState.RenderTarget5, - desc.RenderStates.BlendState.RenderTarget6, - desc.RenderStates.BlendState.RenderTarget7 - ]; - - uint colorAttachmentCount = (uint)desc.Output.ColorAttachments.Length; - - PipelineColorBlendAttachmentState* attachments = (PipelineColorBlendAttachmentState*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - Format* colorAttachmentFormats = (Format*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - for (uint i = 0; i < colorAttachmentCount; i++) - { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; - - attachments[i] = new() - { - BlendEnable = target.BlendEnable, - SrcColorBlendFactor = VKFormats.Vulkan(target.SrcBlend), - DstColorBlendFactor = VKFormats.Vulkan(target.DestBlend), - ColorBlendOp = VKFormats.Vulkan(target.BlendOp), - SrcAlphaBlendFactor = VKFormats.Vulkan(target.SrcBlendAlpha), - DstAlphaBlendFactor = VKFormats.Vulkan(target.DestBlendAlpha), - AlphaBlendOp = VKFormats.Vulkan(target.BlendOpAlpha), - ColorWriteMask = VKFormats.Vulkan(target.Flags) - }; - - colorAttachmentFormats[i] = VKFormats.Vulkan(desc.Output.ColorAttachments[i]); - } - - Format depthStencilAttachmentFormat = VKFormats.Vulkan(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown); - - PipelineRasterizationStateCreateInfo rasterizationState = new() - { - SType = StructureType.PipelineRasterizationStateCreateInfo, - DepthClampEnable = desc.RenderStates.RasterizerState.DepthClipEnable, - PolygonMode = VKFormats.Vulkan(desc.RenderStates.RasterizerState.FillMode), - CullMode = VKFormats.Vulkan(desc.RenderStates.RasterizerState.CullMode), - FrontFace = VKFormats.Vulkan(desc.RenderStates.RasterizerState.FrontFace), - DepthBiasEnable = true, - DepthBiasConstantFactor = desc.RenderStates.RasterizerState.DepthBias, - DepthBiasClamp = desc.RenderStates.RasterizerState.DepthBiasClamp, - DepthBiasSlopeFactor = desc.RenderStates.RasterizerState.SlopeScaledDepthBias, - LineWidth = 1.0f - }; - PipelineDepthStencilStateCreateInfo depthStencilState = new() - { - SType = StructureType.PipelineDepthStencilStateCreateInfo, - DepthTestEnable = desc.RenderStates.DepthStencilState.DepthEnable, - DepthWriteEnable = desc.RenderStates.DepthStencilState.DepthWriteEnable, - DepthCompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.DepthFunc), - StencilTestEnable = desc.RenderStates.DepthStencilState.StencilEnable, - Front = new() - { - FailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilFailOp), - PassOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilPassOp), - DepthFailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilDepthFailOp), - CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.FrontFace.StencilFunc), - CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = 0 - }, - Back = new() - { - FailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilFailOp), - PassOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilPassOp), - DepthFailOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilDepthFailOp), - CompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.BackFace.StencilFunc), - CompareMask = desc.RenderStates.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderStates.DepthStencilState.StencilWriteMask, - Reference = 0 - }, - MinDepthBounds = 0.0f, - MaxDepthBounds = 1.0f - }; - PipelineColorBlendStateCreateInfo colorBlendState = new() - { - SType = StructureType.PipelineColorBlendStateCreateInfo, - AttachmentCount = colorAttachmentCount, - PAttachments = attachments - }; - PipelineViewportStateCreateInfo viewportState = new() - { - SType = StructureType.PipelineViewportStateCreateInfo, - ViewportCount = Math.Max(colorAttachmentCount, 1), - ScissorCount = Math.Max(colorAttachmentCount, 1) - }; - PipelineMultisampleStateCreateInfo multisampleState = new() - { - SType = StructureType.PipelineMultisampleStateCreateInfo, - RasterizationSamples = VKFormats.Vulkan(desc.Output.SampleCount), - AlphaToCoverageEnable = desc.RenderStates.BlendState.AlphaToCoverageEnable - }; - PipelineRenderingCreateInfo rendering = new() - { - SType = StructureType.PipelineRenderingCreateInfo, - ColorAttachmentCount = colorAttachmentCount, - PColorAttachmentFormats = colorAttachmentFormats, - DepthAttachmentFormat = ZenithHelper.HasDepth(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown) ? depthStencilAttachmentFormat : Format.Undefined, - StencilAttachmentFormat = ZenithHelper.HasStencil(desc.Output.DepthStencilAttachment ?? PixelFormat.Unknown) ? depthStencilAttachmentFormat : Format.Undefined - }; - - createInfo.PRasterizationState = &rasterizationState; - createInfo.PDepthStencilState = &depthStencilState; - createInfo.PColorBlendState = &colorBlendState; - createInfo.PViewportState = &viewportState; - createInfo.PMultisampleState = &multisampleState; - createInfo.PNext = &rendering; - } - - // Amplification - Mesh - Pixel - { - List pipelineShaderStageCreateInfos = - [ - desc.Mesh.Vulkan().GetPipelineShaderStageCreateInfo(scope), - desc.Pixel.Vulkan().GetPipelineShaderStageCreateInfo(scope) - ]; - - if (desc.Amplification is not null) - { - pipelineShaderStageCreateInfos.Add(desc.Amplification.Vulkan().GetPipelineShaderStageCreateInfo(scope)); - } - - PipelineShaderStageCreateInfo* stages = (PipelineShaderStageCreateInfo*)ZenithMarshal.Allocate(scope, (uint)pipelineShaderStageCreateInfos.Count); - for (int i = 0; i < pipelineShaderStageCreateInfos.Count; i++) - { - stages[i] = pipelineShaderStageCreateInfos[i]; - } - - createInfo.StageCount = (uint)pipelineShaderStageCreateInfos.Count; - createInfo.PStages = stages; - } - - // ResourceBindings - { - PipelineLayoutCreateInfo pipelineLayoutCreateInfo = new() - { - SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = 1, - PSetLayouts = (DescriptorSetLayout*)ZenithMarshal.AllocateAndFill(scope, [DescriptorSetLayout = desc.ResourceBindings.DescriptorSetLayout(context)]) - }; - - context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - - createInfo.Layout = PipelineLayout; - } - - // PrimitiveTopology - { - PipelineInputAssemblyStateCreateInfo inputAssemblyState = new() - { - SType = StructureType.PipelineInputAssemblyStateCreateInfo, - Topology = VKFormats.Vulkan(desc.PrimitiveTopology) - }; - - createInfo.PInputAssemblyState = &inputAssemblyState; - } - - PipelineDynamicStateCreateInfo dynamicState = new() - { - SType = StructureType.PipelineDynamicStateCreateInfo, - DynamicStateCount = 4, - PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.StencilReference, DynamicState.BlendConstants]) - }; - - createInfo.PDynamicState = &dynamicState; - - context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, null, out Pipeline).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Pipeline, - ObjectHandle = Pipeline.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); - Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); - Context.Vk.DestroyDescriptorSetLayout(Context.Device, DescriptorSetLayout, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKQueryHeap.cs b/sources/Zenith.NET.Vulkan/VKQueryHeap.cs deleted file mode 100644 index 792b5fc7..00000000 --- a/sources/Zenith.NET.Vulkan/VKQueryHeap.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKQueryHeap : QueryHeap -{ - public QueryPool QueryPool; - - public VKQueryHeap(VKGraphicsContext context, QueryHeapDesc desc) : base(context, desc) - { - QueryPoolCreateInfo createInfo = new() - { - SType = StructureType.QueryPoolCreateInfo, - QueryType = VKFormats.Vulkan(desc.Type), - QueryCount = desc.Count - }; - - context.Vk.CreateQueryPool(context.Device, &createInfo, null, out QueryPool).Success(); - - context.Vk.ResetQueryPool(context.Device, QueryPool, 0, desc.Count); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void GetResultsImpl(Span results, uint startIndex) - { - fixed (ulong* pResults = results) - { - Context.Vk.GetQueryPoolResults(Context.Device, - QueryPool, - startIndex, - (uint)results.Length, - (uint)(sizeof(ulong) * results.Length), - pResults, - sizeof(ulong), - QueryResultFlags.Result64Bit).Success(); - } - } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.QueryPool, - ObjectHandle = QueryPool.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroyQueryPool(Context.Device, QueryPool, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKResourceTable.cs b/sources/Zenith.NET.Vulkan/VKResourceTable.cs deleted file mode 100644 index eb7b0f7e..00000000 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ /dev/null @@ -1,169 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKResourceTable : ResourceTable -{ - private readonly ZenithMarshal.Scope scope = new(); - - public WriteDescriptorSet* Sets; - - public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) - { - WriteDescriptorSet[] sets = new WriteDescriptorSet[desc.Bindings.Length]; - - for (int i = 0; i < sets.Length; i++) - { - ResourceBinding binding = desc.Bindings[i]; - - sets[i] = new() - { - SType = StructureType.WriteDescriptorSet, - DstBinding = (uint)i, - DescriptorCount = binding.Count, - DescriptorType = VKFormats.Vulkan(binding.Type) - }; - - switch (binding.Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - sets[i].PBufferInfo = (DescriptorBufferInfo*)ZenithMarshal.Allocate(scope, binding.Count); - break; - - case ResourceType.Texture: - case ResourceType.TextureReadWrite: - case ResourceType.Sampler: - sets[i].PImageInfo = (DescriptorImageInfo*)ZenithMarshal.Allocate(scope, binding.Count); - break; - - case ResourceType.AccelerationStructure: - sets[i].PNext = (WriteDescriptorSetAccelerationStructureKHR*)ZenithMarshal.AllocateAndFill(scope, - [ - new WriteDescriptorSetAccelerationStructureKHR() - { - SType = StructureType.WriteDescriptorSetAccelerationStructureKhr, - AccelerationStructureCount = binding.Count, - PAccelerationStructures = (AccelerationStructureKHR*)ZenithMarshal.Allocate(scope, binding.Count) - } - ]); - break; - } - } - - Sets = (WriteDescriptorSet*)ZenithMarshal.AllocateAndFill(scope, sets); - - SrvTextureViews = new VKTextureView?[desc.Bindings.Sum(static item => item.Count)]; - UavTextureViews = new VKTextureView?[desc.Bindings.Sum(static item => item.Count)]; - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKTextureView?[] SrvTextureViews { get; } - - public VKTextureView?[] UavTextureViews { get; } - - protected override void WriteImpl(uint binding, IBindableResource[] resources) - { - ref WriteDescriptorSet set = ref Sets[binding]; - - switch (Desc.Bindings[binding].Type) - { - case ResourceType.ConstantBuffer: - case ResourceType.StructuredBuffer: - case ResourceType.StructuredBufferReadWrite: - for (int i = 0; i < resources.Length; i++) - { - IBindableResource resource = resources[i]; - - if (resource is Buffer buffer) - { - set.PBufferInfo[i] = buffer.Vulkan().View.BufferInfo; - } - else if (resource is BufferView bufferView) - { - set.PBufferInfo[i] = bufferView.Vulkan().BufferInfo; - } - } - break; - - case ResourceType.Texture: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Sum(static item => item.Count); - - for (int i = 0; i < resources.Length; i++) - { - IBindableResource resource = resources[i]; - - if (resource is Texture texture) - { - set.PImageInfo[i] = texture.Vulkan().View.SrvImageInfo; - - SrvTextureViews[index + i] = texture.Vulkan().View; - } - else if (resource is TextureView textureView) - { - set.PImageInfo[i] = textureView.Vulkan().SrvImageInfo; - - SrvTextureViews[index + i] = textureView.Vulkan(); - } - } - } - break; - - case ResourceType.TextureReadWrite: - { - uint index = (uint)Desc.Bindings.Take((int)binding).Sum(static item => item.Count); - - for (int i = 0; i < resources.Length; i++) - { - IBindableResource resource = resources[i]; - - if (resource is Texture texture) - { - set.PImageInfo[i] = texture.Vulkan().View.UavImageInfo; - - UavTextureViews[index + i] = texture.Vulkan().View; - } - else if (resource is TextureView textureView) - { - set.PImageInfo[i] = textureView.Vulkan().UavImageInfo; - - UavTextureViews[index + i] = textureView.Vulkan(); - } - } - } - break; - - case ResourceType.Sampler: - for (int i = 0; i < resources.Length; i++) - { - if (resources[i] is Sampler sampler) - { - set.PImageInfo[i] = new() { Sampler = sampler.Vulkan().Sampler }; - } - } - break; - - case ResourceType.AccelerationStructure: - for (int i = 0; i < resources.Length; i++) - { - if (resources[i] is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - ((WriteDescriptorSetAccelerationStructureKHR*)set.PNext)->PAccelerationStructures[i] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; - } - } - break; - } - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - scope.Dispose(); - } -} \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/VKSampler.cs b/sources/Zenith.NET.Vulkan/VKSampler.cs deleted file mode 100644 index 1d4fd186..00000000 --- a/sources/Zenith.NET.Vulkan/VKSampler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKSampler : Sampler -{ - public VkSampler Sampler; - - public VKSampler(VKGraphicsContext context, SamplerDesc desc) : base(context, desc) - { - SamplerCreateInfo createInfo = new() - { - SType = StructureType.SamplerCreateInfo, - MagFilter = VKFormats.Vulkan(desc.Filter).MagFilter, - MinFilter = VKFormats.Vulkan(desc.Filter).MinFilter, - MipmapMode = VKFormats.Vulkan(desc.Filter).MipmapMode, - AddressModeU = VKFormats.Vulkan(desc.U), - AddressModeV = VKFormats.Vulkan(desc.V), - AddressModeW = VKFormats.Vulkan(desc.W), - MipLodBias = desc.LodBias, - AnisotropyEnable = desc.Filter is Filter.Anisotropic, - MaxAnisotropy = desc.MaxAnisotropy, - CompareEnable = desc.ComparisonFunc is not ComparisonFunc.Never, - CompareOp = VKFormats.Vulkan(desc.ComparisonFunc), - MinLod = desc.MinLod, - MaxLod = desc.MaxLod, - BorderColor = VKFormats.Vulkan(desc.BorderColor) - }; - - context.Vk.CreateSampler(context.Device, &createInfo, null, out Sampler).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Sampler, - ObjectHandle = Sampler.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroySampler(Context.Device, Sampler, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKShader.cs b/sources/Zenith.NET.Vulkan/VKShader.cs deleted file mode 100644 index 22eda796..00000000 --- a/sources/Zenith.NET.Vulkan/VKShader.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKShader(VKGraphicsContext context, ShaderDesc desc) : Shader(context, desc) -{ - public PipelineShaderStageCreateInfo GetPipelineShaderStageCreateInfo(ZenithMarshal.Scope scope) - { - return new() - { - SType = StructureType.PipelineShaderStageCreateInfo, - Stage = VKFormats.Vulkan(Desc.Stage), - PName = (byte*)ZenithMarshal.StringToPointer(scope, Desc.EntryPoint, StringEncoding.UTF8), - PNext = (void*)ZenithMarshal.AllocateAndFill(scope, - [ - new() - { - SType = StructureType.ShaderModuleCreateInfo, - CodeSize = (uint)Desc.ShaderBytes.Length, - PCode = (uint*)ZenithMarshal.AllocateAndFill(scope, Desc.ShaderBytes) - } - ]) - }; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - } -} diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs deleted file mode 100644 index 8d02a159..00000000 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System.Diagnostics; -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKSwapChain : SwapChain -{ - private readonly VKFence fence; - private readonly VKSwapChainTargets swapChainTargets; - - public SurfaceKHR Surface; - - public SwapchainKHR Swapchain; - - public uint ImageIndex; - - public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context, desc) - { - fence = new(context); - swapChainTargets = new(context, this); - - CreateSurface(); - CreateSwapChain(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public override uint Width => CurrentColorTarget.Desc.Width; - - public override uint Height => CurrentColorTarget.Desc.Height; - - public override Texture CurrentColorTarget => swapChainTargets[ImageIndex]; - - public override Texture? CurrentDepthStencilTarget => swapChainTargets.DepthStencilTarget; - - public override Output Output => new() - { - ColorAttachments = [Desc.ColorTargetFormat], - DepthStencilAttachment = Desc.DepthStencilTargetFormat, - SampleCount = SampleCount.Count1 - }; - - public override void Present() - { - if (Swapchain.Handle is not 0) - { - fixed (SwapchainKHR* pSwapchains = &Swapchain) - { - fixed (uint* pImageIndices = &ImageIndex) - { - PresentInfoKHR presentInfo = new() - { - SType = StructureType.PresentInfoKhr, - SwapchainCount = 1, - PSwapchains = pSwapchains, - PImageIndices = pImageIndices - }; - - (Context.Swapchain?.QueuePresent(Context.GraphicsQueue, &presentInfo) ?? Result.ErrorInitializationFailed).Success(); - - AcquireNextImage(); - } - } - } - } - - protected override void ResizeImpl() - { - CreateSwapChain(); - } - - protected override void RefreshImpl() - { - CreateSurface(); - CreateSwapChain(); - } - - protected override void SetResourceName(string name) - { - if (Swapchain.Handle is not 0) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.SwapchainKhr, - ObjectHandle = Swapchain.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - } - - protected override void Destroy() - { - DestroySwapChain(); - DestroySurface(); - - swapChainTargets.Dispose(); - fence.Dispose(); - } - - private void CreateSurface() - { - DestroySurface(); - - switch (Desc.Surface.Type) - { - case SurfaceType.Win32: - { - Win32SurfaceCreateInfoKHR createInfo = new() - { - SType = StructureType.Win32SurfaceCreateInfoKhr, - Hinstance = Process.GetCurrentProcess().Handle, - Hwnd = Desc.Surface.Handles[0] - }; - - Context.Win32Surface?.CreateWin32Surface(Context.Instance, &createInfo, null, out Surface).Success(); - } - break; - - case SurfaceType.Wayland: - { - WaylandSurfaceCreateInfoKHR createInfo = new() - { - SType = StructureType.WaylandSurfaceCreateInfoKhr, - Display = (nint*)Desc.Surface.Handles[0], - Surface = (nint*)Desc.Surface.Handles[1] - }; - - Context.WaylandSurface?.CreateWaylandSurface(Context.Instance, &createInfo, null, out Surface).Success(); - } - break; - - case SurfaceType.Xlib: - { - XlibSurfaceCreateInfoKHR createInfo = new() - { - SType = StructureType.XlibSurfaceCreateInfoKhr, - Dpy = (nint*)Desc.Surface.Handles[0], - Window = Desc.Surface.Handles[1] - }; - - Context.XlibSurface?.CreateXlibSurface(Context.Instance, &createInfo, null, out Surface).Success(); - } - break; - - case SurfaceType.Android: - { - AndroidSurfaceCreateInfoKHR createInfo = new() - { - SType = StructureType.AndroidSurfaceCreateInfoKhr, - Window = (nint*)Desc.Surface.Handles[0] - }; - - Context.AndroidSurface?.CreateAndroidSurface(Context.Instance, &createInfo, null, out Surface).Success(); - } - break; - - case SurfaceType.Apple: - { - MetalSurfaceCreateInfoEXT createInfo = new() - { - SType = StructureType.MetalSurfaceCreateInfoExt, - PLayer = (nint*)Desc.Surface.Handles[0] - }; - - Context.MetalSurface?.CreateMetalSurface(Context.Instance, &createInfo, null, out Surface).Success(); - } - break; - } - } - - private void DestroySurface() - { - if (Surface.Handle is not 0) - { - Context.Surface?.DestroySurface(Context.Instance, Surface, null); - - Surface = default; - } - } - - private void CreateSwapChain() - { - DestroySwapChain(); - - if (Desc.Surface.Type is not SurfaceType.D3D11Interop) - { - using ZenithMarshal.Scope scope = new(); - - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = Context.GetSharingModeInfo(scope); - - SurfaceCapabilitiesKHR capabilities = default; - Context.Surface?.GetPhysicalDeviceSurfaceCapabilities(Context.PhysicalDevice, Surface, &capabilities).Success(); - - uint surfaceFormatCount = 0; - Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, null).Success(); - - SurfaceFormatKHR* surfaceFormats = (SurfaceFormatKHR*)ZenithMarshal.Allocate(scope, surfaceFormatCount); - Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, surfaceFormats).Success(); - - uint presentModeCount = 0; - Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, null).Success(); - - PresentModeKHR* presentModes = (PresentModeKHR*)ZenithMarshal.Allocate(scope, presentModeCount); - Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, presentModes).Success(); - - uint minImageCount = capabilities.MinImageCount + 1; - if (capabilities.MaxImageCount > 0 && minImageCount > capabilities.MaxImageCount) - { - minImageCount = capabilities.MaxImageCount; - } - - SurfaceFormatKHR surfaceFormat = default; - foreach (SurfaceFormatKHR item in new ReadOnlySpan(surfaceFormats, (int)surfaceFormatCount)) - { - if (item.Format == VKFormats.Vulkan(Desc.ColorTargetFormat)) - { - surfaceFormat = item; - - if (item.ColorSpace is ColorSpaceKHR.SpaceSrgbNonlinearKhr) - { - break; - } - } - } - - Extent2D imageExtent = new() - { - Width = uint.Clamp(capabilities.MinImageExtent.Width, Desc.Surface.Width, capabilities.MaxImageExtent.Width), - Height = uint.Clamp(capabilities.MinImageExtent.Height, Desc.Surface.Height, capabilities.MaxImageExtent.Height) - }; - - SurfaceTransformFlagsKHR preTransform = SurfaceTransformFlagsKHR.InheritBitKhr; - if (capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.IdentityBitKhr)) - { - preTransform = SurfaceTransformFlagsKHR.IdentityBitKhr; - } - - CompositeAlphaFlagsKHR compositeAlpha = CompositeAlphaFlagsKHR.InheritBitKhr; - if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr)) - { - compositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr; - } - - PresentModeKHR presentMode = PresentModeKHR.FifoKhr; - foreach (PresentModeKHR item in new ReadOnlySpan(presentModes, (int)presentModeCount)) - { - if (item is PresentModeKHR.MailboxKhr) - { - presentMode = PresentModeKHR.MailboxKhr; - - break; - } - } - - SwapchainCreateInfoKHR createInfo = new() - { - SType = StructureType.SwapchainCreateInfoKhr, - Surface = Surface, - MinImageCount = minImageCount, - ImageFormat = surfaceFormat.Format, - ImageColorSpace = surfaceFormat.ColorSpace, - ImageExtent = imageExtent, - ImageArrayLayers = 1, - ImageUsage = ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.ColorAttachmentBit, - ImageSharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices, - PreTransform = preTransform, - CompositeAlpha = compositeAlpha, - PresentMode = presentMode, - Clipped = true - }; - - Context.Swapchain?.CreateSwapchain(Context.Device, &createInfo, null, out Swapchain).Success(); - - swapChainTargets.CreateTargets(createInfo.ImageExtent.Width, createInfo.ImageExtent.Height, []); - - AcquireNextImage(); - } - else - { - swapChainTargets.CreateTargets(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); - } - } - - private void DestroySwapChain() - { - swapChainTargets.DestroyTargets(); - - if (Swapchain.Handle is not 0) - { - Context.Swapchain?.DestroySwapchain(Context.Device, Swapchain, null); - - Swapchain = default; - } - - ImageIndex = 0; - } - - private void AcquireNextImage() - { - fixed (uint* pImageIndex = &ImageIndex) - { - (Context.Swapchain?.AcquireNextImage(Context.Device, Swapchain, ulong.MaxValue, default, fence.Fence, pImageIndex) ?? Result.ErrorInitializationFailed).Success(); - - fence.Wait(); - } - } -} diff --git a/sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs b/sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs deleted file mode 100644 index fcbc8c7b..00000000 --- a/sources/Zenith.NET.Vulkan/VKSwapChainTargets.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKSwapChainTargets(VKGraphicsContext context, VKSwapChain swapChain) : GraphicsResource(context) -{ - private VKTexture? depthStencilTarget; - private VKTexture[] colorTargets = []; - - public VKTexture this[uint index] => colorTargets[index]; - - public VKTexture? DepthStencilTarget => depthStencilTarget; - - public void CreateTargets(uint width, uint height, nint[] handles) - { - if (swapChain.Desc.DepthStencilTargetFormat is not null) - { - depthStencilTarget = new(context, new() - { - Type = TextureType.Texture2D, - Format = swapChain.Desc.DepthStencilTargetFormat.Value, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil - }); - } - - TextureDesc colorTargetDesc = new() - { - Type = TextureType.Texture2D, - Format = swapChain.Desc.ColorTargetFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.RenderTarget - }; - - if (swapChain.Desc.Surface.Type is not SurfaceType.D3D11Interop) - { - using ZenithMarshal.Scope scope = new(); - - uint swapchainImageCount = 0; - context.Swapchain?.GetSwapchainImages(context.Device, swapChain.Swapchain, &swapchainImageCount, null).Success(); - - Image* swapchainImages = (Image*)ZenithMarshal.Allocate(scope, swapchainImageCount); - context.Swapchain?.GetSwapchainImages(context.Device, swapChain.Swapchain, &swapchainImageCount, swapchainImages).Success(); - - colorTargets = new VKTexture[swapchainImageCount]; - - for (uint i = 0; i < swapchainImageCount; i++) - { - colorTargets[i] = new(context, colorTargetDesc, swapchainImages[i]); - } - } - else if (swapChain.Desc.Surface.Type is SurfaceType.D3D11Interop) - { - colorTargets = new VKTexture[1]; - - colorTargets[0] = new(context, colorTargetDesc, ExternalMemoryHandleTypeFlags.D3D11TextureBit, handles[0]); - } - } - - public void DestroyTargets() - { - foreach (VKTexture texture in colorTargets) - { - texture.Dispose(); - } - colorTargets = []; - - depthStencilTarget?.Dispose(); - depthStencilTarget = null; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroyTargets(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs deleted file mode 100644 index 3d4893c0..00000000 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ /dev/null @@ -1,184 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKTexture : Texture -{ - public Image Image; - - public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); - - ImageCreateInfo createInfo = new() - { - SType = StructureType.ImageCreateInfo, - Flags = desc.Type is TextureType.TextureCube or TextureType.TextureCubeArray ? ImageCreateFlags.CreateCubeCompatibleBit : ImageCreateFlags.None, - ImageType = VKFormats.Vulkan(desc.Type).Type, - Format = VKFormats.Vulkan(desc.Format), - Extent = new() - { - Width = desc.Width, - Height = desc.Height, - Depth = desc.Type is TextureType.Texture3D ? desc.Depth : 1 - }, - MipLevels = desc.MipLevels, - ArrayLayers = desc.ArrayLayers, - Samples = VKFormats.Vulkan(desc.SampleCount), - Usage = VKFormats.Vulkan(desc.Format, desc.Flags).UsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices - }; - - context.Vk.CreateImage(context.Device, &createInfo, null, out Image).Success(); - - DeviceMemory = new(context, this); - - View = new(context, new() - { - Texture = this, - Type = desc.Type, - Format = desc.Format, - Range = new() - { - BaseMipLevel = 0, - LevelCount = desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = desc.ArrayLayers - } - }); - } - - public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image) : base(context, desc) - { - Image = image; - - View = new(context, new() - { - Texture = this, - Type = desc.Type, - Format = desc.Format, - Range = new() - { - BaseMipLevel = 0, - LevelCount = desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = desc.ArrayLayers - } - }); - } - - public VKTexture(VKGraphicsContext context, TextureDesc desc, ExternalMemoryHandleTypeFlags handleTypes, nint handle) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); - - ImageCreateInfo createInfo = new() - { - SType = StructureType.ImageCreateInfo, - Flags = desc.Type is TextureType.TextureCube or TextureType.TextureCubeArray ? ImageCreateFlags.CreateCubeCompatibleBit : ImageCreateFlags.None, - ImageType = VKFormats.Vulkan(desc.Type).Type, - Format = VKFormats.Vulkan(desc.Format), - Extent = new() - { - Width = desc.Width, - Height = desc.Height, - Depth = desc.Type is TextureType.Texture3D ? desc.Depth : 1 - }, - MipLevels = desc.MipLevels, - ArrayLayers = desc.ArrayLayers, - Samples = VKFormats.Vulkan(desc.SampleCount), - Usage = VKFormats.Vulkan(desc.Format, desc.Flags).UsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices - }; - - createInfo.AddNext(out ExternalMemoryImageCreateInfo externalMemoryImageCreateInfo); - externalMemoryImageCreateInfo.HandleTypes = handleTypes; - - context.Vk.CreateImage(context.Device, &createInfo, null, out Image).Success(); - - DeviceMemory = new(context, this, handleTypes, handle); - - View = new(context, new() - { - Texture = this, - Type = desc.Type, - Format = desc.Format, - Range = new() - { - BaseMipLevel = 0, - LevelCount = desc.MipLevels, - BaseArrayLayer = 0, - LayerCount = desc.ArrayLayers - } - }); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKDeviceMemory? DeviceMemory { get; } - - public VKTextureView View { get; } - - public uint SubresourceIndex(TextureSubresource subresource) - { - return (subresource.ArrayLayer * Desc.MipLevels) + subresource.MipLevel; - } - - public ImageView CreateAttachmentView(TextureSubresource subresource) - { - ImageViewCreateInfo createInfo = new() - { - SType = StructureType.ImageViewCreateInfo, - Image = Image, - ViewType = ImageViewType.Type2D, - Format = VKFormats.Vulkan(Desc.Format), - SubresourceRange = new() - { - AspectMask = VKFormats.Vulkan(Desc.Format, Desc.Flags).AspectFlags, - BaseMipLevel = subresource.MipLevel, - LevelCount = 1, - BaseArrayLayer = subresource.ArrayLayer, - LayerCount = 1 - } - }; - - ImageView imageView; - Context.Vk.CreateImageView(Context.Device, &createInfo, null, &imageView).Success(); - - return imageView; - } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Image, - ObjectHandle = Image.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - View.Dispose(); - - if (DeviceMemory is not null) - { - Context.Vk.DestroyImage(Context.Device, Image, null); - - DeviceMemory.Dispose(); - } - } -} diff --git a/sources/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Zenith.NET.Vulkan/VKTextureView.cs deleted file mode 100644 index c31b41ee..00000000 --- a/sources/Zenith.NET.Vulkan/VKTextureView.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKTextureView : TextureView -{ - public ImageView ImageView; - - public VKTextureView(VKGraphicsContext context, TextureViewDesc desc) : base(context, desc) - { - TextureSubresourceRange range = desc.Range; - - ImageViewCreateInfo createInfo = new() - { - SType = StructureType.ImageViewCreateInfo, - Image = desc.Texture.Vulkan().Image, - ViewType = VKFormats.Vulkan(desc.Type).ViewType, - Format = VKFormats.Vulkan(desc.Format), - SubresourceRange = new() - { - AspectMask = VKFormats.Vulkan(desc.Format, desc.Texture.Desc.Flags).AspectFlags & ~ImageAspectFlags.StencilBit, - BaseMipLevel = range.BaseMipLevel, - LevelCount = range.LevelCount, - BaseArrayLayer = range.BaseArrayLayer, - LayerCount = range.LayerCount - } - }; - - context.Vk.CreateImageView(context.Device, &createInfo, null, out ImageView).Success(); - - SrvImageInfo = new() - { - ImageView = ImageView, - ImageLayout = ImageLayout.ShaderReadOnlyOptimal - }; - - UavImageInfo = new() - { - ImageView = ImageView, - ImageLayout = ImageLayout.General - }; - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public DescriptorImageInfo SrvImageInfo { get; } - - public DescriptorImageInfo UavImageInfo { get; } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.ImageView, - ObjectHandle = ImageView.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.Vk.DestroyImageView(Context.Device, ImageView, null); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs deleted file mode 100644 index 4139bbd1..00000000 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ /dev/null @@ -1,208 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKTopLevelAccelerationStructure : TopLevelAccelerationStructure -{ - public AccelerationStructureKHR AccelerationStructure; - - public ulong DeviceAddress; - - public VKTopLevelAccelerationStructure(VKGraphicsContext context, TopLevelAccelerationStructureDesc desc, VKCommandBuffer commandBuffer) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - InstanceBuffer = new(context, new() - { - SizeInBytes = (uint)(sizeof(AccelerationStructureInstanceKHR) * desc.Instances.Length), - StrideInBytes = (uint)sizeof(AccelerationStructureInstanceKHR), - Flags = BufferUsageFlags.AccelerationStructure | BufferUsageFlags.MapWrite - }); - - FillInstanceBuffer(desc, out AccelerationStructureGeometryKHR geometry, out AccelerationStructureBuildRangeInfoKHR buildRangeInfo); - - AccelerationStructureBuildGeometryInfoKHR buildInfo = new() - { - SType = StructureType.AccelerationStructureBuildGeometryInfoKhr, - Type = AccelerationStructureTypeKHR.TopLevelKhr, - Flags = VKFormats.Vulkan(desc.Flags), - Mode = BuildAccelerationStructureModeKHR.BuildKhr, - GeometryCount = 1, - PGeometries = &geometry - }; - - AccelerationStructureBuildSizesInfoKHR sizeInfo = new() { SType = StructureType.AccelerationStructureBuildSizesInfoKhr }; - - context.AccelerationStructure?.GetAccelerationStructureBuildSizes(context.Device, AccelerationStructureBuildTypeKHR.DeviceKhr, &buildInfo, &buildRangeInfo.PrimitiveCount, &sizeInfo); - - BufferDesc accelerationStructureBufferDesc = new() - { - SizeInBytes = (uint)sizeInfo.AccelerationStructureSize, - StrideInBytes = (uint)sizeInfo.AccelerationStructureSize - }; - - AccelerationStructureBuffer = new(context, accelerationStructureBufferDesc, VkBufferUsageFlags.AccelerationStructureStorageBitKhr); - - AccelerationStructureCreateInfoKHR createInfo = new() - { - SType = StructureType.AccelerationStructureCreateInfoKhr, - Buffer = AccelerationStructureBuffer.Buffer, - Size = sizeInfo.AccelerationStructureSize, - Type = AccelerationStructureTypeKHR.TopLevelKhr - }; - - context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, null, out AccelerationStructure).Success(); - - AccelerationStructureDeviceAddressInfoKHR addressInfo = new() - { - SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, - AccelerationStructure = AccelerationStructure - }; - - DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &addressInfo) ?? 0; - - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)sizeInfo.BuildScratchSize, - StrideInBytes = (uint)sizeInfo.BuildScratchSize, - Flags = BufferUsageFlags.ShaderResource - }); - - buildInfo.DstAccelerationStructure = AccelerationStructure; - buildInfo.ScratchData = new() { DeviceAddress = ScratchBuffer.DeviceAddress }; - - AccelerationStructureBuildRangeInfoKHR* pBuildRangeInfo = &buildRangeInfo; - - context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &buildInfo, &pBuildRangeInfo); - - MemoryBarrier barrier = new() - { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.AccelerationStructureWriteBitKhr, - DstAccessMask = AccessFlags.AccelerationStructureReadBitKhr - }; - - context.Vk.CmdPipelineBarrier(commandBuffer.CommandBuffer, - PipelineStageFlags.AccelerationStructureBuildBitKhr, - PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit, - 0, - 1, - &barrier, - 0, - null, - 0, - null); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - public VKBuffer InstanceBuffer { get; } - - public VKBuffer AccelerationStructureBuffer { get; } - - public VKBuffer ScratchBuffer { get; } - - public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) - { - FillInstanceBuffer(newDesc, out AccelerationStructureGeometryKHR geometry, out AccelerationStructureBuildRangeInfoKHR buildRangeInfo); - - AccelerationStructureBuildGeometryInfoKHR buildInfo = new() - { - SType = StructureType.AccelerationStructureBuildGeometryInfoKhr, - Type = AccelerationStructureTypeKHR.TopLevelKhr, - Flags = VKFormats.Vulkan(newDesc.Flags), - Mode = BuildAccelerationStructureModeKHR.UpdateKhr, - SrcAccelerationStructure = AccelerationStructure, - DstAccelerationStructure = AccelerationStructure, - GeometryCount = 1, - PGeometries = &geometry, - ScratchData = new() { DeviceAddress = ScratchBuffer.DeviceAddress } - }; - - AccelerationStructureBuildRangeInfoKHR* pBuildRangeInfo = &buildRangeInfo; - - Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &buildInfo, &pBuildRangeInfo); - - MemoryBarrier barrier = new() - { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.AccelerationStructureWriteBitKhr, - DstAccessMask = AccessFlags.AccelerationStructureReadBitKhr - }; - - Context.Vk.CmdPipelineBarrier(commandBuffer.CommandBuffer, - PipelineStageFlags.AccelerationStructureBuildBitKhr, - PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit, - 0, - 1, - &barrier, - 0, - null, - 0, - null); - } - - protected override void SetResourceName(string name) - { - using ZenithMarshal.Scope scope = new(); - - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.AccelerationStructureKhr, - ObjectHandle = AccelerationStructure.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; - - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); - } - - protected override void Destroy() - { - Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, null); - - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - InstanceBuffer.Dispose(); - } - - private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc, out AccelerationStructureGeometryKHR geometry, out AccelerationStructureBuildRangeInfoKHR buildRangeInfo) - { - uint instanceCount = (uint)desc.Instances.Length; - - MappedMemory mappedMemory = InstanceBuffer.Map(); - - AccelerationStructureInstanceKHR* instances = (AccelerationStructureInstanceKHR*)mappedMemory.Pointer; - for (uint i = 0; i < instanceCount; i++) - { - RayTracingInstance instance = desc.Instances[i]; - - instances[i] = new() - { - Transform = VKFormats.Vulkan(instance.Transform), - InstanceCustomIndex = instance.ID, - Mask = instance.Mask, - Flags = VKFormats.Vulkan(instance.Flags), - AccelerationStructureReference = instance.AccelerationStructure.Vulkan().DeviceAddress - }; - } - - InstanceBuffer.Unmap(); - - geometry = new() - { - SType = StructureType.AccelerationStructureGeometryKhr, - GeometryType = GeometryTypeKHR.InstancesKhr, - Geometry = new() - { - Instances = new() - { - SType = StructureType.AccelerationStructureGeometryInstancesDataKhr, - Data = new() { DeviceAddress = InstanceBuffer.DeviceAddress } - } - } - }; - - buildRangeInfo = new() { PrimitiveCount = instanceCount }; - } -} diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs deleted file mode 100644 index df6c94ee..00000000 --- a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKValidationLayer : ValidationLayer -{ - private readonly PfnDebugUtilsMessengerCallbackEXT pfnUserCallback; - private readonly DebugUtilsMessengerEXT messenger; - - public VKValidationLayer(VKGraphicsContext context) : base(context) - { - DebugUtilsMessengerCreateInfoEXT createInfo = new() - { - SType = StructureType.DebugUtilsMessengerCreateInfoExt, - MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt - | DebugUtilsMessageSeverityFlagsEXT.InfoBitExt - | DebugUtilsMessageSeverityFlagsEXT.WarningBitExt - | DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt, - MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt - | DebugUtilsMessageTypeFlagsEXT.ValidationBitExt - | DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt, - PfnUserCallback = pfnUserCallback = new(UserCallback) - }; - - context.DebugUtils?.CreateDebugUtilsMessenger(context.Instance, &createInfo, null, out messenger).Success(); - } - - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - Context.DebugUtils?.DestroyDebugUtilsMessenger(Context.Instance, messenger, null); - - pfnUserCallback.Dispose(); - } - - private uint UserCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, - DebugUtilsMessageTypeFlagsEXT messageTypes, - DebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) - { - Report(messageSeverity switch - { - DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => MessageSeverity.Error, - DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => MessageSeverity.Warning, - _ => MessageSeverity.Info - }, ZenithMarshal.StringFromPointer((nint)pCallbackData->PMessage, StringEncoding.UTF8)); - - return Vk.False; - } -} From 3ea967c8506e02e529d56fcfeedce1dbc8910400 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 14:40:08 +0800 Subject: [PATCH 102/466] Add NSAutorelease utility for managed object ownership Introduced NSAutorelease class in Zenith.NET.Metal to provide Own methods that wrap object creation in an NSAutoreleasePool and return retained NSObject instances. Supports up to three arguments for creation functions, improving memory management for native interop. --- sources/Zenith.NET.Metal/NSAutorelease.cs | 34 +++++++++++++++++++++++ sources/Zenith.NET.Metal/Usings.cs | 1 + sources/Zenith.NET.Vulkan/Usings.cs | 1 + 3 files changed, 36 insertions(+) create mode 100644 sources/Zenith.NET.Metal/NSAutorelease.cs create mode 100644 sources/Zenith.NET.Metal/Usings.cs create mode 100644 sources/Zenith.NET.Vulkan/Usings.cs diff --git a/sources/Zenith.NET.Metal/NSAutorelease.cs b/sources/Zenith.NET.Metal/NSAutorelease.cs new file mode 100644 index 00000000..5d11406d --- /dev/null +++ b/sources/Zenith.NET.Metal/NSAutorelease.cs @@ -0,0 +1,34 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal static class NSAutorelease +{ + public static T Own(Func func) where T : NSObject, INativeObject + { + using NSAutoreleasePool _ = new(); + + return func().Retain(); + } + + public static T Own(Func func, T1 arg1) where T : NSObject, INativeObject + { + using NSAutoreleasePool _ = new(); + + return func(arg1).Retain(); + } + + public static T Own(Func func, T1 arg1, T2 arg2) where T : NSObject, INativeObject + { + using NSAutoreleasePool _ = new(); + + return func(arg1, arg2).Retain(); + } + + public static T Own(Func func, T1 arg1, T2 arg2, T3 arg3) where T : NSObject, INativeObject + { + using NSAutoreleasePool _ = new(); + + return func(arg1, arg2, arg3).Retain(); + } +} \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/Usings.cs b/sources/Zenith.NET.Metal/Usings.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/sources/Zenith.NET.Metal/Usings.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -0,0 +1 @@ + \ No newline at end of file From da55ce3709d0a69f3eb425964a2bf65554ce629b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 15:14:35 +0800 Subject: [PATCH 103/466] Refactor shader to use descriptor handles in Constants Replaces global texture and sampler uniforms with DescriptorHandle fields in the Constants struct. Updates PSMain to sample textures via the constants struct, improving resource binding flexibility and encapsulation. --- .../Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index d13dd5f1..c215fd51 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -28,11 +28,13 @@ struct VSOutput struct Constants { float4x4 Projection; + + DescriptorHandle Texture; + + DescriptorHandle Sampler; }; uniform Constants constants; -uniform Texture2D texture; -uniform SamplerState sampler; float3 SrgbToLinear(float3 srgb) { @@ -56,7 +58,7 @@ VSOutput VSMain(VSInput input) float4 PSMain(VSOutput input) : SV_TARGET { - return input.Color * texture.Sample(sampler, input.UV); + return input.Color * (*constants.Texture).Sample(*constants.Sampler, input.UV); } "; From cb4f565e96d0310c181bedf90a7e931e57bc5924 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 16:04:29 +0800 Subject: [PATCH 104/466] Remove constants system and related attributes Removed custom attribute classes and the entire Constants buffer management system, including the IConstantsLayout interface. Updated CommandBuffer to use SetConstantBuffer instead of SetConstants, and removed all references to Constants from GraphicsContext and CommandBuffer. Constant buffer management is now expected to be handled externally. --- .../Attributes/ConstantsAttribute.cs | 4 - .../Zenith.NET/Attributes/SampledAttribute.cs | 4 - .../Zenith.NET/Attributes/SamplerAttribute.cs | 4 - .../Zenith.NET/Attributes/StorageAttribute.cs | 4 - .../Attributes/StorageReadOnlyAttribute.cs | 4 - .../Attributes/StorageReadWriteAttribute.cs | 4 - .../Zenith.NET/Attributes/UniformAttribute.cs | 4 - sources/Zenith.NET/CommandBuffer.cs | 8 +- sources/Zenith.NET/Constants.cs | 138 ------------------ sources/Zenith.NET/GraphicsContext.cs | 4 - .../Zenith.NET/Interfaces/IConstantsLayout.cs | 16 -- 11 files changed, 3 insertions(+), 191 deletions(-) delete mode 100644 sources/Zenith.NET/Attributes/ConstantsAttribute.cs delete mode 100644 sources/Zenith.NET/Attributes/SampledAttribute.cs delete mode 100644 sources/Zenith.NET/Attributes/SamplerAttribute.cs delete mode 100644 sources/Zenith.NET/Attributes/StorageAttribute.cs delete mode 100644 sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs delete mode 100644 sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs delete mode 100644 sources/Zenith.NET/Attributes/UniformAttribute.cs delete mode 100644 sources/Zenith.NET/Constants.cs delete mode 100644 sources/Zenith.NET/Interfaces/IConstantsLayout.cs diff --git a/sources/Zenith.NET/Attributes/ConstantsAttribute.cs b/sources/Zenith.NET/Attributes/ConstantsAttribute.cs deleted file mode 100644 index fbbb8e55..00000000 --- a/sources/Zenith.NET/Attributes/ConstantsAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Struct)] -public sealed class ConstantsAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/SampledAttribute.cs b/sources/Zenith.NET/Attributes/SampledAttribute.cs deleted file mode 100644 index 331f0524..00000000 --- a/sources/Zenith.NET/Attributes/SampledAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Field)] -public sealed class SampledAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/SamplerAttribute.cs b/sources/Zenith.NET/Attributes/SamplerAttribute.cs deleted file mode 100644 index b385e482..00000000 --- a/sources/Zenith.NET/Attributes/SamplerAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Field)] -public sealed class SamplerAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/StorageAttribute.cs b/sources/Zenith.NET/Attributes/StorageAttribute.cs deleted file mode 100644 index c8a9c95b..00000000 --- a/sources/Zenith.NET/Attributes/StorageAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Field)] -public sealed class StorageAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs b/sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs deleted file mode 100644 index 352ee7e4..00000000 --- a/sources/Zenith.NET/Attributes/StorageReadOnlyAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Field)] -public sealed class StorageReadOnlyAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs b/sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs deleted file mode 100644 index d7d8d94c..00000000 --- a/sources/Zenith.NET/Attributes/StorageReadWriteAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Field)] -public sealed class StorageReadWriteAttribute : Attribute; diff --git a/sources/Zenith.NET/Attributes/UniformAttribute.cs b/sources/Zenith.NET/Attributes/UniformAttribute.cs deleted file mode 100644 index d3f919bc..00000000 --- a/sources/Zenith.NET/Attributes/UniformAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Zenith.NET; - -[AttributeUsage(AttributeTargets.Field)] -public sealed class UniformAttribute : Attribute; diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 0a71f5ca..36954987 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -233,14 +233,14 @@ public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexF SetIndexBufferImpl(pipeline, buffer, offsetInBytes, indexFormat); } - public void SetConstants(T data) where T : unmanaged, IConstantsLayout + public void SetConstantBuffer(Buffer buffer, uint offsetInBytes) { if (!TryGetCurrentPipeline(out Pipeline pipeline)) { return; } - SetConstantsImpl(pipeline, Context.Constants.Buffer(this, data)); + SetConstantBufferImpl(pipeline, buffer, offsetInBytes); } public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) @@ -369,7 +369,6 @@ internal void Reset() Context.Uploader.Release(this); Context.Downloader.Release(this); - Context.Constants.Release(this); currentPipeline = null; } @@ -378,7 +377,6 @@ protected override void Destroy() { Context.Uploader.Release(this); Context.Downloader.Release(this); - Context.Constants.Release(this); currentPipeline = null; } @@ -465,7 +463,7 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); - protected abstract void SetConstantsImpl(Pipeline pipeline, Buffer buffer); + protected abstract void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes); protected abstract void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance); diff --git a/sources/Zenith.NET/Constants.cs b/sources/Zenith.NET/Constants.cs deleted file mode 100644 index 04ffcc4b..00000000 --- a/sources/Zenith.NET/Constants.cs +++ /dev/null @@ -1,138 +0,0 @@ -namespace Zenith.NET; - -internal class Constants(GraphicsContext context) : DisposableObject -{ - private static readonly TimeSpan LeaseLifetime = TimeSpan.FromSeconds(120); - - private readonly Lock @lock = new(); - private readonly List available = []; - private readonly Dictionary> borrowed = []; - - public Buffer Buffer(CommandBuffer commandBuffer, T data) where T : unmanaged, IConstantsLayout - { - using Lock.Scope _ = @lock.EnterScope(); - - uint sizeInBytes = SizeInBytes(); - - if (!borrowed.TryGetValue(commandBuffer, out List? leases)) - { - borrowed[commandBuffer] = leases = []; - } - - if (!(available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) - { - lease = new(context.CreateBuffer(new() - { - SizeInBytes = sizeInBytes, - StrideInBytes = 1, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Uniform - })); - } - - leases.Add(lease); - - Write(data, lease.Buffer); - - return lease.Buffer; - } - - public void Release(CommandBuffer commandBuffer) - { - using Lock.Scope _ = @lock.EnterScope(); - - CleanupExpiredLeases(); - - if (borrowed.Remove(commandBuffer, out List? leases)) - { - foreach (Lease lease in leases) - { - available.Add(lease.Renew()); - } - } - } - - protected override void Destroy() - { - foreach (CommandBuffer commandBuffer in borrowed.Keys.ToArray()) - { - Release(commandBuffer); - } - - foreach (Lease lease in available) - { - lease.Release(); - } - available.Clear(); - } - - private uint SizeInBytes() where T : unmanaged, IConstantsLayout - { - return context.GraphicsApi switch - { - GraphicsApi.DirectX12 => T.DirectX12SizeInBytes, - GraphicsApi.Metal => T.MetalSizeInBytes, - GraphicsApi.Vulkan => T.VulkanSizeInBytes, - _ => 0 - }; - } - - private void Write(T data, Buffer buffer) where T : unmanaged, IConstantsLayout - { - switch (context.GraphicsApi) - { - case GraphicsApi.DirectX12: - T.WriteDirectX12(data, buffer); - break; - - case GraphicsApi.Metal: - T.WriteMetal(data, buffer); - break; - - case GraphicsApi.Vulkan: - T.WriteVulkan(data, buffer); - break; - } - } - - private void CleanupExpiredLeases() - { - available.RemoveAll(static item => item.TryExpire()); - } - - private class Lease(Buffer buffer) - { - private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; - - public Buffer Buffer { get; } = buffer; - - public bool HasCapacityFor(uint sizeInBytes) - { - return Buffer.Desc.SizeInBytes >= sizeInBytes; - } - - public bool TryExpire() - { - if (DateTime.UtcNow >= expirationTime) - { - Release(); - - return true; - } - - return false; - } - - public Lease Renew() - { - expirationTime = DateTime.UtcNow + LeaseLifetime; - - return this; - } - - public void Release() - { - Buffer.Dispose(); - } - } -} \ No newline at end of file diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 9874f07d..a8af0063 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -23,7 +23,6 @@ protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) Uploader = new(this); Downloader = new(this); - Constants = new(this); } public GraphicsApi GraphicsApi { get; } @@ -42,8 +41,6 @@ protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) internal Downloader Downloader { get; } - internal Constants Constants { get; } - public event EventHandler? ValidationMessage; public SwapChain CreateSwapChain(SwapChainDesc desc) @@ -147,7 +144,6 @@ protected override void Destroy() Uploader.Dispose(); Downloader.Dispose(); - Constants.Dispose(); } protected abstract void Initialize(bool useValidationLayer, diff --git a/sources/Zenith.NET/Interfaces/IConstantsLayout.cs b/sources/Zenith.NET/Interfaces/IConstantsLayout.cs deleted file mode 100644 index 4c8300e3..00000000 --- a/sources/Zenith.NET/Interfaces/IConstantsLayout.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Zenith.NET; - -public interface IConstantsLayout where T : unmanaged, IConstantsLayout -{ - static abstract uint DirectX12SizeInBytes { get; } - - static abstract uint MetalSizeInBytes { get; } - - static abstract uint VulkanSizeInBytes { get; } - - static abstract void WriteDirectX12(T data, Buffer dst); - - static abstract void WriteMetal(T data, Buffer dst); - - static abstract void WriteVulkan(T data, Buffer dst); -} From ebf482876f25c6da2c21dd071ff36a8a98746fdd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 16:13:48 +0800 Subject: [PATCH 105/466] Remove default null for searchPaths in shader methods Removed the default value of null for the searchPaths parameter in CreateShaderFromFile and CreateShaderFromSource. Callers must now explicitly provide this argument. --- sources/Zenith.NET/GraphicsContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index a8af0063..b773f571 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -113,7 +113,7 @@ public QueryHeap CreateQueryHeap(QueryHeapDesc desc) return CreateQueryHeapImpl(desc); } - public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages stage, string[]? searchPaths = null) + public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages stage, string[]? searchPaths) { return CreateShader(new() { @@ -123,7 +123,7 @@ public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages }); } - public Shader CreateShaderFromSource(string source, string entryPoint, ShaderStages stage, string[]? searchPaths = null) + public Shader CreateShaderFromSource(string source, string entryPoint, ShaderStages stage, string[]? searchPaths) { return CreateShader(new() { From 6f154283371b1422a7c57aadd3de26623dcd7234 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 16:53:52 +0800 Subject: [PATCH 106/466] Refactor texture creation and mipmap upload logic Refactored texture creation to use TextureDesc.Texture2D helper, updated command buffer acquisition, and renamed layout fields to match API changes. Improved mipmap upload loop clarity and updated command buffer submission for new API semantics. --- .../Extensions.cs | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index 37e9eec9..ab4f8fa4 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -14,23 +14,16 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) uint mipLevels = generateMipMaps ? ZenithHelper.MipLevels((uint)image.Width, (uint)image.Height, 1) : 1; - Texture texture = context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = PixelFormat.R8G8B8A8UNorm, - Width = (uint)image.Width, - Height = (uint)image.Height, - Depth = 1, - MipLevels = mipLevels, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.ShaderResource - }); + Texture texture = context.CreateTexture(TextureDesc.Texture2D(PixelFormat.R8G8B8A8UNorm, + (uint)image.Width, + (uint)image.Height, + mipLevels, + SampleCount.Count1)); Rgba32[] pixels = new Rgba32[image.Width * image.Height]; image.CopyPixelDataTo(pixels); - CommandBuffer commandBuffer = context.Copy.CommandBuffer(); + CommandBuffer commandBuffer = context.CopyQueue.AcquireCommandBuffer(); unsafe { @@ -44,26 +37,23 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) Layout = new() { SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), - RowPitchInBytes = ZenithHelper.RowPitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), - SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) + RowStrideInBytes = ZenithHelper.RowStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) } }; commandBuffer.Upload(texture, default, default, extent, data); } - } - for (uint i = 1; i < mipLevels; i++) - { - ZenithHelper.MipDimensions((uint)image.Width, (uint)image.Height, 1, i, out uint mipWidth, out uint mipHeight, out _); + for (uint i = 1; i < mipLevels; i++) + { + ZenithHelper.MipDimensions((uint)image.Width, (uint)image.Height, 1, i, out uint mipWidth, out uint mipHeight, out _); - using Image mipImage = image.Clone(ctx => ctx.Resize((int)mipWidth, (int)mipHeight, KnownResamplers.MitchellNetravali)); + using Image mipImage = image.Clone(ctx => ctx.Resize((int)mipWidth, (int)mipHeight, KnownResamplers.MitchellNetravali)); - pixels = new Rgba32[mipWidth * mipHeight]; - mipImage.CopyPixelDataTo(pixels); + pixels = new Rgba32[mipWidth * mipHeight]; + mipImage.CopyPixelDataTo(pixels); - unsafe - { fixed (Rgba32* pPixels = pixels) { Extent3D extent = new() { Width = mipWidth, Height = mipHeight, Depth = 1 }; @@ -74,8 +64,8 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) Layout = new() { SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), - RowPitchInBytes = ZenithHelper.RowPitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), - SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) + RowStrideInBytes = ZenithHelper.RowStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) } }; @@ -84,7 +74,7 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) } } - commandBuffer.Submit(true); + commandBuffer.Submit().Wait(); return texture; } From d0de1e5ec2f666eaa860b2924447ee585b524eb6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 16:54:35 +0800 Subject: [PATCH 107/466] Make searchPaths parameter optional in shader methods Updated CreateShaderFromFile and CreateShaderFromSource to use a default value of null for the searchPaths parameter, allowing callers to omit this argument. This improves API usability and maintains backward compatibility. --- sources/Zenith.NET/GraphicsContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index b773f571..a8af0063 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -113,7 +113,7 @@ public QueryHeap CreateQueryHeap(QueryHeapDesc desc) return CreateQueryHeapImpl(desc); } - public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages stage, string[]? searchPaths) + public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages stage, string[]? searchPaths = null) { return CreateShader(new() { @@ -123,7 +123,7 @@ public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages }); } - public Shader CreateShaderFromSource(string source, string entryPoint, ShaderStages stage, string[]? searchPaths) + public Shader CreateShaderFromSource(string source, string entryPoint, ShaderStages stage, string[]? searchPaths = null) { return CreateShader(new() { From d7e06d133fbc052e5c6e27f0a41d4a28fe3ca45a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 17:32:17 +0800 Subject: [PATCH 108/466] Rename "Uniform" buffer references to "Constant" Refactored all occurrences of "Uniform" to "Constant" across buffer-related properties, methods, enum values, and usage flags. This includes renaming handles, usages, and descriptors in Buffer, BufferView, DXBuffer, DXBufferView, BufferDesc, and BufferViewDesc for consistency and clarity. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 2 +- sources/Zenith.NET.DirectX12/DXBufferView.cs | 2 +- sources/Zenith.NET/Buffer.cs | 2 +- sources/Zenith.NET/BufferView.cs | 2 +- sources/Zenith.NET/Enums/BufferUsages.cs | 2 +- sources/Zenith.NET/Structs/BufferDesc.cs | 4 ++-- sources/Zenith.NET/Structs/BufferViewDesc.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index f7ef2fb0..876d2767 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -45,7 +45,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr View.UniformHandle; + public override ResourceHandle ConstantHandle => View.ConstantHandle; public override ResourceHandle StorageReadOnlyHandle => View.StorageReadOnlyHandle; diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs index 02326d71..ea65bcd8 100644 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -9,7 +9,7 @@ internal unsafe class DXBufferView(DXGraphicsContext context, BufferViewDesc des private DXDescriptorToken? srvToken; private DXDescriptorToken? uavToken; - public override ResourceHandle UniformHandle => (cbvToken ??= CreateCbvToken()).ResourceHandle; + public override ResourceHandle ConstantHandle => (cbvToken ??= CreateCbvToken()).ResourceHandle; public override ResourceHandle StorageReadOnlyHandle => (srvToken ??= CreateSrvToken()).ResourceHandle; diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 35362bdd..cb7d1e5d 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -6,7 +6,7 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public ref readonly BufferDesc Desc => ref desc; - public abstract ResourceHandle UniformHandle { get; } + public abstract ResourceHandle ConstantHandle { get; } public abstract ResourceHandle StorageReadOnlyHandle { get; } diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs index b75e1de4..3d41fbc3 100644 --- a/sources/Zenith.NET/BufferView.cs +++ b/sources/Zenith.NET/BufferView.cs @@ -6,7 +6,7 @@ public abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : public ref readonly BufferViewDesc Desc => ref desc; - public abstract ResourceHandle UniformHandle { get; } + public abstract ResourceHandle ConstantHandle { get; } public abstract ResourceHandle StorageReadOnlyHandle { get; } diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs index 182c104f..3517ea4f 100644 --- a/sources/Zenith.NET/Enums/BufferUsages.cs +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -9,7 +9,7 @@ public enum BufferUsages CopyDst = 1 << 1, - Uniform = 1 << 2, + Constant = 1 << 2, Vertex = 1 << 3, diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index 3eade879..50f93c27 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -32,14 +32,14 @@ public static BufferDesc Index(uint sizeInBytes, uint strideInBytes) }; } - public static BufferDesc Uniform(uint sizeInBytes) + public static BufferDesc Constant(uint sizeInBytes) { return new() { SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.Uniform + Usages = BufferUsages.CopyDst | BufferUsages.Constant }; } diff --git a/sources/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Zenith.NET/Structs/BufferViewDesc.cs index e8be7c1b..1295d57e 100644 --- a/sources/Zenith.NET/Structs/BufferViewDesc.cs +++ b/sources/Zenith.NET/Structs/BufferViewDesc.cs @@ -10,7 +10,7 @@ public struct BufferViewDesc public uint StrideInBytes; - public static BufferViewDesc Uniform(Buffer buffer, uint offsetInBytes, uint sizeInBytes) + public static BufferViewDesc Constant(Buffer buffer, uint offsetInBytes, uint sizeInBytes) { return new() { From 54fc78f6c3ff5f614ad7bf5e5ca2df1212dc8cb2 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 18:15:32 +0800 Subject: [PATCH 109/466] Refactor ImGui renderer for modern graphics API usage Refactored ImGuiController and ImGuiRenderer to use explicit graphics API abstractions and handle resources directly via handles instead of ResourceTables. Updated shader selection, input layouts, and buffer management for clarity and efficiency. Introduced a new Constant struct for per-texture constant buffer updates. Improved resource cleanup and modernized code throughout. --- .../ImGuiController.cs | 4 +- .../ImGuiRenderer.cs | 217 ++++++++---------- 2 files changed, 96 insertions(+), 125 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs index 806afe66..0a9771d1 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs @@ -25,7 +25,7 @@ public unsafe class ImGuiController : DisposableObject private bool frameBegun; private ZenithMarshal.Scope? clipboardScope; - public ImGuiController(GraphicsContext context, Output output, ImGuiColorSpace colorSpace, string? fontPath = null, Action? otherSetup = null) + public ImGuiController(GraphicsContext context, AttachmentFormats attachmentFormats, ImGuiColorSpace colorSpace, string? fontPath = null, Action? otherSetup = null) { HexaImGui.SetCurrentContext(Context = HexaImGui.CreateContext()); @@ -46,7 +46,7 @@ public ImGuiController(GraphicsContext context, Output output, ImGuiColorSpace c otherSetup?.Invoke(io); - renderer = new(context, output, colorSpace); + renderer = new(context, attachmentFormats, colorSpace); platformGetClipboardText = PlatformGetClipboardText; platformSetClipboardText = PlatformSetClipboardText; diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index c215fd51..6e49df80 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.Buffers; +using System.Numerics; using System.Runtime.InteropServices; using Hexa.NET.ImGui; @@ -25,7 +26,7 @@ struct VSOutput float4 Color : COLOR0; }; -struct Constants +struct Constant { float4x4 Projection; @@ -34,7 +35,7 @@ struct Constants DescriptorHandle Sampler; }; -uniform Constants constants; +uniform Constant constant; float3 SrgbToLinear(float3 srgb) { @@ -45,7 +46,7 @@ VSOutput VSMain(VSInput input) { VSOutput output; - output.Position = mul(float4(input.Position, 0.0, 1.0), constants.Projection); + output.Position = mul(float4(input.Position, 0.0, 1.0), constant.Projection); output.UV = input.UV; output.Color = input.Color; @@ -56,9 +57,9 @@ VSOutput VSMain(VSInput input) return output; } -float4 PSMain(VSOutput input) : SV_TARGET +float4 FSMain(VSOutput input) : SV_TARGET { - return input.Color * (*constants.Texture).Sample(*constants.Sampler, input.UV); + return input.Color * (*constant.Texture).Sample(*constant.Sampler, input.UV); } "; @@ -70,7 +71,7 @@ float4 PSMain(VSOutput input) : SV_TARGET // Vertex Shader - Linear "44584243D753A620DDB4C72961AF9BB49563BC6A0100000094120000070000003C0000004C000000D40000006001000090020000E0090000FC090000534649300800000000000000000000004953473180000000030000000800000000000000680000000000000000000000030000000000000003030000000000000000000071000000000000000000000003000000010000000303000000000000000000007A000000000000000000000003000000020000000F0F000000000000504F534954494F4E00544558434F4F524400434F4C4F52004F5347318400000003000000080000000000000068000000000000000100000003000000000000000F00000000000000000000007400000000000000000000000300000001000000030C000000000000000000007D000000000000000000000003000000020000000F0000000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200005053563028010000340000000100000000000000000000000000000000000000FFFFFFFF010000000303000303000000000000000000000000000000280000000100000018000000020000000000000000000000000000000D000000000000003000000000504F534954494F4E00544558434F4F524400434F4C4F5200544558434F4F524400434F4C4F520056534D61696E0000010000000000000010000000010000000000000001004200030000000A000000000000000101420003000000130000000000000001024400030000000000000000000000010044030304000019000000000000000101420003020000220000000000000001024400030200000F0000000F00000000000000000000001000000020000000000000000000000000010000000200000004000000080000535441544807000066000100D20100004458494C0601000010000000300700004243C0DE210C0000C90100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010016000000321E981819114C908C092647C60443224AA01846000A30A00C8AA010CAA124CAA35CCAAF208A828A92180128824228030A0B10101081BA1900F26600E89B0120702C87619E070020300000100121100C4050206E0600000079180000A00000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241300C0A76731B06842026085F47E78CED4D2C8C0D2A4C2EAC6DEE0B668240281B1043590C6360800D41B3810000079820741B97B137B739BA3037BAB92F980902B16C208848323608CC3441E03E3250726F6A656374696F6E5F30130482992010CD8601C3860902E14C108867034258572619DAB64160B80982C76D208845323608CCB7E13020AAEA3C30982008C00660C3608CC1186C08C860C3308841194C10C0C0DB109C010DABA9A6B0343722504F534954494F4E1384A29A2014D686C0982014D7064192362C861AAC011BB4011B0C6E60B0C11B10A12AC21A7A7A9222DAB00C71B0066CD0066C30B8C1C0066FC062E889E9496A825060130402DA204875B061B9E6600DD8A00DD860A0838B0DEC60C300077270075CA6ACBEA0DEE6D2E8D2DEDC260845B66131F2600DF4A00DE860A003830DEC60C332C4C11AB0411BB8C1E006031BBCC186E59A83356083367083810E2E36B0830DC31EF0411F6C18F0C00F80094218681B04031436146290067FF084020D33B6B730BAB9090211B148739BA39B9B2010128DB9B4B32F36B2090231D1984B3BFB9AA39B2010D406641448A1144CE11450211554A10A1B9B5D9B4B1A59991BDD9420A84286E76257263797F6E63625209A90E1B9D885B1D995C94D098C3A64782E7368616465724D6F64656C5302A40C199E8B5CD9DC5B9DDC58D9DC94C0A94486E74297075716E4E6F646174697F6E6363745008332A84386E76297567697443645174657362538833A64782E656E747279506F696E7473538250E84286E732F656E7465726373725500500791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF4000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC3600000000000000484153481400000000000000F9DD1AE5250EB9E439EBECBF95D9943F4458494C9008000066000100240200004458494C0601000010000000780800004243C0DE210C00001B0200000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010011000000321E981419114C908C092647C60443224AA0188A6204A00003CAA008CA838A92180128824228030A0B10101081C0B11C86791E0080C000004004844030004181BE19000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241180C0A76731B0684202608843241F82C02130462D980280BA32843036C089C0D04003CC004010CAA0D41344110001A56534D61696E44A09EA692A8929E9C2608C5334128A00D81324128A20902C16C10346DC3A254D6855D43A65C1B11AA22ACA1A72729A20DCBD05917760DD9706D1304A26131F4C4F424354128A40902E16C103431D8B07C60605DD83584C1778DC18681F3C880CB94D517D4DB5C1A5DDA9BDB04A198362C8A1958678085C11006CA35061B96A1B32E2C1BB2E1DA362C1F185817960D61F05D63B06140833450830D4319AC01304108036A83A0B4C18662A2D80072832A6C6C766D2E6964656E745382A00A199E8B5D99DC5CDA9BDB9480684286E76217C6665726372530EA90E1B9CCA1859195C935BD9195B14D09903264782E7265736F75726365735382A70E199E8B5D5AD95D12D9145D185DD99420AA4386E752E646279707F596E646373725700300000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF4000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC3600000061200000900000001304412C100000000E000000548D005051024494462914C20C4071955DC90E14EC000D6304A2B9EAA4374640DA682F7F14CD21640B5173108AA244348C11C8288DA71F002306080082606081C12170CF88C10180201848627004C48841028020181867D06C1EC68C18240008828181060EF745CD8841028020181869F07460903923060900826060A801E48501F68C182400088281B10651198801078D182400088281C10692198C41158D182400088281D10613199081278D182400088281E106541994C1358D181C00088241D306526206A30901309A2004A30983309A400C230607008260D0C8C1E5B8C16842008C2608C168C2208C2610C388C1018020183477C04D6E309A1000A30942309A3008A309C4604E249F11030400413078F8600C22253023808E41947C460C100004C1E0F90333A098C002043A265DF2193140001004834714D2E072020B14E818A5C967C4000140100C9E5260030D0A2C60A063636006F2B1313803F9D818A0817C6C6003F8D8D006F0B1C10DE063431AC8C78634908F0D69201F1BE6003E36D0017C6CA803F8D8F006F2B1E10DE463C31BC867C4200140100C105C000558800555C8460C120004C100C1055080055810056BC4200140100C105C00055880855498460C120004C100C10550800558380568C4200140100C105C00855980055570831183040041304070011466011644A10D460C120004C100C10550680558508561C4200140100C105C00855680055110460C120004C100C10550680558488560C4200140100C105C0085568085537803040000000000", - // Pixel Shader + // Fragment Shader "44584243EE63AC19F937568890164F4A4BFF155601000000D4100000070000003C0000004C000000D800000014010000240200001C0900003809000053464930080000000000000000000000495347318400000003000000080000000000000068000000000000000100000003000000000000000F000000000000000000000074000000000000000000000003000000010000000303000000000000000000007D000000000000000000000003000000020000000F0F00000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200004F5347313400000001000000080000000000000028000000000000004000000003000000000000000F0000000000000053565F5461726765740000005053563008010000340000000000000000000000000000000000000000000000FFFFFFFF000000000301000301000000000000000000000000000000100000000200000018000000010000000000000000000000000000000E000000000000000300000000000000000000000000000002000000000000001800000000544558434F4F524400434F4C4F520050534D61696E000001000000000000001000000000000000000000000100440303040000010000000000000001014200030200000A00000000000000010244000302000000000000000000000100441003000000000000000000000000000000000000000F0000000F00000000000000000000000100000002000000040000000800000053544154F006000066000000BC0100004458494C0601000010000000D80600004243C0DE210C0000B30100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A32528848901420434688A500193242E4480E909122C4504151818CE183E58A0429460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000005000000032224809206485049322A484049322E384A19014124C8A8C0B84A44C107C23002500146600E608C0608E0029C620841442A61880105206A19B86CB9FB08790FC95905662F28BDB46C51863102AF70C973F610F21F921D00C0B8182551845181B630C42C8A076DB70F913F61092BF12924345029146CE43441342484820A4108CB047F0A0E1F227EC21247F25A40D6906441042CA1C41500A46249944070286118861A636180776088779980737A08572C0077AA8077928073920053EB0877218077A780779E0037360877708077A600330A0033F00033FD0033D688774808779F8057AC807782807149099C4601CD8211CE6611EDC8016CA011FE8A11EE4A11CE48014F8C01ECA611CE8E11DE4810FCC811DDE211CE8810DC0800EFC000CFC00091753BE499A224A987C16609E8588D80998081410DAE940E608400100000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000431E0808800000000000000000863C1410000100000000000000648100000011000000321E981419114C908C092647C604432225500C230045501265501E855030544AA20C0A6104A0080A847C01020222109F01A03E96C310000000CF0300814020A46700000079180000870000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304C2982010C7066120260804B241300C0A70731B06842026081AC684AE0C8FAE4EAEEC0B66824024130442D92018CD86C45016C31818C3D9103C1304CE623217D606C75626F705B3013122C9300603D8104C1B080800A80982006C00360CC6756D08B00DC360651384EEDA106C34A0A69AC2D2DCB84C597D41BDCDA5D1A5BDB94D108A678250401B0263825044138442DAB018DE0706612006831818630010A12AC21A7A7A92229A2014D304815836086770061B96A10CBE3108033318CC601803346031F4C4F424354120980DC219ACC1868551836F0CC2C00C063160C6800D360C6490066DC064CAEA8B2A4CEEAC8C6E8250501B16E30D3E3808833118C4C0180336D810C4C186C10DE400982078D506C1A0830D85D5CD41550734CCD8DEC2E8E6260844C322CD6D8E6E6E82403834E6D2CEBED8C868CCA59D7DCDD111A12BC3FB727B936BDBA0DC011EE4811EEC01C10778D00743153636BB369734B23237BA294150850CCFC5AE4C6E2EEDCD6D4A403421C373B10B63B32B939B121875C8F05CE6D0C2C8CAE49ADEC8CAD8A6044819323C17B9B2B9B73AB9B1B2B9290155890CCF852E0FAE2CC8CDED8D2E8C2EEDCD6D6E4A90D521C373B14B2BBB4B229BA20BA32B9B126C75C8F05CCADCE8E4F2A0DED2DCE8E6A60475D0850CCF65ECADCE8DAE4C6E6E4AD007000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF400000071200000190000000660A4AC09208D1150C3E53B8F0F348D3301131102CDB01056D00D97EF3CBE1011C0448440332CC41739CC863403D21816300D97EF3CFEE20083D83CD4E417B76D03D070F9CEE34B00F32C845FDCB60954C3E53B8F2F4D4E44A0D4F450935FDC36000000000000004841534814000000000000009F974238F30DDDF016CFF4F093C3E59C4458494C9407000066000000E50100004458494C06010000100000007C0700004243C0DE210C0000DC0100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A32528848901420434688A500193242E4480E909122C4504151818CE183E58A0429460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000005000000032224809206485049322A484049322E384A19014124C8A8C0B84A44C107C23002500146600E608C0608E0029C620841442A61880105206A19B86CB9FB08790FC95905662F28BDB46C51863102AF70C973F610F21F921D00C0B8182551845181B630C42C8A076DB70F913F61092BF12924345029146CE43441342484820A4108CB047F0A0E1F227EC21247F25A40D6906441042CA1C41500A46249944070286118861A636180776088779980737A08572C0077AA8077928073920053EB0877218077A780779E0037360877708077A600330A0033F00033FD0033D688774808779F8057AC807782807149099C4601CD8211CE6611EDC8016CA011FE8A11EE4A11CE48014F8C01ECA611CE8E11DE4810FCC811DDE211CE8810DC0800EFC000CFC00091753BE499A224A987C16609E8588D80998081410DAE940E608400100000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000431E0808800000000000000000863C1410000100000000000000648100000011000000321E981419114C908C092647C604432225500C4531025004255106E541A524CAA01046008AA040C81720202002F11900EA63390C010000F03C00100804427A060000000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304C2982010C7066120260804B241180C0A70731B0684202608443241D02E02130442992010CB0641713624CAC228CAD028CF86009A2070D5064491184519146043306D202200A026089DB521B026080240036AAA292CCD8DCB94D517D4DB5C1A5DDA9BDB04A1702608C5B32150260805344128A20D8BA26D5CE70D9EF20144A88AB0869E9EA488260885344120980DC2188CC186650883EDEBC4601083E1230316434F4C4F521304A2D9208CC1196C581A33D8BE4E0C06AFF9D060C300066590064CA6ACBEA8C2E4CECAE82608C5B46151D6606383EE1B3CE543830D411B6C18D4C00D800982476D101438D85060D91B547150858DCDAECD258DACCC8D6E4A105421C373B12B939B4B7B739B12104DC8F05CECC2D8ECCAE4A604461D323C9739B430B232B9A637B232B6290152860CCF45AE6CEEAD4E6EAC6C6E4A40D521C373B14B2BBB4B229BA20BA32B9B125875C8F05CCADCE8E4F2A0DED2DCE8E6A6047100000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF400000071200000190000000660A4AC09208D1150C3E53B8F0F348D3301131102CDB01056D00D97EF3CBE1011C0448440332CC41739CC863403D21816300D97EF3CFEE20083D83CD4E417B76D03D070F9CEE34B00F32C845FDCB60954C3E53B8F2F4D4E44A0D4F450935FDC3600000061200000490000001304412C100000000B00000034470088CC0014422994EC40C10E94244471141E9512A03787A07873085F427204600E22499287C60C0000002306080082604089C13278CF8801028020185063C00C1F34629000200806061A3C1F1860D088410280201818690081411824D1884102802018186A1085811864D2884102802018186B2089C11874D3884102802018186C309D011970D4884102802018186D40A1411934D588C101802018406B2021CD88C101802018406C3021CD88C103802018346E100901312C0B1AA001B68C2604C06882108C260CC268023118D1C8C788463E4634F231A291CF8841028020182078F0C5411CA40131629000200806081E7C711007D930629000200806081E7C71100768208C18240008820182075F1CC4C11A0408000000000000" ]; @@ -82,7 +83,7 @@ float4 PSMain(VSOutput input) : SV_TARGET // Vertex Shader - Linear "030223070006010000002800750000000000000011000200010000000E00030000000000010000000F000C00000000000200000056534D61696E0000500000005400000057000000150000002B000000310000001C000000030003000B00000001000000050009000B000000436F6E7374616E74735F7374643134305F6C6F676963616C00000000060006000B0000000000000050726F6A656374696F6E00000500060015000000696E7075742E506F736974696F6E0000050007001A000000436F6E7374616E74735F73746431343000000000060006001A0000000000000050726F6A656374696F6E00000500070019000000476C6F62616C506172616D735F73746431343000060006001900000000000000636F6E7374616E7473000000050006001C000000676C6F62616C506172616D7300000000050005002B000000696E7075742E5556000000000500050031000000696E7075742E436F6C6F72000500090054000000656E747279506F696E74506172616D5F56534D61696E2E555600000005000A0057000000656E747279506F696E74506172616D5F56534D61696E2E436F6C6F7200000000050004000200000056534D61696E000047000400150000001E00000000000000480005001A000000000000002300000000000000480004001A0000000000000005000000480005001A0000000000000007000000100000004700030019000000020000004800050019000000000000002300000000000000470004001C0000002100000000000000470004001C0000002200000000000000470004002B0000001E0000000100000047000400310000001E0000000200000047000400500000000B0000000000000047000400540000001E0000000000000047000400570000001E0000000100000013000200010000002100030003000000010000001600030006000000200000001700040007000000060000000400000017000400080000000600000002000000180004000C00000007000000040000001E0003000B0000000C000000150004000F00000020000000010000002B0004000F0000001000000000000000200004001400000001000000080000002B0004000600000017000000000000002B00040006000000180000000000803F1E0003001A0000000C0000001E000300190000001A000000200004001B0000000200000019000000200004001D000000020000001A00000020000400300000000100000007000000170004003300000006000000030000002B000400060000003B00000012519C3E2B000400060000003D000000C4A22E3F2B0004000600000041000000C22C4D3C200004004F0000000300000007000000200004005300000003000000080000003B0004001400000015000000010000003B0004001B0000001C000000020000003B000400140000002B000000010000003B0004003000000031000000010000003B0004004F00000050000000030000003B0004005300000054000000030000003B0004004F00000057000000030000002C000600330000006D0000003D0000003D0000003D0000002C000600330000006E0000004100000041000000410000003600050001000000020000000000000003000000F8000200040000003D000400080000001300000015000000500006000700000016000000130000001700000018000000410005001D0000001E0000001C000000100000003D0004001A0000001F0000001E000000900104000B000000200000001F000000510005000C0000006C00000020000000000000009100050007000000250000006C000000160000003D000400080000002A0000002B0000003D000400070000002F000000310000004F00080033000000340000002F0000002F0000000000000001000000020000008E000500330000005D000000340000003B00000081000500330000005F0000005D0000006D000000850005003300000060000000340000005F000000810005003300000062000000600000006E00000085000500330000006300000034000000620000005100050006000000470000006300000000000000520006000700000070000000470000002F00000000000000510005000600000049000000630000000100000052000600070000007200000049000000700000000100000051000500060000004B00000063000000020000005200060007000000740000004B00000072000000020000003E00030050000000250000003E000300540000002A0000003E0003005700000074000000FD00010038000100", - // Pixel Shader + // Fragment Shader "0302230700060100000028001E0000000000000011000200010000000E00030000000000010000000F000A00040000000200000050534D61696E000011000000150000001D0000000D00000009000000100003000200000007000000030003000B000000010000000500050009000000696E7075742E436F6C6F7200050005000D000000696E7075742E55560000000005000400110000007465787475726500050004001500000073616D706C657200050006001800000073616D706C6564496D61676500000000050004001900000073616D706C656400050008001D000000656E747279506F696E74506172616D5F50534D61696E0000050004000200000050534D61696E000047000400090000001E00000001000000470004000D0000001E0000000000000047000400110000002100000001000000470004001100000022000000000000004700040015000000210000000200000047000400150000002200000000000000470004001D0000001E0000000000000013000200010000002100030003000000010000001600030005000000200000001700040006000000050000000400000020000400080000000100000006000000170004000A0000000500000002000000200004000C000000010000000A000000190009000E000000050000000100000002000000000000000000000001000000000000002000040010000000000000000E0000001A00020012000000200004001400000000000000120000001B000300170000000E000000200004001C00000003000000060000003B0004000800000009000000010000003B0004000C0000000D000000010000003B0004001000000011000000000000003B0004001400000015000000000000003B0004001C0000001D000000030000003600050001000000020000000000000003000000F8000200040000003D0004000600000007000000090000003D0004000A0000000B0000000D0000003D0004000E0000000F000000110000003D0004001200000013000000150000005600050017000000180000000F00000013000000570006000600000019000000180000000B0000000000000085000500060000001B00000007000000190000003E0003001D0000001B000000FD00010038000100" ]; @@ -94,98 +95,77 @@ float4 PSMain(VSOutput input) : SV_TARGET // Vertex Shader - Linear "4D544C4201800200070000810E000000881300000000000058000000000000008A00000000000000160100000000000039000000000000004F0100000000000008000000000000005701000000000000A00E000000000000010000008A0000004E414D45070056534D61696E005459504501000048415348200018279E3BBD163C92118F96D26DD622BB504BD02519E79C64165EE9EDB89C0D444F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800A00E00000000000052464C5408000400000000000000454E4454524C53541000F70F0000000000009103000000000000555549441000C24CC1BA27A533429FAEB8AF316BA7F7454E4454390000005641545420000300506F736974696F6E5F3100008055565F31000180436F6C6F725F310002805641545905000300040406454E445408000000454E4454DEC0170B0000000014000000840E0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000680300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C800000089200000270000003222480920624600212B249814212524981419270C85A4906052645C20246582609A0118462080610401B843104109C24CD43CD0833CD4C338D0831BB44339D04338B0831EE8413B84033DC8433AE0430A084AD21451C2E4734E234D4033493F9D82829534459430F9E0E204C0920211C0483F9D0233887008A6184204446920608E000CE6084061102110861108661861608611066004000000000051180000630000001BFA23F8FFFFFFFF013005C00F003800FE00908009A0803E20C2011EE0411EDE011FDAC01CEAC11DC6A10DCC011EDAA01DC2811ED001A00779A8877200087390877068877268037878877470077A28077900C2811DD80120DA211DDCA10DD8A11CCE211CD8A10DECA11CC6811EDE411EDAE01ED2811CE8011D803890033C00067778873610877A480776A08774708779000877788736480777308779680373808736688770A0077400E8411EEAA11C00C21DDEA10DDC211CDC611EDAC01CE0A10DDA211CE8011D007A90877A280780708777688379488773708772208736D0877290877798873630077868837608077A4007801EE4A11ECA0120DCE11DDA801EE4211CE0011ED2C11DCEA10DDA211CE8011D007A90877A28078098077A08877158873680077978077A288771A0877790873610877A30077328077968837948077D2807000F00A21EDC611EC2C11CCAA10DCC011EDAA01DC2811ED001A00779A8877200367402012C002900D5100EE9200F6D200EF5600EE6500EF2D006EEF00E6D100EEC900EE1300F00000049180000010000001384400013B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69BCAC0920CD900A30120000020000000100000000000390D82050546D0000200B0400000A000000321E981019114C908C092647C6044362255008E55004E5538002055120C530025006051800000000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A80778980700000000792000001D010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A398066400560000004A63611BB44171506C1C193091C128916310CB01490A7128484421CCB24497E30000000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E706F736974696F6E6169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D65506F736974696F6E5F306169722E7665727465785F6F75747075747573657228544558434F4F524429666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E7665727465785F696E7075746169722E6C6F636174696F6E5F696E646578506F736974696F6E5F3155565F31436F6C6F725F316169722E6275666665726169722E726561646169722E616464726573735F73706163656169722E7374727563745F747970655F696E666F666C6F617434783450726F6A656374696F6E5F30436F6E7374616E74735F30636F6E7374616E74735F306169722E6172675F747970655F73697A656169722E6172675F747970655F616C69676E5F73697A65476C6F62616C506172616D735F30676C6F62616C506172616D735F3026760000000000003082C00423088C3082C00C23080C3182C01423088C3182C01C23080C3282C02423088C3282C02C23080A3082C030330C6A10ACC10C031B086D30C3C006831BCC30B001E106330C6C50B8C10C031B186F30C3C006071CCC30B00112073304C90C431BC8C11CCC402874A006733043B0CC10303304CD0C85F34091348331514F15593318D3F5401136C3500AA6700A332473906973A0064F156D33246A90696AA0064F157133246D90696DA0060F147533147360077320066330C31106AC3007763007645006334874E06973A0061F18B44118B48219D8C119C4C18306511ACC40A4822AAC822BCC30D4012ABCC29D01C0711CC7711CC7711CC7B9811BB8811BB8811BB8811B5874A0079665B9011DD0011DE0022EE0022B90836A80828C042628233636BB3697B637B23AB6321733B6B0B3B951103BB8033CC8033DD8033EE88354D8D8ECDA5CD2C8CADCE84609FC2097B0343917BB32B9B9B437B751823F482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D940014720A4B9373197B6B834B632BFB7A83A34B7B739B1B65080551188554C2D2E45CECCAE4E8CAF046095E01000000A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000500000001304412C1000000018000000C46600A88D00944019D01B01188B08822098835002221A8B0002E12038D600040285D100123300346600288E35C8288DA79F8CD278FAC9288DA7DF580369A3BDFC9136DACB1F69A3BDFC8D3588E6AA939E68AE3AE989E6AA93DE680000000000F1300000140000002247C890510A841C00000000980500006169722D616C6961732D73636F7065732856534D61696E296169722D616C6961732D73636F70652D61726728332900002B8459888515032DCC822C6C08680100FD18D1735DD7434128830C16E2A010887F3F46356DDB265110CA2083C6442804E28F4101FEFD181A06066000061605A1E01080FF208337511844E28F4104FE180CE28F8104FE1814E23FC8104CDC20435006DC6C439600B30D011A04B30DC1206C101003040000005B86E0A0852D4371D0C2964139680100000000007120000003000000320E1022840098060000000000000000650C00001F000000120394F0000000000300000006000000090000004C000000010000005800000000000000580000000100000070000000000000000F0000001C00000000000000060000000600000000000000700000000000000000000000010000000000000000000000060000000000000006000000FFFFFFFF00240000000000005D0C00000E0000001203946B0000000056534D61696E33323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000000000000000000010000008D030000524255467D03000000000000000000000018000000414952521000180004001000000000000000140010000000000000000400000000000000100000000400000001000000000000000A000000E00200009002000034020000F0010000AC0100007C0100004C010000D40000006800000004000000F4FDFFFF01000800040000009EFFFFFF04000000010000001400000010001000000000000400000008000C0010000000400000001C000000040000000C00000050726F6A656374696F6E5F300000000008000000666C6F617434783400000000B0FDFFFF010008000C0000000000060008000400060000000400000001000000140000001000140004000000080000000C00100010000000090000004000000018000000040000000B000000636F6E7374616E74735F30000B000000436F6E7374616E74735F3000BCFEFFFF00000400200000001C0024000000000008000C000600070010000000140018001C0020001C0000000000010200000000010000000800000040000000100000001C000000040000000E000000676C6F62616C506172616D735F3000000E000000476C6F62616C506172616D735F30000030FFFFFF0420040004000000A6FFFFFF0200000001000000500100000400000007000000436F6C6F725F31005CFFFFFF0420040004000000D2FFFFFF0100000001000000CC000000040000000400000055565F3100000000E4FEFFFF042004001400000000000E0014000000040008000C0010000E000000000000000100000090000000040000000A000000506F736974696F6E5F310000C8FFFFFF0400020004000000C0FFFFFF18000000BC0000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290008000C000400080008000000040002001400000010001000000004000000000008000C00100000002400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000BCFFFFFF020002001400000000000E000C00000000000000040008000E00000018000000040000000A000000506F736974696F6E5F30000006000000666C6F617434000008000E000400080008000000030000001000000000000A001000040008000C000A000000300000001C000000040000000400000004000000050000000600000007000000030000000100000002000000030000000600000056534D61696E0000454E4454", - // Pixel Shader + // Fragment Shader "4D544C4201800200070000810E000000D81100000000000058000000000000008A00000000000000160100000000000008000000000000001E0100000000000008000000000000002601000000000000000E000000000000010000008A0000004E414D45070050534D61696E00545950450100014841534820008335EA1F82646DCCC623827DA6EBF0258B1E224AA4218D60D70CC077494E05494F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800000E00000000000052464C5408000400000000000000454E4454524C53541000260F000000000000B202000000000000555549441000C710A16106E43B598CE29DE59990D629454E445408000000454E445408000000454E4454DEC0170B0000000014000000EC0D0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000350300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C8000000892000001F0000003222480920624600212B249814212524981419270C85A4906052645C20246582609A01184620801B84610401404A9A224A98FC7F22AE898A88DF1EFE698C00184420028CA429A284C9FF2580791622FAA731026010C1108C214608E5109A23408E10D41C4130470006C30842639455CE608E01D0E80D048C0000000051180000670000001BF623F8FFFFFFFF015803C014003F002460022AA00F8870800778908777C0873630877A708771680373808736688770A0077400E8411EEAA11C00C21CE4211CDAA11CDA001EDE211DDC811ECA411E807060077600887648077768037628877308077668037B288771A08777908736B8877420077A4007200EE4000F80C11DDEA10DC4A11ED2811DE8211DDC611E00C21DDEA10DD2C11DCC611EDAC01CE0A10DDA211CE8011D007A90877A28078070877768037708077798873630077868837608077A4007801EE4A11ECA0120DCE11DDA601ED2E11CDCA11CC8A10DF4A11CE4E11DE6A10DCC011EDAA01DC2811ED001A00779A88772000877788736A0077908077880877470877368837608077A4007801EE4A11ECA0120E6811EC2611CD6A10DE0411EDE811ECA611CE8E11DE4A10DC4A11ECCC11CCA411EDA601ED2411FCA01C00380A80777988770308772680373808736688770A0077400E8411EEAA11C800D843000A4B0011AFEFFFFFF7F00DA005803C014003F0024A002FA60834104C002541B8C420016A0DAC018FFFFFFFF3F006D00AC01200115D00700491800000300000013844098300C44316130880213B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69A4AC0920CD900A301000000200000001000000000003185219DB0304800000002000000000006000121B048ADE0C000064810009000000321E981019114C908C092647C604436A255008E55004E5538002055120C5300250060000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A807789807000000007920000008010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A3680660604F0000004A63611BB44171506C1C194491C12092A33C06B11C8CA4388BA460C97224000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E72656E6465725F7461726765746169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D656F75747075745F306169722E667261676D656E745F696E7075747573657228544558434F4F5244296169722E63656E7465726169722E7065727370656374697665666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E706F736974696F6E6169722E6E6F5F7065727370656374697665506F736974696F6E5F306169722E6172675F756E757365646169722E746578747572656169722E6C6F636174696F6E5F696E6465786169722E73616D706C657465787475726532643C666C6F61742C2073616D706C653E746578747572655F316169722E73616D706C657273616D706C657273616D706C65725F31000026630000000000003082D0082308CD3082D01023084D3182D0182308CD3182D02023084D3282D0282308CD3282D03023080930C37006011ACC30A481A006330C6930ACC10C431A106B30C39006C51ACC30A481C106330C6970B4C10C431A206E304390CC30A8C11BC0C10C8412076700073304CB0C013343D0CC70387000070F14493304A130430207135559CF1561332467306595F540913643A2065BC53D50D479332871F081011C9C41183C62108DC10C091A900118C0C1193C651099C10CC52890422998C229CC30C88128A0C28D01C0711CC7711CC7711CE7066EE0066EE0066EE0066E60D1811E5896650A1C2BB0022BD8033CB0828C042628233636BB3697B637B23AB6321733B6B0B3B951103998033AA8033BB8033CC88354D8D8ECDA5CD2C8CADCE84609F42097B0343917BB32B9B9B437B751823D482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D94800F720A4B9373197B6B834B632BFB7A83A34B7B739B1B65E8033FF88364C2D2E45CCCE4C2CEDACADCE84609500100A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000310000001304412C1000000004000000C46A600480DC08008111001233000000F13000001C0000002247C890510E042B00000000188601006169722D616C6961732D73636F7065732850534D61696E296169722D616C6961732D73636F70652D73616D706C6572736169722D616C6961732D73636F70652D74657874757265732B8456508515832BB4C22AAC185EA115586183F00AAE00002306CD108260F05887A114032108CC68420060B088FF6C0311001B04C4000000020000005B06E07805000000000000007120000003000000320E1022840084060000000000000000650C00002500000012039428010000000300000021000000090000004C000000010000005800000000000000580000000200000088000000000000002A0000001C00000000000000060000000600000000000000880000000000000000000000020000000000000000000000060000000000000006000000FFFFFFFF00240000060000001B000000060000001B000000FFFFFFFF08240000000000005D0C000015000000120394A60000000050534D61696E6169722E73616D706C655F746578747572655F32642E763466333233323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000001000000AE020000524255469E02000000000000000000000000140000004149525200000A0018000400100014000A000000000000000400000000000000100000000400000001000000000000000700000008020000AC0100004801000008010000C000000054000000040000001CFEFFFF010004001400000000000E0014000000040008000C0010000E000000000000000100000018000000040000000900000073616D706C65725F310000000700000073616D706C65720068FEFFFF0200040018000000000012001800000008000C00070000001000140012000000000000040000000001000000180000000400000009000000746578747572655F31000000180000007465787475726532643C666C6F61742C2073616D706C653E00000000C4FFFFFF02300400140000001000100000000000000008000C000700100000000000000100010000040000000A000000506F736974696F6E5F30000008000C0004000800080000000030040004000000C2FFFFFF0000010118000000C80000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290050FFFFFF0030040018000000000012001400000008000000060007000C00100012000000000001012400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000B0FFFFFF0010020018000000000012001400000004000800000000000C0010001200000000000000000000001800000004000000080000006F75747075745F300000000006000000666C6F617434000008000E000400080008000000010000001000000000000A001000040008000C000A0000002C000000200000000400000005000000020000000300000004000000050000000600000001000000010000000600000050534D61696E0000454E4454" ]; - private readonly ResourceBinding[] ResourceBindings = - [ - new() { Type = ResourceType.ConstantBuffer, Count = 1 }, - new() { Type = ResourceType.Texture, Count = 1 }, - new() { Type = ResourceType.Sampler, Count = 1 } - ]; - - private readonly Buffer constants; private readonly Sampler sampler; private readonly GraphicsPipeline graphicsPipeline; private readonly Dictionary textureBindings = []; private readonly Dictionary textureViewBindings = []; - private readonly Dictionary resourceTableBindings = []; + private readonly SortedList resourceHandleBindings = []; private readonly Dictionary drawDataTextures = []; private Buffer? vertexBuffer; private Buffer? indexBuffer; + private Buffer? constantBuffer; - public ImGuiRenderer(GraphicsContext context, Output output, ImGuiColorSpace colorSpace) + public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormats, ImGuiColorSpace colorSpace) { - byte[] vertexShaderBytes = []; - byte[] pixelShaderBytes = []; - switch (context.Backend) + byte[] vertexBytecode = []; + byte[] fragmentBytecode = []; + switch (context.GraphicsApi) { - case Backend.DirectX12: - vertexShaderBytes = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Dxil[0] : Dxil[1]); - pixelShaderBytes = Convert.FromHexString(Dxil[2]); + case GraphicsApi.DirectX12: + vertexBytecode = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Dxil[0] : Dxil[1]); + fragmentBytecode = Convert.FromHexString(Dxil[2]); break; - case Backend.Vulkan: - vertexShaderBytes = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Spirv[0] : Spirv[1]); - pixelShaderBytes = Convert.FromHexString(Spirv[2]); + case GraphicsApi.Vulkan: + vertexBytecode = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Spirv[0] : Spirv[1]); + fragmentBytecode = Convert.FromHexString(Spirv[2]); break; - case Backend.Metal: - vertexShaderBytes = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Metallib[0] : Metallib[1]); - pixelShaderBytes = Convert.FromHexString(Metallib[2]); + case GraphicsApi.Metal: + vertexBytecode = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Metallib[0] : Metallib[1]); + fragmentBytecode = Convert.FromHexString(Metallib[2]); break; } using Shader vertex = context.CreateShader(new() { - ShaderBytes = vertexShaderBytes, + Bytecode = vertexBytecode, EntryPoint = "VSMain", - Stage = ShaderStageFlags.Vertex + Stage = ShaderStages.Vertex }); - using Shader pixel = context.CreateShader(new() + using Shader fragment = context.CreateShader(new() { - ShaderBytes = pixelShaderBytes, + Bytecode = fragmentBytecode, EntryPoint = "PSMain", - Stage = ShaderStageFlags.Pixel + Stage = ShaderStages.Fragment }); - constants = context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite - }); - - sampler = context.CreateSampler(new() - { - U = AddressMode.Clamp, - V = AddressMode.Clamp, - W = AddressMode.Clamp, - Filter = Filter.MinPointMagPointMipPoint - }); + sampler = context.CreateSampler(SamplerDesc.PointClamp()); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.Position }); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.TexCoord }); - inputLayout.Add(new() { Format = ElementFormat.UByte4Normalized, Semantic = ElementSemantic.Color }); + inputLayout.Add(new() { Format = ElementFormat.UByte4, Semantic = ElementSemantic.Color }); graphicsPipeline = context.CreateGraphicsPipeline(new() { - RenderStates = new() + VertexShader = vertex, + FragmentShader = fragment, + InputLayouts = [inputLayout], + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = attachmentFormats, + RenderState = new() { RasterizerState = RasterizerState.CullNone(), DepthStencilState = DepthStencilState.DepthNone(), BlendState = BlendState.AlphaBlend() - }, - Vertex = vertex, - Pixel = pixel, - ResourceBindings = ResourceBindings, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = output + } }); Context = context; @@ -198,17 +178,12 @@ public ImTextureID Binding(Texture texture) if (!textureBindings.TryGetValue(texture, out ImTextureID textureID)) { ulong id = 0; - while (resourceTableBindings.ContainsKey(id)) + while (resourceHandleBindings.ContainsKey(id)) { id++; } - ResourceTable resourceTable = Context.CreateResourceTable(new() { Bindings = ResourceBindings }); - resourceTable.Write(0, constants); - resourceTable.Write(1, texture); - resourceTable.Write(2, sampler); - - resourceTableBindings[textureBindings[texture] = textureID = id] = resourceTable; + resourceHandleBindings[textureBindings[texture] = textureID = id] = texture.SampledHandle; } return textureID; @@ -219,17 +194,12 @@ public ImTextureID Binding(TextureView textureView) if (!textureViewBindings.TryGetValue(textureView, out ImTextureID textureID)) { ulong id = 0; - while (resourceTableBindings.ContainsKey(id)) + while (resourceHandleBindings.ContainsKey(id)) { id++; } - ResourceTable resourceTable = Context.CreateResourceTable(new() { Bindings = ResourceBindings }); - resourceTable.Write(0, constants); - resourceTable.Write(1, textureView); - resourceTable.Write(2, sampler); - - resourceTableBindings[textureViewBindings[textureView] = textureID = id] = resourceTable; + resourceHandleBindings[textureViewBindings[textureView] = textureID = id] = textureView.SampledHandle; } return textureID; @@ -252,18 +222,11 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, { case ImTextureStatus.WantCreate: { - Texture texture = Context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = textureData.Format is ImTextureFormat.Rgba32 ? PixelFormat.R8G8B8A8UNorm : PixelFormat.R8UNorm, - Width = (uint)textureData.Width, - Height = (uint)textureData.Height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.ShaderResource - }); + Texture texture = Context.CreateTexture(TextureDesc.Texture2D(textureData.Format is ImTextureFormat.Rgba32 ? PixelFormat.R8G8B8A8UNorm : PixelFormat.R8UNorm, + (uint)textureData.Width, + (uint)textureData.Height, + 1, + SampleCount.Count1)); Extent3D extent = new() { Width = (uint)textureData.Width, Height = (uint)textureData.Height, Depth = 1 }; @@ -275,8 +238,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, Layout = new() { SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), - RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), - SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) } }; @@ -290,8 +253,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, Layout = new() { SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), - RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), - SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) } }; @@ -333,8 +296,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, Layout = new() { SizeInBytes = (uint)(sizeof(int) * rect.W * rect.H), - RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), - SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) } }; @@ -355,8 +318,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, Layout = new() { SizeInBytes = (uint)(rect.W * rect.H), - RowPitchInBytes = ZenithHelper.RowPitchInBytes(texture.Desc.Format, extent.Width, extent.Height), - SlicePitchInBytes = ZenithHelper.SlicePitchInBytes(texture.Desc.Format, extent.Width, extent.Height) + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) } }; @@ -389,25 +352,21 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, if (vertexBuffer is null || vertexBuffer.Desc.SizeInBytes < totalVertexSizeInBytes) { vertexBuffer?.Dispose(); - - vertexBuffer = Context.CreateBuffer(new() - { - SizeInBytes = totalVertexSizeInBytes, - StrideInBytes = (uint)sizeof(ImDrawVert), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite - }); + vertexBuffer = Context.CreateBuffer(BufferDesc.Vertex(totalVertexSizeInBytes, (uint)sizeof(ImDrawVert))); } uint totalIndexSizeInBytes = (uint)(sizeof(ushort) * (int)(drawData.TotalIdxCount * 1.2)); if (indexBuffer is null || indexBuffer.Desc.SizeInBytes < totalIndexSizeInBytes) { indexBuffer?.Dispose(); - indexBuffer = Context.CreateBuffer(new() - { - SizeInBytes = totalIndexSizeInBytes, - StrideInBytes = sizeof(ushort), - Flags = BufferUsageFlags.Index | BufferUsageFlags.MapWrite - }); + indexBuffer = Context.CreateBuffer(BufferDesc.Index(totalIndexSizeInBytes, sizeof(ushort))); + } + + uint constantBufferSizeInBytes = (uint)(sizeof(Constant) * (int)(resourceHandleBindings.Count * 1.2)); + if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < constantBufferSizeInBytes) + { + constantBuffer?.Dispose(); + constantBuffer = Context.CreateBuffer(BufferDesc.Constant(constantBufferSizeInBytes)); } for (int i = 0, vertexOffset = 0, indexOffset = 0; i < drawData.CmdListsCount; i++) @@ -421,17 +380,30 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, indexOffset += drawListPtr.IdxBuffer.Size; } - Constants constantsData = new() + Constant[] constants = ArrayPool.Shared.Rent(resourceHandleBindings.Count); + + Constant constant = new() { Projection = Matrix4x4.CreateOrthographicOffCenter(drawData.DisplayPos.X, drawData.DisplayPos.X + drawData.DisplaySize.X, drawData.DisplayPos.Y + drawData.DisplaySize.Y, drawData.DisplayPos.Y, 0.0f, - 1.0f) + 1.0f), + Sampler = sampler.Handle }; - constants.Upload(0, new() { Pointer = (nint)(&constantsData), SizeInBytes = (uint)sizeof(Constants) }); + for (int i = 0; i < resourceHandleBindings.Count; i++) + { + constants[i] = constant with { Texture = resourceHandleBindings[i] }; + } + + fixed (Constant* pointer = constants) + { + constantBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Constant) * resourceHandleBindings.Count) }); + } + + ArrayPool.Shared.Return(constants); commandBuffer.BeginDebugEvent("ImGui"); @@ -473,7 +445,7 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, } commandBuffer.SetScissors([scissor]); - commandBuffer.PushResourceTable(resourceTableBindings[drawCmd.TexRef.GetTexID()]); + commandBuffer.SetConstantBuffer(constantBuffer, (uint)(sizeof(Constant) * resourceHandleBindings.IndexOfKey(drawCmd.TexRef.GetTexID()))); commandBuffer.DrawIndexed(drawCmd.ElemCount, 1, (uint)(drawCmd.IdxOffset + indexOffset), (int)(drawCmd.VtxOffset + vertexOffset), 0); } } @@ -489,6 +461,7 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, protected override void Destroy() { + constantBuffer?.Dispose(); indexBuffer?.Dispose(); vertexBuffer?.Dispose(); @@ -498,22 +471,17 @@ protected override void Destroy() } drawDataTextures.Clear(); - foreach (ResourceTable resourceTable in resourceTableBindings.Values) - { - resourceTable.Dispose(); - } - resourceTableBindings.Clear(); + resourceHandleBindings.Clear(); textureViewBindings.Clear(); textureBindings.Clear(); graphicsPipeline.Dispose(); sampler.Dispose(); - constants.Dispose(); } private void RemoveDestroyedBindings() { - ImTextureID[] destroyedTextureIDs = [.. resourceTableBindings.Keys.Where(textureID => + ImTextureID[] destroyedTextureIDs = [.. resourceHandleBindings.Keys.Where(textureID => { if (textureBindings.FirstOrDefault(kv => kv.Value == textureID).Key is Texture texture) { @@ -530,10 +498,7 @@ private void RemoveDestroyedBindings() foreach (ImTextureID textureID in destroyedTextureIDs) { - if (resourceTableBindings.Remove(textureID, out ResourceTable? resourceTable)) - { - resourceTable.Dispose(); - } + resourceHandleBindings.Remove(textureID); if (textureViewBindings.FirstOrDefault(kv => kv.Value == textureID).Key is TextureView textureView) { @@ -548,9 +513,15 @@ private void RemoveDestroyedBindings() } } -[StructLayout(LayoutKind.Explicit, Size = 64)] -file struct Constants +[StructLayout(LayoutKind.Explicit, Size = 256)] +file struct Constant { [FieldOffset(0)] public Matrix4x4 Projection; + + [FieldOffset(64)] + public ResourceHandle Texture; + + [FieldOffset(72)] + public ResourceHandle Sampler; } From 9e42c08334e612ac94f1a3f4fd64dd164f9f655c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 18:40:21 +0800 Subject: [PATCH 110/466] Refactor Surface: use primary ctor, update property init Refactored the Surface class to use C# primary constructor syntax and moved property initializations inline. Simplified texture creation with TextureDesc.Texture2D. Updated property initializers for Target, Bitmap, GraphicsContext, Width, and Height. Renamed layout fields in Present method for clarity. --- .../Zenith.NET.Views.Avalonia/Surface.cs | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index ac49fd29..6fd5bdb7 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -4,39 +4,17 @@ namespace Zenith.NET.Views.Avalonia; -internal class Surface : DisposableObject +internal class Surface(GraphicsContext graphicsContext, uint width, uint height) : DisposableObject { - public Surface(GraphicsContext graphicsContext, uint width, uint height) - { - Target = graphicsContext.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.ColorFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.RenderTarget - }); - - Bitmap = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); - - GraphicsContext = graphicsContext; - Width = width; - Height = height; - } - - public Texture Target { get; } + public Texture Target { get; } = graphicsContext.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.ColorFormat, width, height, 1, SampleCount.Count1)); - public WriteableBitmap Bitmap { get; } + public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); - public GraphicsContext GraphicsContext { get; } + public GraphicsContext GraphicsContext { get; } = graphicsContext; - public uint Width { get; } + public uint Width { get; } = width; - public uint Height { get; } + public uint Height { get; } = height; public void Present() { @@ -48,8 +26,8 @@ public void Present() Layout = new() { SizeInBytes = (uint)(lockedFramebuffer.RowBytes * Height), - RowPitchInBytes = (uint)lockedFramebuffer.RowBytes, - SlicePitchInBytes = (uint)(lockedFramebuffer.RowBytes * Height) + RowStrideInBytes = (uint)lockedFramebuffer.RowBytes, + SliceStrideInBytes = (uint)(lockedFramebuffer.RowBytes * Height) } }); } From e630d4045e749df8e13549bd7ebabb39595c9a97 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 20:49:07 +0800 Subject: [PATCH 111/466] Refactor swap chain and texture property naming Renamed swap chain properties for clarity: ColorTargetFormat to ColorFormat, CurrentColorTarget/CurrentColorAttachment to CurrentTexture, and CurrentDepthStencilAttachment to CurrentDepthStencilTexture. Streamlined Surface class initialization and modernized pointer/struct usage in D3D interop. Updated sample description initialization and performed minor code cleanups for consistency across platforms. --- .../Zenith.NET.Views.Avalonia/Surface.cs | 2 - .../Platforms/Android/MauiZenithView.cs | 4 +- .../Platforms/MacCatalyst/MauiZenithView.cs | 4 +- .../Platforms/Windows/D3D.cs | 10 ++--- .../Platforms/Windows/D3DTexture.cs | 8 ++-- .../Platforms/Windows/MauiZenithView.cs | 4 +- .../Platforms/iOS/MauiZenithView.cs | 4 +- sources/Views/Zenith.NET.Views.WPF/D3D.cs | 17 ++++--- .../Views/Zenith.NET.Views.WPF/D3DTexture.cs | 10 ++--- .../Views/Zenith.NET.Views.WPF/ZenithView.cs | 4 +- .../Zenith.NET.Views.WinForms/ZenithView.cs | 4 +- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 44 ++++--------------- .../ZenithView.WinUI.cs | 23 +++++----- sources/Zenith.NET/SwapChain.cs | 4 +- 14 files changed, 55 insertions(+), 87 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 6fd5bdb7..0b11795a 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -10,8 +10,6 @@ internal class Surface(GraphicsContext graphicsContext, uint width, uint height) public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); - public GraphicsContext GraphicsContext { get; } = graphicsContext; - public uint Width { get; } = width; public uint Height { get; } = height; diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs index ea16f5b6..ba0a305f 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs @@ -26,7 +26,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Android(ANativeWindowFromSurface(JniEnvironment.EnvironmentPointer, Holder!.Surface!.Handle), width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -43,7 +43,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); + handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs index 79567caa..3a44e5d4 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -44,7 +44,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); + handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs index 9be621f6..07617f8d 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs @@ -20,16 +20,16 @@ static D3D() Success(DXGI.CreateDXGIFactory2(0, out Factory)); - Success(D3D11.CreateDevice(default(ComPtr), + Success(D3D11.CreateDevice(default, D3DDriverType.Hardware, 0, (uint)CreateDeviceFlag.BgraSupport, - null, + default, 0, D3D11.SdkVersion, - ref Device, - null, - ref DeviceContext)); + Device.GetAddressOf(), + default, + DeviceContext.GetAddressOf())); } public static DXGI DXGI { get; } diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs index 9e17d24a..c65c074b 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs @@ -30,14 +30,14 @@ public D3DTexture(uint width, uint height) Width = width, Height = height, Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + SampleDesc = new(1), BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = 3, Scaling = Scaling.Stretch, SwapEffect = SwapEffect.FlipSequential }; - D3D.Success(D3D.Factory.CreateSwapChainForComposition((IUnknown*)D3D.Device.Handle, &swapChainDesc, (IDXGIOutput*)null, SwapChain.GetAddressOf())); + D3D.Success(D3D.Factory.CreateSwapChainForComposition((IUnknown*)D3D.Device.Handle, &swapChainDesc, default(IDXGIOutput*), SwapChain.GetAddressOf())); Texture2DDesc texture2DDesc = new() { @@ -46,7 +46,7 @@ public D3DTexture(uint width, uint height) MipLevels = 1, ArraySize = 1, Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + SampleDesc = new(1), BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) }; @@ -58,7 +58,7 @@ public D3DTexture(uint width, uint height) using ComPtr resource = Texture.QueryInterface(); void* sharedHandle = null; - D3D.Success(resource.CreateSharedHandle((SecurityAttributes*)null, DXGI.SharedResourceRead | DXGI.SharedResourceWrite, (char*)null, &sharedHandle)); + D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); Handle = (nint)Texture.Handle; SharedHandle = (nint)sharedHandle; diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs index 0d44aa89..ea912b57 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); this.As().SetSwapChain(texture.SwapChain); @@ -44,7 +44,7 @@ public void Tick() texture.AcquireSync(); handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); + handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs index 1dbd74f2..749d1d6e 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -44,7 +44,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentColorTarget); + handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.WPF/D3D.cs b/sources/Views/Zenith.NET.Views.WPF/D3D.cs index cf7bf6f5..f8cfbf14 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3D.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3D.cs @@ -2,7 +2,6 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D11; using Silk.NET.Direct3D9; -using Silk.NET.DXGI; using Format = Silk.NET.Direct3D9.Format; using PresentParameters = Silk.NET.Direct3D9.PresentParameters; @@ -23,7 +22,7 @@ static D3D() D3D9 = D3D9.GetApi(null); D3D11 = D3D11.GetApi(null); - Success(D3D9.Direct3DCreate9Ex(D3D9.SdkVersion, ref D3D9Ex)); + Success(D3D9.Direct3DCreate9Ex(D3D9.SdkVersion, D3D9Ex.GetAddressOf())); PresentParameters present = new() { @@ -40,19 +39,19 @@ static D3D() 0, D3D9.CreateHardwareVertexprocessing | D3D9.CreateMultithreaded | D3D9.CreatePuredevice | D3D9.CreateFpuPreserve, &present, - (Displaymodeex*)null, - ref D3D9DeviceEx)); + default(Displaymodeex*), + D3D9DeviceEx.GetAddressOf())); - Success(D3D11.CreateDevice(default(ComPtr), + Success(D3D11.CreateDevice(default, D3DDriverType.Hardware, 0, (uint)CreateDeviceFlag.BgraSupport, - null, + default, 0, D3D11.SdkVersion, - ref D3D11Device, - null, - ref D3D11DeviceContext)); + D3D11Device.GetAddressOf(), + default, + D3D11DeviceContext.GetAddressOf())); } public static D3D9 D3D9 { get; } diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs index 03615e07..a51a9301 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs @@ -38,10 +38,10 @@ public D3DTexture(uint width, uint height, D3DImage image) D3D9.UsageRendertarget, D3D9Format.A8R8G8B8, Pool.Default, - ref D3D9RenderTarget, + D3D9RenderTarget.GetAddressOf(), &sharedHandle)); - D3D.Success(D3D9RenderTarget.GetSurfaceLevel(0, ref D3D9RenderSurface)); + D3D.Success(D3D9RenderTarget.GetSurfaceLevel(0, D3D9RenderSurface.GetAddressOf())); D3D.Success(D3D.D3D11Device.OpenSharedResource(sharedHandle, out D3D9SharedTexture)); Texture2DDesc desc = new() @@ -51,19 +51,19 @@ public D3DTexture(uint width, uint height, D3DImage image) MipLevels = 1, ArraySize = 1, Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + SampleDesc = new(1), BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) }; - D3D.Success(D3D.D3D11Device.CreateTexture2D(&desc, null, ref D3D11RenderTarget)); + D3D.Success(D3D.D3D11Device.CreateTexture2D(&desc, default(SubresourceData*), D3D11RenderTarget.GetAddressOf())); D3D.Success(D3D11RenderTarget.QueryInterface(out D3D11Mutex)); using ComPtr resource = D3D11RenderTarget.QueryInterface(); sharedHandle = null; - D3D.Success(resource.CreateSharedHandle((SecurityAttributes*)null, DXGI.SharedResourceRead | DXGI.SharedResourceWrite, (char*)null, &sharedHandle)); + D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); SharedHandle = (nint)sharedHandle; diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index 3ecfe8f5..114878f1 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -111,7 +111,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); } } @@ -126,7 +126,7 @@ void IZenithView.Tick() texture.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentColorTarget)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentTexture)); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs index 11b6de49..d3140aaa 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs @@ -81,7 +81,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.Win32(Handle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -98,7 +98,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentColorTarget)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentTexture)); } void IZenithView.Present() diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index 79742d16..338bac84 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -50,48 +50,20 @@ void IZenithView.ReleaseResources() } } -internal unsafe class Surface : DisposableObject +internal unsafe class Surface(GraphicsContext context, uint width, uint height) : DisposableObject { - private readonly byte[] pixels; + private readonly byte[] pixels = new byte[width * height * 4]; - public Surface(GraphicsContext context, uint width, uint height) - { - pixels = new byte[width * height * 4]; - - Target = context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.ColorFormat, - Width = width, - Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.RenderTarget - }); - - Bitmap = new((int)width, (int)height); - - Context = context; - Width = width; - Height = height; - } + public Texture Target { get; } = context.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.ColorFormat, width, height, 1, SampleCount.Count1)); - public Texture Target { get; } + public WriteableBitmap Bitmap { get; } = new((int)width, (int)height); - public WriteableBitmap Bitmap { get; } + public uint Width { get; } = width; - public GraphicsContext Context { get; } - - public uint Width { get; } - - public uint Height { get; } + public uint Height { get; } = height; public void Present() { - uint rowPitchInBytes = Width * 4; - fixed (byte* pPixels = pixels) { Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() @@ -100,8 +72,8 @@ public void Present() Layout = new() { SizeInBytes = (uint)pixels.Length, - RowPitchInBytes = rowPitchInBytes, - SlicePitchInBytes = (uint)pixels.Length + RowStrideInBytes = Width * 4, + SliceStrideInBytes = (uint)pixels.Length } }); } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 2daa9f8b..c93f3f43 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -33,7 +33,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorTargetFormat = ZenithViewHelper.ColorFormat + ColorFormat = ZenithViewHelper.ColorFormat }); this.As().SetSwapChain(texture.SwapChain); @@ -50,7 +50,7 @@ void IZenithView.Tick() texture.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentColorTarget)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentTexture)); texture.ReleaseSync(); } @@ -86,16 +86,16 @@ static D3D() Success(DXGI.CreateDXGIFactory2(0, out Factory)); - Success(D3D11.CreateDevice(default(ComPtr), + Success(D3D11.CreateDevice(default, D3DDriverType.Hardware, 0, (uint)CreateDeviceFlag.BgraSupport, - null, + default, 0, D3D11.SdkVersion, - ref Device, - null, - ref DeviceContext)); + Device.GetAddressOf(), + default, + DeviceContext.GetAddressOf())); } public static DXGI DXGI { get; } @@ -110,7 +110,6 @@ public static void Success(int result) } } } - internal unsafe partial class D3DTexture : DisposableObject { [LibraryImport("kernel32")] @@ -135,14 +134,14 @@ public D3DTexture(uint width, uint height) Width = width, Height = height, Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + SampleDesc = new(1), BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = 3, Scaling = Scaling.Stretch, SwapEffect = SwapEffect.FlipSequential }; - D3D.Success(D3D.Factory.CreateSwapChainForComposition((IUnknown*)D3D.Device.Handle, &swapChainDesc, (IDXGIOutput*)null, SwapChain.GetAddressOf())); + D3D.Success(D3D.Factory.CreateSwapChainForComposition((IUnknown*)D3D.Device.Handle, &swapChainDesc, default(IDXGIOutput*), SwapChain.GetAddressOf())); Texture2DDesc texture2DDesc = new() { @@ -151,7 +150,7 @@ public D3DTexture(uint width, uint height) MipLevels = 1, ArraySize = 1, Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + SampleDesc = new(1), BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) }; @@ -163,7 +162,7 @@ public D3DTexture(uint width, uint height) using ComPtr resource = Texture.QueryInterface(); void* sharedHandle = null; - D3D.Success(resource.CreateSharedHandle((SecurityAttributes*)null, DXGI.SharedResourceRead | DXGI.SharedResourceWrite, (char*)null, &sharedHandle)); + D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); Handle = (nint)Texture.Handle; SharedHandle = (nint)sharedHandle; diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 34cc8269..6ee27303 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -6,9 +6,9 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public ref readonly SwapChainDesc Desc => ref desc; - public abstract Texture CurrentColorAttachment { get; } + public abstract Texture CurrentTexture { get; } - public abstract Texture? CurrentDepthStencilAttachment { get; } + public abstract Texture? CurrentDepthStencilTexture { get; } public abstract CommandSubmission Acquire(); From b4d3ad900bf29532a7ff34ded99d1a3c4e93fda8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 21:18:42 +0800 Subject: [PATCH 112/466] Initialize ComPtr fields and update Silk.NET interop usage Refactored ComPtr fields to be initialized with new() for safety. Replaced out parameters with SilkMarshal.GuidPtrOf() and GetAddressOf() for DirectX/DXGI interop. Updated QueryInterface and GetBuffer calls to use explicit GUID/address pattern. Wrapped temporary ComPtr variables in using statements for proper disposal, removing redundant Dispose() calls. Improves resource management and aligns with Silk.NET best practices. --- .../Platforms/Windows/D3D.cs | 8 +++--- .../Platforms/Windows/D3DTexture.cs | 16 ++++++------ sources/Views/Zenith.NET.Views.WPF/D3D.cs | 8 +++--- .../Views/Zenith.NET.Views.WPF/D3DTexture.cs | 18 +++++++------ .../ZenithView.WinUI.cs | 25 ++++++++++--------- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs index 07617f8d..a81baf70 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3D.cs @@ -7,18 +7,18 @@ namespace Zenith.NET.Views.Maui.Platforms.Windows; internal static unsafe class D3D { - public static ComPtr Factory; + public static ComPtr Factory = new(); - public static ComPtr Device; + public static ComPtr Device = new(); - public static ComPtr DeviceContext; + public static ComPtr DeviceContext = new(); static D3D() { DXGI = DXGI.GetApi(null); D3D11 = D3D11.GetApi(null); - Success(DXGI.CreateDXGIFactory2(0, out Factory)); + Success(DXGI.CreateDXGIFactory2(0, SilkMarshal.GuidPtrOf(), (void**)Factory.GetAddressOf())); Success(D3D11.CreateDevice(default, D3DDriverType.Hardware, diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs index c65c074b..56161f09 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs @@ -11,11 +11,11 @@ internal unsafe partial class D3DTexture : DisposableObject [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); - public ComPtr SwapChain; + public ComPtr SwapChain = new(); - public ComPtr Texture; + public ComPtr Texture = new(); - public ComPtr Mutex; + public ComPtr Mutex = new(); public nint Handle; @@ -51,9 +51,9 @@ public D3DTexture(uint width, uint height) MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) }; - D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, null, ref Texture)); + D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, default(SubresourceData*), Texture.GetAddressOf())); - D3D.Success(Texture.QueryInterface(out Mutex)); + D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)Mutex.GetAddressOf())); using ComPtr resource = Texture.QueryInterface(); @@ -83,7 +83,9 @@ public void ReleaseSync() public void Present() { - D3D.Success(SwapChain.GetBuffer(0, out ComPtr backBuffer)); + using ComPtr backBuffer = new(); + + D3D.Success(SwapChain.GetBuffer(0, SilkMarshal.GuidPtrOf(), (void**)backBuffer.GetAddressOf())); AcquireSync(); @@ -92,8 +94,6 @@ public void Present() ReleaseSync(); - backBuffer.Dispose(); - D3D.Success(SwapChain.Present(1, 0)); } diff --git a/sources/Views/Zenith.NET.Views.WPF/D3D.cs b/sources/Views/Zenith.NET.Views.WPF/D3D.cs index f8cfbf14..4c8eaa64 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3D.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3D.cs @@ -9,13 +9,13 @@ namespace Zenith.NET.Views.WPF; internal static unsafe class D3D { - public static ComPtr D3D9Ex; + public static ComPtr D3D9Ex = new(); - public static ComPtr D3D9DeviceEx; + public static ComPtr D3D9DeviceEx = new(); - public static ComPtr D3D11Device; + public static ComPtr D3D11Device = new(); - public static ComPtr D3D11DeviceContext; + public static ComPtr D3D11DeviceContext = new(); static D3D() { diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs index a51a9301..dbc9127e 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs @@ -15,15 +15,15 @@ internal unsafe partial class D3DTexture : DisposableObject [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); - public ComPtr D3D9RenderTarget; + public ComPtr D3D9RenderTarget = new(); - public ComPtr D3D9RenderSurface; + public ComPtr D3D9RenderSurface = new(); - public ComPtr D3D9SharedTexture; + public ComPtr D3D9SharedTexture = new(); - public ComPtr D3D11RenderTarget; + public ComPtr D3D11RenderTarget = new(); - public ComPtr D3D11Mutex; + public ComPtr D3D11Mutex = new(); public nint SharedHandle; @@ -42,7 +42,7 @@ public D3DTexture(uint width, uint height, D3DImage image) &sharedHandle)); D3D.Success(D3D9RenderTarget.GetSurfaceLevel(0, D3D9RenderSurface.GetAddressOf())); - D3D.Success(D3D.D3D11Device.OpenSharedResource(sharedHandle, out D3D9SharedTexture)); + D3D.Success(D3D.D3D11Device.OpenSharedResource(sharedHandle, SilkMarshal.GuidPtrOf(), (void**)D3D9SharedTexture.GetAddressOf())); Texture2DDesc desc = new() { @@ -58,9 +58,11 @@ public D3DTexture(uint width, uint height, D3DImage image) D3D.Success(D3D.D3D11Device.CreateTexture2D(&desc, default(SubresourceData*), D3D11RenderTarget.GetAddressOf())); - D3D.Success(D3D11RenderTarget.QueryInterface(out D3D11Mutex)); + D3D.Success(D3D11RenderTarget.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)D3D11Mutex.GetAddressOf())); - using ComPtr resource = D3D11RenderTarget.QueryInterface(); + using ComPtr resource = new(); + + D3D.Success(D3D11RenderTarget.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf())); sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index c93f3f43..c27f59a0 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -73,18 +73,18 @@ void IZenithView.ReleaseResources() internal static unsafe class D3D { - public static ComPtr Factory; + public static ComPtr Factory = new(); - public static ComPtr Device; + public static ComPtr Device = new(); - public static ComPtr DeviceContext; + public static ComPtr DeviceContext = new(); static D3D() { DXGI = DXGI.GetApi(null); D3D11 = D3D11.GetApi(null); - Success(DXGI.CreateDXGIFactory2(0, out Factory)); + Success(DXGI.CreateDXGIFactory2(0, SilkMarshal.GuidPtrOf(), (void**)Factory.GetAddressOf())); Success(D3D11.CreateDevice(default, D3DDriverType.Hardware, @@ -110,16 +110,17 @@ public static void Success(int result) } } } + internal unsafe partial class D3DTexture : DisposableObject { [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); - public ComPtr SwapChain; + public ComPtr SwapChain = new(); - public ComPtr Texture; + public ComPtr Texture = new(); - public ComPtr Mutex; + public ComPtr Mutex = new(); public nint Handle; @@ -155,9 +156,9 @@ public D3DTexture(uint width, uint height) MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) }; - D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, null, ref Texture)); + D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, default(SubresourceData*), Texture.GetAddressOf())); - D3D.Success(Texture.QueryInterface(out Mutex)); + D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)Mutex.GetAddressOf())); using ComPtr resource = Texture.QueryInterface(); @@ -187,7 +188,9 @@ public void ReleaseSync() public void Present() { - D3D.Success(SwapChain.GetBuffer(0, out ComPtr backBuffer)); + using ComPtr backBuffer = new(); + + D3D.Success(SwapChain.GetBuffer(0, SilkMarshal.GuidPtrOf(), (void**)backBuffer.GetAddressOf())); AcquireSync(); @@ -196,8 +199,6 @@ public void Present() ReleaseSync(); - backBuffer.Dispose(); - D3D.Success(SwapChain.Present(1, 0)); } From 759238dc4fb1f33b07341e5826b9824ec5f87b16 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 21:41:38 +0800 Subject: [PATCH 113/466] Update Silk.NET interop to use explicit GUID-based APIs Refactored Direct3D12 interop to use explicit GUID-based QueryInterface and Create* methods, replacing older overloads. Updated default parameters to pointer types, switched to pointer-based resource mapping, and improved type safety. Removed redundant Dispose() calls and unused usings. These changes align with newer Silk.NET API patterns and enhance code clarity and maintainability. --- .../Platforms/Windows/D3DTexture.cs | 4 +- .../Views/Zenith.NET.Views.WPF/D3DTexture.cs | 1 - .../ZenithView.WinUI.cs | 4 +- sources/Zenith.NET.DirectX12/DXBuffer.cs | 15 +++---- sources/Zenith.NET.DirectX12/DXBufferView.cs | 5 +-- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 2 +- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 42 +++++++++---------- sources/Zenith.NET.DirectX12/DXHeap.cs | 19 +++++---- sources/Zenith.NET.DirectX12/DXTexture.cs | 10 +++-- sources/Zenith.NET.DirectX12/DXTextureView.cs | 5 +-- 10 files changed, 55 insertions(+), 52 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs index 56161f09..14c9b2f9 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs @@ -55,7 +55,8 @@ public D3DTexture(uint width, uint height) D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)Mutex.GetAddressOf())); - using ComPtr resource = Texture.QueryInterface(); + using ComPtr resource = new(); + D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf())); void* sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); @@ -84,7 +85,6 @@ public void ReleaseSync() public void Present() { using ComPtr backBuffer = new(); - D3D.Success(SwapChain.GetBuffer(0, SilkMarshal.GuidPtrOf(), (void**)backBuffer.GetAddressOf())); AcquireSync(); diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs index dbc9127e..b0fee48e 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs @@ -61,7 +61,6 @@ public D3DTexture(uint width, uint height, D3DImage image) D3D.Success(D3D11RenderTarget.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)D3D11Mutex.GetAddressOf())); using ComPtr resource = new(); - D3D.Success(D3D11RenderTarget.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf())); sharedHandle = null; diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index c27f59a0..b2950e35 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -160,7 +160,8 @@ public D3DTexture(uint width, uint height) D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)Mutex.GetAddressOf())); - using ComPtr resource = Texture.QueryInterface(); + using ComPtr resource = new(); + D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf())); void* sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); @@ -189,7 +190,6 @@ public void ReleaseSync() public void Present() { using ComPtr backBuffer = new(); - D3D.Success(SwapChain.GetBuffer(0, SilkMarshal.GuidPtrOf(), (void**)backBuffer.GetAddressOf())); AcquireSync(); diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 876d2767..09315030 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -22,11 +22,12 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr), + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), 0, - default, - out Resource).Success(); + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); } else { @@ -59,14 +60,14 @@ public override nint GetNativeObject(NativeObjectType type) public override MappedMemory Map() { void* pointer; - Resource.Map(0, default(ReadOnlySpan), &pointer).Success(); + Resource.Map(0, default(DxRange*), &pointer).Success(); return new((nint)pointer, Desc.SizeInBytes); } public override void Unmap() { - Resource.Unmap(0, default(ReadOnlySpan)); + Resource.Unmap(0, default(DxRange*)); } protected override void SetResourceName(string name) @@ -90,7 +91,7 @@ public static ResourceDesc1 ResourceDesc(BufferDesc desc) Height = 1, DepthOrArraySize = 1, MipLevels = 1, - SampleDesc = new(1, 0), + SampleDesc = new(1), Layout = TextureLayout.LayoutRowMajor, Flags = DXFormats.DirectX12(desc.Usages) }; diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs index ea65bcd8..7af23440 100644 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -1,5 +1,4 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; +using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; @@ -82,7 +81,7 @@ private DXDescriptorToken CreateUavToken() } }; - context.Device10.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, default(ComPtr), &viewDesc, token.CpuHandle); + context.Device10.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); return token; } diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 13438dc4..22df9d88 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -26,7 +26,7 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None }; - context.Device10.CreateDescriptorHeap(&desc, out Heap).Success(); + context.Device10.CreateDescriptorHeap(&desc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); CpuStartHandle = Heap.GetCPUDescriptorHandleForHeapStart(); GpuStartHandle = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index eb9ee9b1..c4d3fd11 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -58,23 +58,23 @@ protected override void Initialize(bool useValidationLayer, out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - if (useValidationLayer && D3D12.GetDebugInterface(out ComPtr debug).IsSuccess()) + using ComPtr debug = new(); + if (useValidationLayer && D3D12.GetDebugInterface(SilkMarshal.GuidPtrOf(), (void**)debug.GetAddressOf()).IsSuccess()) { debug.EnableDebugLayer(); - - debug.Dispose(); } - DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), out Factory7).Success(); + DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), SilkMarshal.GuidPtrOf(), (void**)Factory7.GetAddressOf()).Success(); - Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, out Adapter4).Success(); + Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, SilkMarshal.GuidPtrOf(), (void**)Adapter4.GetAddressOf()).Success(); - if (!D3D12.CreateDevice(Adapter4, D3DFeatureLevel.Level122, out Device10).IsSuccess()) + if (!D3D12.CreateDevice((IUnknown*)Adapter4.Handle, D3DFeatureLevel.Level122, SilkMarshal.GuidPtrOf(), (void**)Device10.GetAddressOf()).IsSuccess()) { throw new NotSupportedException("Direct3D 12 Feature Level 12_2 is not supported on the selected adapter."); } - if (Device10.QueryInterface(out ComPtr infoQueue1).IsSuccess()) + ComPtr infoQueue1 = new(); + if (Device10.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)infoQueue1.GetAddressOf()).IsSuccess()) { InfoQueue1 = infoQueue1; } @@ -99,37 +99,35 @@ protected override void Initialize(bool useValidationLayer, Desc11 = rootSignatureDesc }; - ComPtr rootSignatureBlob = default; - ComPtr rootSignatureError = default; - D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, ref rootSignatureBlob, ref rootSignatureError).Success(); - Device10.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), out RootSignature).Success(); - rootSignatureBlob.Dispose(); - rootSignatureError.Dispose(); + using ComPtr rootSignatureBlob = new(); + using ComPtr rootSignatureError = new(); + D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, rootSignatureBlob.GetAddressOf(), rootSignatureError.GetAddressOf()).Success(); + Device10.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DrawSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DrawIndexedSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DispatchSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), out DispatchMeshSignature).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; - Device10.CreateCommandQueue(&commandQueueDesc, out GraphicsCommandQueue).Success(); + Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Compute; - Device10.CreateCommandQueue(&commandQueueDesc, out ComputeCommandQueue).Success(); + Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)ComputeCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Copy; - Device10.CreateCommandQueue(&commandQueueDesc, out CopyCommandQueue).Success(); + Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CopyCommandQueue.GetAddressOf()).Success(); capabilities = new DXCapabilities(this); graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); @@ -152,7 +150,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); - ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(Span)); + ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } @@ -161,7 +159,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); - ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(Span)); + ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index 0b95e1e4..d3700c58 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -1,5 +1,6 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -16,7 +17,7 @@ public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) Alignment = DXGraphicsContext.DefaultHeapAlignment }; - context.Device10.CreateHeap(&heapDesc, out Heap).Success(); + context.Device10.CreateHeap(&heapDesc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; @@ -30,14 +31,16 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); + ComPtr resource = new(); Context.Device10.CreatePlacedResource2(Heap, offsetInBytes, &resourceDesc, BarrierLayout.Undefined, - default, + default(ClearValue*), 0, - default, - out ComPtr resource).Success(); + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)resource.GetAddressOf()).Success(); return new DXBuffer(Context, desc, resource); } @@ -46,14 +49,16 @@ protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc de { ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); + ComPtr resource = new(); Context.Device10.CreatePlacedResource2(Heap, offsetInBytes, &resourceDesc, BarrierLayout.Undefined, - default, + default(ClearValue*), 0, - default, - out ComPtr resource).Success(); + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)resource.GetAddressOf()).Success(); return new DXTexture(Context, desc, resource); } diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 4c0a7875..f7cc3288 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -1,5 +1,6 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -19,11 +20,12 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr), + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), 0, - default, - out Resource).Success(); + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); } else { diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs index 201537d9..5ac6720a 100644 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -1,5 +1,4 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; +using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; @@ -202,7 +201,7 @@ private DXDescriptorToken CreateUavToken() break; } - context.Device10.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, default(ComPtr), &viewDesc, token.CpuHandle); + context.Device10.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); return token; } From 775d4f13af0144711c817c0ecc6624fde646339f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 22:10:57 +0800 Subject: [PATCH 114/466] Update CreateCommandSignature to use raw pointer default Replaced default(ComPtr) with default(ID3D12RootSignature*) in Device10.CreateCommandSignature calls to match the expected parameter type and improve type correctness. --- sources/Zenith.NET.DirectX12/DXGraphicsContext.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index c4d3fd11..9f97c898 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -106,19 +106,19 @@ protected override void Initialize(bool useValidationLayer, IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ComPtr), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); + Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); From a70e776e8f9433a896d00d56226a3fe971626e35 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 22:12:38 +0800 Subject: [PATCH 115/466] Refactor to use GraphicsApi enum and update capability props Replaced Backend enum with GraphicsApi throughout the file, updated context creation and console output accordingly. Renamed capability properties from RayTracingSupported and MeshShadingSupported to RayTracing and MeshShading. --- .../Experiments/PlatformDetection/Program.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sources/Experiments/PlatformDetection/Program.cs b/sources/Experiments/PlatformDetection/Program.cs index 6cec30be..b578e3e4 100644 --- a/sources/Experiments/PlatformDetection/Program.cs +++ b/sources/Experiments/PlatformDetection/Program.cs @@ -3,26 +3,26 @@ using Zenith.NET.Metal; using Zenith.NET.Vulkan; -foreach (Backend backend in Enum.GetValues()) +foreach (GraphicsApi graphicsApi in Enum.GetValues()) { try { - using GraphicsContext context = backend switch + using GraphicsContext context = graphicsApi switch { - Backend.DirectX12 => GraphicsContext.CreateDirectX12(true), - Backend.Metal => GraphicsContext.CreateMetal(true), - Backend.Vulkan => GraphicsContext.CreateVulkan(true), + GraphicsApi.DirectX12 => GraphicsContext.CreateDirectX12(true), + GraphicsApi.Metal => GraphicsContext.CreateMetal(true), + GraphicsApi.Vulkan => GraphicsContext.CreateVulkan(true), _ => throw new NotSupportedException() }; - Console.WriteLine($"Backend {backend} is supported."); + Console.WriteLine($"GraphicsApi {graphicsApi} is supported."); Console.WriteLine($" Device Name: {context.Capabilities.DeviceName}"); - Console.WriteLine($" Ray Tracing Supported: {context.Capabilities.RayTracingSupported}"); - Console.WriteLine($" Mesh Shading Supported: {context.Capabilities.MeshShadingSupported}"); + Console.WriteLine($" Ray Tracing Supported: {context.Capabilities.RayTracing}"); + Console.WriteLine($" Mesh Shading Supported: {context.Capabilities.MeshShading}"); } catch (Exception) { - Console.WriteLine($"Backend {backend} is not supported."); + Console.WriteLine($"GraphicsApi {graphicsApi} is not supported."); } Console.WriteLine(); From 97f22d5638db8269d6c57f31eb2dc6b059735579 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 25 May 2026 22:15:40 +0800 Subject: [PATCH 116/466] Update SkiaSharp to version 3.119.4 Upgraded the SkiaSharp NuGet package from version 3.119.2 to 3.119.4 in Directory.Packages.props to include the latest fixes and improvements. --- sources/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 86c36453..1b98393c 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -19,7 +19,7 @@ - + From 184c0053dd3a6527f28a5007501061ea850257b2 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 09:24:46 +0800 Subject: [PATCH 117/466] Set up SixLabors license key in CI and project config Added SIXLABORS_LICENSE_KEY to CI environment variables and passed it to the Zenith.NET.Extensions.ImageSharp.csproj as a property for license management. --- .github/workflows/continuous-integration.yml | 3 +++ .../Zenith.NET.Extensions.ImageSharp.csproj | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1964c235..9fd780a8 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -6,6 +6,9 @@ on: pull_request: branches: ["*"] +env: + SIXLABORS_LICENSE_KEY: ${{ secrets.SIXLABORS_LICENSE_KEY }} + jobs: build: strategy: diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj index 9b04ef4c..4bf295f1 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Zenith.NET.Extensions.ImageSharp.csproj @@ -2,6 +2,7 @@ $(StandardTargetFramework) + $(SIXLABORS_LICENSE_KEY) From faf38ff77ef492df2207f747ddb9fb0320252c0a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 09:52:56 +0800 Subject: [PATCH 118/466] Add abstract Handle property to TopLevelAccelerationStructure Introduce abstract ResourceHandle property, requiring all subclasses of TopLevelAccelerationStructure to implement the Handle property for consistent resource access. --- sources/Zenith.NET/TopLevelAccelerationStructure.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/Zenith.NET/TopLevelAccelerationStructure.cs b/sources/Zenith.NET/TopLevelAccelerationStructure.cs index 4c9c53ca..dbf24852 100644 --- a/sources/Zenith.NET/TopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET/TopLevelAccelerationStructure.cs @@ -6,6 +6,8 @@ public abstract class TopLevelAccelerationStructure(GraphicsContext context, Top public ref readonly TopLevelAccelerationStructureDesc Desc => ref desc; + public abstract ResourceHandle Handle { get; } + internal void Refresh(TopLevelAccelerationStructureDesc newDesc) { desc = newDesc; From 1f532794b692e96745ccebafc6096379d8058b45 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 10:12:34 +0800 Subject: [PATCH 119/466] Refactor rendering pipeline and shader interfaces Modernize rendering and shader code: - Update swap chain and ImGui initialization to new API - Refactor shader constant buffers to structured types - Use descriptor handles for buffer/texture access - Standardize function naming and math expressions - Improve resource binding and encapsulation - Update ImGui handler to use attachment formats - General code cleanup and preparation for future APIs --- sources/Experiments/CornellBox/App.cs | 20 +- .../Assets/Shaders/PathTracing.slang | 199 ++++++++++-------- .../Assets/Shaders/Rasterization.slang | 65 +++--- .../CornellBox/Handlers/ImGuiHandler.cs | 2 +- 4 files changed, 162 insertions(+), 124 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 8edddf62..fc2e8617 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -72,9 +72,9 @@ static App() surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, Width, Height); } - swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorTargetFormat = PixelFormat.B8G8R8A8UNorm, DepthStencilTargetFormat = PixelFormat.D32FloatS8UInt }); - imGui = new(input, swapChain.Output); - camera = new(input, Matrix4x4.CreateTranslation(278f, 273f, -800f)) + swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorFormat = PixelFormat.B8G8R8A8UNorm, DepthStencilFormat = PixelFormat.D32FloatS8UInt }); + imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], DepthStencilFormat = PixelFormat.D32FloatS8UInt }); + camera = new(input, Matrix4x4.CreateTranslation(278.0f, 273.0f, -800.0f)) { Speed = 240.0f, FarPlane = 2000.0f @@ -82,7 +82,7 @@ static App() rasterizer = new(); - if (Context.Capabilities.RayTracingSupported) + if (Context.Capabilities.RayTracing) { activeRenderer = pathTracer = new(); currentMode = 0; @@ -124,14 +124,14 @@ public static void Run() ImGui.SetNextWindowPos(new(10, 10), ImGuiCond.FirstUseEver); if (ImGui.Begin("Cornell Box", ImGuiWindowFlags.AlwaysAutoResize)) { - ImGui.Text($"Backend: {Context.Backend}"); + ImGui.Text($"GraphicsApi: {Context.GraphicsApi}"); ImGui.Text(Context.Capabilities.DeviceName); ImGui.Separator(); ImGui.Text("Render Mode:"); - if (Context.Capabilities.RayTracingSupported) + if (Context.Capabilities.RayTracing) { if (ImGui.RadioButton("Path Tracing", currentMode is 0) && currentMode is not 0) { @@ -172,20 +172,18 @@ public static void Run() return; } - CommandBuffer commandBuffer = Context.Graphics.CommandBuffer(); + CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); activeRenderer.Render(commandBuffer); imGui.Render(commandBuffer, new() { - Texture = swapChain.CurrentColorTarget, + Texture = swapChain.CurrentTexture, LoadOp = LoadOp.Load, StoreOp = StoreOp.Store }); - commandBuffer.Submit(true); - - swapChain.Present(); + swapChain.Present(commandBuffer.Submit()).Wait(); }; window.Resize += _ => diff --git a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang index c171acc2..027c3ea7 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang @@ -36,13 +36,7 @@ struct Material { private float4 AlbedoAndEmission; - float Metallic; - - float Roughness; - - private float padding0; - - private float padding1; + private float4 MetallicRoughnessAndPadding; property float3 Albedo { @@ -57,9 +51,23 @@ struct Material return AlbedoAndEmission.w; } } + + property float Metallic + { + get { + return MetallicRoughnessAndPadding.x; + } + } + + property float Roughness + { + get { + return MetallicRoughnessAndPadding.y; + } + } }; -struct CameraParams +struct PathTracingConstants { float4x4 InvView; @@ -67,13 +75,19 @@ struct CameraParams private float4 PositionAndPadding; - uint FrameCount; + private uint4 FrameCountWidthHeightAndPadding; + + DescriptorHandle Scene; - uint Width; + DescriptorHandle> Vertices; - uint Height; + DescriptorHandle> Indices; - private float padding0; + DescriptorHandle> Materials; + + DescriptorHandle> AccumTexture; + + DescriptorHandle> OutputTexture; property float3 Position { @@ -81,15 +95,30 @@ struct CameraParams return PositionAndPadding.xyz; } } + + property uint FrameCount + { + get { + return FrameCountWidthHeightAndPadding.x; + } + } + + property uint Width + { + get { + return FrameCountWidthHeightAndPadding.y; + } + } + + property uint Height + { + get { + return FrameCountWidthHeightAndPadding.z; + } + } }; -RaytracingAccelerationStructure scene; -ConstantBuffer camera; -StructuredBuffer vertices; -StructuredBuffer indices; -StructuredBuffer materials; -RWTexture2D accumTexture; -RWTexture2D outputTexture; +uniform PathTracingConstants pathTracing; float DistributionGGX(float NdotH, float roughness) { @@ -118,7 +147,7 @@ float3 FresnelSchlick(float cosTheta, float3 F0) return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0); } -float3 evaluateBRDF(float3 N, float3 V, float3 L, Material mat) +float3 EvaluateBRDF(float3 N, float3 V, float3 L, Material mat) { float roughness = max(mat.Roughness, 0.04); float NdotL = max(dot(N, L), 0.0); @@ -141,14 +170,14 @@ float3 evaluateBRDF(float3 N, float3 V, float3 L, Material mat) return diffuse + specular; } -float powerHeuristic(float pdfA, float pdfB) +float PowerHeuristic(float pdfA, float pdfB) { float a2 = pdfA * pdfA; return a2 / (a2 + pdfB * pdfB + 0.0001); } -float computeBrdfPdf(float3 N, float3 V, float3 L, float roughness, float specProb) +float ComputeBrdfPdf(float3 N, float3 V, float3 L, float roughness, float specProb) { float NdotL = max(dot(N, L), 0.0); if (NdotL <= 0.0) @@ -171,12 +200,27 @@ float computeBrdfPdf(float3 N, float3 V, float3 L, float roughness, float specPr return specProb * pdfSpec + (1.0 - specProb) * pdfDiff; } -float3 sampleGGXHalfVector(float3 N, float roughness, inout uint seed) +uint PcgHash(uint input) +{ + uint state = input * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + + return (word >> 22u) ^ word; +} + +float RandomFloat(inout uint seed) +{ + seed = PcgHash(seed); + + return float(seed) / 4294967295.0; +} + +float3 SampleGGXHalfVector(float3 N, float roughness, inout uint seed) { float a = roughness * roughness; - float r1 = randomFloat(seed); - float r2 = randomFloat(seed); + float r1 = RandomFloat(seed); + float r2 = RandomFloat(seed); float phi = 2.0 * PI * r1; float cosTheta = sqrt((1.0 - r2) / (1.0 + (a * a - 1.0) * r2)); @@ -190,25 +234,10 @@ float3 sampleGGXHalfVector(float3 N, float roughness, inout uint seed) return normalize(u * cos(phi) * sinTheta + v * sin(phi) * sinTheta + w * cosTheta); } -uint pcgHash(uint input) -{ - uint state = input * 747796405u + 2891336453u; - uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; - - return (word >> 22u) ^ word; -} - -float randomFloat(inout uint seed) -{ - seed = pcgHash(seed); - - return float(seed) / 4294967295.0; -} - -float3 cosineSampleHemisphere(float3 normal, inout uint seed) +float3 CosineSampleHemisphere(float3 normal, inout uint seed) { - float r1 = randomFloat(seed); - float r2 = randomFloat(seed); + float r1 = RandomFloat(seed); + float r2 = RandomFloat(seed); float phi = 2.0 * PI * r1; float sinTheta = sqrt(r2); @@ -222,7 +251,7 @@ float3 cosineSampleHemisphere(float3 normal, inout uint seed) return normalize(u * cos(phi) * sinTheta + v * sin(phi) * sinTheta + w * cosTheta); } -bool traceShadowRay(float3 origin, float3 direction, float maxDist) +bool TraceShadowRay(float3 origin, float3 direction, float maxDist) { RayDesc shadowRay; shadowRay.Origin = origin; @@ -236,7 +265,7 @@ bool traceShadowRay(float3 origin, float3 direction, float maxDist) } RayQuery shadowQuery; - shadowQuery.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, shadowRay); + shadowQuery.TraceRayInline(*pathTracing.Scene, RAY_FLAG_NONE, 0xFF, shadowRay); while (shadowQuery.Proceed()) { @@ -245,11 +274,11 @@ bool traceShadowRay(float3 origin, float3 direction, float maxDist) return shadowQuery.CommittedStatus() != COMMITTED_NOTHING; } -float3 sampleLightDirect(float3 hitPos, float3 hitNormal, float3 geoNormal, float3 V, Material mat, float roughness, +float3 SampleLightDirect(float3 hitPos, float3 hitNormal, float3 geoNormal, float3 V, Material mat, float roughness, float specProb, inout uint rng) { - float r1 = randomFloat(rng); - float r2 = randomFloat(rng); + float r1 = RandomFloat(rng); + float r2 = RandomFloat(rng); float3 lightPoint = float3(lerp(LightMin.x, LightMax.x, r1), LightMin.y, lerp(LightMin.z, LightMax.z, r2)); @@ -270,24 +299,24 @@ float3 sampleLightDirect(float3 hitPos, float3 hitNormal, float3 geoNormal, floa } float3 shadowOrigin = dot(geoNormal, L) > 0.0 ? hitPos + geoNormal * 0.001 : hitPos - geoNormal * 0.001; - if (traceShadowRay(shadowOrigin, L, dist)) + if (TraceShadowRay(shadowOrigin, L, dist)) { return float3(0.0, 0.0, 0.0); } - Material lightMat = materials[3]; + Material lightMat = (*pathTracing.Materials)[3]; float3 lightEmission = lightMat.Albedo * lightMat.Emission; float pdfLight = (dist * dist) / (lightCosine * LightArea); - float pdfBrdf = computeBrdfPdf(hitNormal, V, L, roughness, specProb); - float misWeight = powerHeuristic(pdfLight, pdfBrdf); + float pdfBrdf = ComputeBrdfPdf(hitNormal, V, L, roughness, specProb); + float misWeight = PowerHeuristic(pdfLight, pdfBrdf); - float3 brdf = evaluateBRDF(hitNormal, V, L, mat); + float3 brdf = EvaluateBRDF(hitNormal, V, L, mat); return lightEmission * brdf * NdotL * misWeight / pdfLight; } -float3 tracePath(float3 origin, float3 direction, inout uint rng) +float3 TracePath(float3 origin, float3 direction, inout uint rng) { float3 throughput = float3(1.0, 1.0, 1.0); float3 radiance = float3(0.0, 0.0, 0.0); @@ -301,7 +330,7 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) ray.TMax = 100000.0; RayQuery query; - query.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, ray); + query.TraceRayInline(*pathTracing.Scene, RAY_FLAG_NONE, 0xFF, ray); while (query.Proceed()) { @@ -320,13 +349,13 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) float t = query.CommittedRayT(); float3 hitPos = origin + direction * t; - uint i0 = indices[primIdx * 3 + 0]; - uint i1 = indices[primIdx * 3 + 1]; - uint i2 = indices[primIdx * 3 + 2]; + uint i0 = (*pathTracing.Indices)[primIdx * 3 + 0]; + uint i1 = (*pathTracing.Indices)[primIdx * 3 + 1]; + uint i2 = (*pathTracing.Indices)[primIdx * 3 + 2]; - Vertex v0 = vertices[i0]; - Vertex v1 = vertices[i1]; - Vertex v2 = vertices[i2]; + Vertex v0 = (*pathTracing.Vertices)[i0]; + Vertex v1 = (*pathTracing.Vertices)[i1]; + Vertex v2 = (*pathTracing.Vertices)[i2]; float3 baryWeights = float3(1.0 - bary.x - bary.y, bary.x, bary.y); float3 normal = normalize(v0.Normal * baryWeights.x + v1.Normal * baryWeights.y + v2.Normal * baryWeights.z); @@ -337,7 +366,7 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) normal = -normal; } - Material mat = materials[v0.MaterialID]; + Material mat = (*pathTracing.Materials)[v0.MaterialID]; if (mat.Emission > 0.0) { @@ -358,14 +387,14 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) float total = specWeight + diffWeight; float specProb = specWeight / total; - radiance += throughput * sampleLightDirect(hitPos, normal, geoNormal, V, mat, roughness, specProb, rng); + radiance += throughput * SampleLightDirect(hitPos, normal, geoNormal, V, mat, roughness, specProb, rng); float3 newDir; float NdotV = max(dot(normal, V), 0.001); - if (randomFloat(rng) < specProb) + if (RandomFloat(rng) < specProb) { - float3 H = sampleGGXHalfVector(normal, roughness, rng); + float3 H = SampleGGXHalfVector(normal, roughness, rng); newDir = reflect(-V, H); float NdotL = dot(normal, newDir); @@ -385,7 +414,7 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) } else { - newDir = cosineSampleHemisphere(normal, rng); + newDir = CosineSampleHemisphere(normal, rng); float3 H = normalize(V + newDir); float HdotV = max(dot(H, V), 0.0); @@ -403,7 +432,7 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) { float p = max(throughput.r, max(throughput.g, throughput.b)); - if (randomFloat(rng) > p) + if (RandomFloat(rng) > p) { break; } @@ -415,7 +444,7 @@ float3 tracePath(float3 origin, float3 direction, inout uint rng) return radiance; } -float halton(uint index, uint base) +float Halton(uint index, uint base) { float result = 0.0; float f = 1.0; @@ -437,35 +466,35 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) { uint2 pixel = dispatchThreadID.xy; - if (pixel.x >= camera.Width || pixel.y >= camera.Height) + if (pixel.x >= pathTracing.Width || pixel.y >= pathTracing.Height) { return; } - uint rng = pcgHash(pixel.x + pixel.y * camera.Width + camera.FrameCount * camera.Width * camera.Height); + uint rng = PcgHash(pixel.x + pixel.y * pathTracing.Width + pathTracing.FrameCount * pathTracing.Width * pathTracing.Height); - uint sampleIndex = camera.FrameCount + 1; - float hx = halton(sampleIndex, 2); - float hy = halton(sampleIndex, 3); - float ox = randomFloat(rng) * 0.5 - 0.25; - float oy = randomFloat(rng) * 0.5 - 0.25; + uint sampleIndex = pathTracing.FrameCount + 1; + float hx = Halton(sampleIndex, 2); + float hy = Halton(sampleIndex, 3); + float ox = RandomFloat(rng) * 0.5 - 0.25; + float oy = RandomFloat(rng) * 0.5 - 0.25; float2 jitter = frac(float2(hx + ox, hy + oy)); - float2 uv = (float2(pixel) + jitter) / float2(camera.Width, camera.Height); + float2 uv = (float2(pixel) + jitter) / float2(pathTracing.Width, pathTracing.Height); float2 ndc = uv * 2.0 - 1.0; ndc.y = -ndc.y; - float4 target = mul(float4(ndc, 1.0, 1.0), camera.InvProjection); + float4 target = mul(float4(ndc, 1.0, 1.0), pathTracing.InvProjection); float3 localDir = normalize(target.xyz / target.w); - float3 direction = normalize(mul(float4(localDir, 0.0), camera.InvView).xyz); - float3 origin = camera.Position; + float3 direction = normalize(mul(float4(localDir, 0.0), pathTracing.InvView).xyz); + float3 origin = pathTracing.Position; - float3 color = tracePath(origin, direction, rng); + float3 color = TracePath(origin, direction, rng); color = min(color, float3(30.0, 30.0, 30.0)); - float4 prev = accumTexture[pixel]; + float4 prev = (*pathTracing.AccumTexture)[pixel]; float4 accumulated; - if (camera.FrameCount == 0) + if (pathTracing.FrameCount == 0) { accumulated = float4(color, 1.0); } @@ -474,9 +503,9 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) accumulated = prev + float4(color, 1.0); } - accumTexture[pixel] = accumulated; + (*pathTracing.AccumTexture)[pixel] = accumulated; - float3 avg = accumulated.rgb / float(camera.FrameCount + 1); + float3 avg = accumulated.rgb / float(pathTracing.FrameCount + 1); // ACES tonemapping float3 a = avg * (avg * 2.51 + 0.03); @@ -484,5 +513,5 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) avg = saturate(a / b); avg = pow(avg, 1.0 / 2.2); - outputTexture[pixel] = float4(avg, 1.0); + (*pathTracing.OutputTexture) [pixel] = float4(avg, 1.0); } diff --git a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang index 17135159..223d57b3 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang @@ -1,14 +1,10 @@ -struct Material +static const float PI = 3.14159265; + +struct Material { private float4 AlbedoAndEmission; - float Metallic; - - float Roughness; - - private float padding0; - - private float padding1; + private float4 MetallicRoughnessAndPadding; property float3 Albedo { @@ -23,9 +19,23 @@ return AlbedoAndEmission.w; } } + + property float Metallic + { + get { + return MetallicRoughnessAndPadding.x; + } + } + + property float Roughness + { + get { + return MetallicRoughnessAndPadding.y; + } + } }; -struct RasterConstants +struct RasterizationConstants { float4x4 Model; @@ -39,6 +49,8 @@ struct RasterConstants private float4 CameraPosAndPadding; + DescriptorHandle> Materials; + property float3 LightPos { get { @@ -100,17 +112,14 @@ struct PSInput nointerpolation uint MaterialID : TEXCOORD2; }; -ConstantBuffer raster; -StructuredBuffer materials; - -static const float PI = 3.14159265; +uniform RasterizationConstants rasterization; float3 ACESFilm(float3 x) { return saturate((x * (x * 2.51 + 0.03)) / (x * (x * 2.43 + 0.59) + 0.14)); } -float3 toSRGB(float3 linear) +float3 ToSRGB(float3 linear) { return pow(linear, 1.0 / 2.2); } @@ -119,15 +128,17 @@ float DistributionGGX(float NdotH, float roughness) { float a = roughness * roughness; float a2 = a * a; - float d = NdotH * NdotH * (a2 - 1.0) + 1.0; - return a2 / (PI * d * d); + float denom = NdotH * NdotH * (a2 - 1.0) + 1.0; + + return a2 / (PI * denom * denom); } -float GeometrySchlickGGX(float NdotV, float roughness) +float GeometrySchlickGGX(float NdotX, float roughness) { float r = roughness + 1.0; float k = (r * r) / 8.0; - return NdotV / (NdotV * (1.0 - k) + k); + + return NdotX / (NdotX * (1.0 - k) + k); } float GeometrySmith(float NdotV, float NdotL, float roughness) @@ -142,12 +153,12 @@ float3 FresnelSchlick(float cosTheta, float3 F0) PSInput VSMain(VSInput input) { - float4 worldPos = mul(float4(input.Position, 1.0), raster.Model); + float4 worldPos = mul(float4(input.Position, 1.0), rasterization.Model); PSInput output; - output.Position = mul(mul(worldPos, raster.View), raster.Projection); + output.Position = mul(mul(worldPos, rasterization.View), rasterization.Projection); output.WorldPos = worldPos.xyz; - output.Normal = normalize(mul(float4(input.Normal, 0.0), raster.Model).xyz); + output.Normal = normalize(mul(float4(input.Normal, 0.0), rasterization.Model).xyz); output.MaterialID = input.MaterialID; return output; @@ -155,20 +166,20 @@ PSInput VSMain(VSInput input) float4 PSMain(PSInput input) : SV_TARGET { - Material mat = materials[input.MaterialID]; + Material mat = (*rasterization.Materials)[input.MaterialID]; if (mat.Emission > 0.0) { float3 emissive = mat.Albedo * mat.Emission; float3 mapped = emissive / (emissive + 1.0); - return float4(toSRGB(mapped), 1.0); + return float4(ToSRGB(mapped), 1.0); } float3 N = normalize(input.Normal); float3 worldPos = input.WorldPos; - float3 V = normalize(raster.CameraPos - worldPos); - float3 toLight = raster.LightPos - worldPos; + float3 V = normalize(rasterization.CameraPos - worldPos); + float3 toLight = rasterization.LightPos - worldPos; float dist = length(toLight); float3 L = toLight / dist; float3 H = normalize(L + V); @@ -192,14 +203,14 @@ float4 PSMain(PSInput input) : SV_TARGET float3 diffuse = kD * mat.Albedo / PI; float atten = 1.0 / (dist * dist) * 80000.0; - float3 Lo = (diffuse + specular) * raster.LightColor * NdotL * atten; + float3 Lo = (diffuse + specular) * rasterization.LightColor * NdotL * atten; float hemiFactor = N.y * 0.5 + 0.5; float3 ambient = mat.Albedo * lerp(0.03, 0.10, hemiFactor) * (1.0 - metallic * 0.7); float3 color = ambient + Lo; - color = toSRGB(ACESFilm(color)); + color = ToSRGB(ACESFilm(color)); return float4(color, 1.0); } diff --git a/sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs b/sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs index 4fbd18ea..e23892f1 100644 --- a/sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs +++ b/sources/Experiments/CornellBox/Handlers/ImGuiHandler.cs @@ -11,7 +11,7 @@ internal class ImGuiHandler : ImGuiController, IImGuiPlatformBindings private readonly IMouse mouse; private readonly IKeyboard keyboard; - public ImGuiHandler(IInputContext input, Output output) : base(App.Context, output, ImGuiColorSpace.Legacy, Path.Combine(AppContext.BaseDirectory, "Assets", "Fonts", "msyh.ttf"), OtherSetup) + public ImGuiHandler(IInputContext input, AttachmentFormats attachmentFormats) : base(App.Context, attachmentFormats, ImGuiColorSpace.Legacy, Path.Combine(AppContext.BaseDirectory, "Assets", "Fonts", "msyh.ttf"), OtherSetup) { mouse = input.Mice[0]; mouse.MouseDown += OnMouseDown; From 69cdbf27efcde716f5387d55afd0444090f619c8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 10:15:22 +0800 Subject: [PATCH 120/466] Refactor graphics API usage and update attachment formats Modernize graphics code by replacing deprecated properties and enums: - Use `AttachmentFormats` instead of `RenderOutput` - Update texture creation from `Flags` to `Usages` - Set `SampleCount` explicitly for ImGui - Align property names with latest API conventions for clarity --- sources/Experiments/CornellBox/App.cs | 2 +- .../CornellBox/Renderers/RasterizationRenderer.cs | 2 +- sources/Experiments/CornellBox/Renderers/Renderer.cs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index fc2e8617..91aab7e1 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -73,7 +73,7 @@ static App() } swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorFormat = PixelFormat.B8G8R8A8UNorm, DepthStencilFormat = PixelFormat.D32FloatS8UInt }); - imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], DepthStencilFormat = PixelFormat.D32FloatS8UInt }); + imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], DepthStencilFormat = PixelFormat.D32FloatS8UInt, SampleCount = SampleCount.Count1 }); camera = new(input, Matrix4x4.CreateTranslation(278.0f, 273.0f, -800.0f)) { Speed = 240.0f, diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 236cfbbd..63d42ce1 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -97,7 +97,7 @@ public RasterizationRenderer() ResourceBindings = ResourceBindings, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = RenderOutput + Output = AttachmentFormats }); } diff --git a/sources/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Experiments/CornellBox/Renderers/Renderer.cs index 9d6fd975..add22c90 100644 --- a/sources/Experiments/CornellBox/Renderers/Renderer.cs +++ b/sources/Experiments/CornellBox/Renderers/Renderer.cs @@ -14,10 +14,10 @@ protected Renderer() public Texture DepthStencil { get; private set; } = null!; - public Output RenderOutput => new() + public AttachmentFormats AttachmentFormats => new() { - ColorAttachments = [Color.Desc.Format], - DepthStencilAttachment = DepthStencil.Desc.Format, + ColorFormats = [Color.Desc.Format], + DepthStencilFormat = DepthStencil.Desc.Format, SampleCount = Color.Desc.SampleCount }; @@ -40,7 +40,7 @@ public virtual void Resize(uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.RenderTarget | TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment }); DepthStencil = App.Context.CreateTexture(new() @@ -53,7 +53,7 @@ public virtual void Resize(uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil + Usages = TextureUsages.DepthStencil }); } From 6e12a1e28bcea2c617a1c012fff31c0f6b81f160 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 10:31:44 +0800 Subject: [PATCH 121/466] Refactor PathTracingRenderer to use constant buffer Replaces resource table with a single PathTracingConstants constant buffer containing all resource handles and camera parameters. Updates buffer and texture creation to use new API fields. Moves accumulation texture creation to Resize and updates resource cleanup. Modernizes pipeline and resource binding for improved clarity and maintainability. --- .../Renderers/PathTracingRenderer.cs | 140 ++++++++---------- 1 file changed, 62 insertions(+), 78 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 1b17adea..14c5c53a 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -3,7 +3,6 @@ using CornellBox.Handlers; using CornellBox.Helpers; using Zenith.NET; -using Zenith.NET.Extensions.Slang; using Buffer = Zenith.NET.Buffer; namespace CornellBox.Renderers; @@ -12,27 +11,15 @@ internal unsafe class PathTracingRenderer : Renderer { private const uint ThreadGroupSize = 16; - private static readonly ResourceBinding[] ResourceBindings = - [ - new() { Type = ResourceType.AccelerationStructure, Count = 1 }, - new() { Type = ResourceType.ConstantBuffer, Count = 1 }, - new() { Type = ResourceType.StructuredBuffer, Count = 1 }, - new() { Type = ResourceType.StructuredBuffer, Count = 1 }, - new() { Type = ResourceType.StructuredBuffer, Count = 1 }, - new() { Type = ResourceType.TextureReadWrite, Count = 1 }, - new() { Type = ResourceType.TextureReadWrite, Count = 1 } - ]; - private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; private readonly Buffer materialBuffer; - private readonly Buffer cameraBuffer; + private readonly Buffer constantBuffer; private readonly BottomLevelAccelerationStructure blas; private readonly TopLevelAccelerationStructure tlas; private readonly ComputePipeline pipeline; private Texture? accumulationTexture; - private ResourceTable? resourceTable; private Matrix4x4 lastView; private Matrix4x4 lastProjection; @@ -45,7 +32,8 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure }); fixed (Vertex* pointer = vertices) @@ -57,7 +45,8 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(uint) * indices.Length), StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure }); fixed (uint* pointer = indices) @@ -69,7 +58,8 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Material) * materials.Length), StrideInBytes = (uint)sizeof(Material), - Flags = BufferUsageFlags.ShaderResource + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.StorageReadOnly }); fixed (Material* pointer = materials) @@ -77,14 +67,9 @@ public PathTracingRenderer() materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); } - cameraBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(CameraParams), - StrideInBytes = (uint)sizeof(CameraParams), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite - }); + constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(PathTracingConstants))); - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); + CommandBuffer commandBuffer = App.Context.ComputeQueue.AcquireCommandBuffer(); blas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc { @@ -92,8 +77,8 @@ public PathTracingRenderer() [ new() { - Type = RayTracingGeometryType.Triangles, - Triangles = new() + Type = RayTracingGeometryType.Triangle, + TriangleGeometry = new() { VertexBuffer = vertexBuffer, VertexFormat = PixelFormat.R32G32B32Float, @@ -104,10 +89,10 @@ public PathTracingRenderer() IndexCount = (uint)indices.Length, Transform = Matrix4x4.Identity }, - Flags = RayTracingGeometryFlags.Opaque + IsOpaque = true } ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace }); tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc @@ -117,27 +102,20 @@ public PathTracingRenderer() new() { AccelerationStructure = blas, - ID = 0, - Mask = 0xFF, + InstanceId = 0, + VisibilityMask = 0xFF, Transform = Matrix4x4.Identity, Flags = RayTracingInstanceFlags.None } ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace }); - commandBuffer.Submit(waitForCompletion: true); + commandBuffer.Submit().Wait(); - using Shader computeShader = App.Context.LoadShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStageFlags.Compute); + using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); - pipeline = App.Context.CreateComputePipeline(new() - { - Compute = computeShader, - ResourceBindings = ResourceBindings, - ThreadGroupSizeX = ThreadGroupSize, - ThreadGroupSizeY = ThreadGroupSize, - ThreadGroupSizeZ = 1 - }); + pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); } public uint FrameCount { get; set; } @@ -158,48 +136,29 @@ public override void Update(CameraHandler camera) Matrix4x4.Invert(view, out Matrix4x4 invView); Matrix4x4.Invert(projection, out Matrix4x4 invProjection); - CameraParams parameters = new() + PathTracingConstants parameters = new() { InvView = invView, InvProjection = invProjection, Position = camera.Position, FrameCount = FrameCount, Width = App.Width, - Height = App.Height + Height = App.Height, + Scene = tlas.Handle, + Vertices = vertexBuffer.StorageReadOnlyHandle, + Indices = indexBuffer.StorageReadOnlyHandle, + Materials = materialBuffer.StorageReadOnlyHandle, + AccumTexture = accumulationTexture!.StorageHandle, + OutputTexture = Color.StorageHandle }; - cameraBuffer.Upload(0, new() { Pointer = (nint)(¶meters), SizeInBytes = (uint)sizeof(CameraParams) }); + constantBuffer.Upload(0, new() { Pointer = (nint)(¶meters), SizeInBytes = (uint)sizeof(PathTracingConstants) }); } public override void Render(CommandBuffer commandBuffer) { - if (resourceTable is null || accumulationTexture is null) - { - accumulationTexture = App.Context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = PixelFormat.R32G32B32A32Float, - Width = App.Width, - Height = App.Height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess - }); - - resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); - resourceTable.Write(0, tlas); - resourceTable.Write(1, cameraBuffer); - resourceTable.Write(2, vertexBuffer); - resourceTable.Write(3, indexBuffer); - resourceTable.Write(4, materialBuffer); - resourceTable.Write(5, accumulationTexture); - resourceTable.Write(6, Color); - } - commandBuffer.SetPipeline(pipeline); - commandBuffer.PushResourceTable(resourceTable); + commandBuffer.SetConstantBuffer(constantBuffer, 0); commandBuffer.Dispatch((App.Width + ThreadGroupSize - 1) / ThreadGroupSize, (App.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1); @@ -210,11 +169,19 @@ public override void Resize(uint width, uint height) { base.Resize(width, height); - resourceTable?.Dispose(); - resourceTable = null; - accumulationTexture?.Dispose(); - accumulationTexture = null; + accumulationTexture = App.Context.CreateTexture(new() + { + Type = TextureType.Texture2D, + Format = PixelFormat.R32G32B32A32Float, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage + }); FrameCount = 0; } @@ -223,21 +190,20 @@ public override void Dispose() { base.Dispose(); - resourceTable?.Dispose(); accumulationTexture?.Dispose(); pipeline.Dispose(); tlas.Dispose(); blas.Dispose(); - cameraBuffer.Dispose(); + constantBuffer.Dispose(); materialBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); } } -[StructLayout(LayoutKind.Explicit, Size = 160)] -file struct CameraParams +[StructLayout(LayoutKind.Explicit, Size = 208)] +file struct PathTracingConstants { [FieldOffset(0)] public Matrix4x4 InvView; @@ -256,4 +222,22 @@ file struct CameraParams [FieldOffset(152)] public uint Height; + + [FieldOffset(160)] + public ResourceHandle Scene; + + [FieldOffset(168)] + public ResourceHandle Vertices; + + [FieldOffset(176)] + public ResourceHandle Indices; + + [FieldOffset(184)] + public ResourceHandle Materials; + + [FieldOffset(192)] + public ResourceHandle AccumTexture; + + [FieldOffset(200)] + public ResourceHandle OutputTexture; } From f043f1129f7bbbaf3955032700aaaf40202dcfd1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 10:41:36 +0800 Subject: [PATCH 122/466] Refactor renderer resource and buffer management Refactored PathTracingRenderer and RasterizationRenderer to use new buffer helper methods for clarity and consistency. Removed ResourceTable in favor of direct buffer binding. Updated shader loading and render pass setup to modern APIs. Renamed and expanded RasterConstants to RasterizationConstants to support direct material buffer binding. Improved resource initialization and disposal order. --- sources/Experiments/CornellBox/App.cs | 7 +- .../Renderers/PathTracingRenderer.cs | 40 +++--- .../Renderers/RasterizationRenderer.cs | 114 ++++++------------ 3 files changed, 57 insertions(+), 104 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 91aab7e1..74872698 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -176,12 +176,7 @@ public static void Run() activeRenderer.Render(commandBuffer); - imGui.Render(commandBuffer, new() - { - Texture = swapChain.CurrentTexture, - LoadOp = LoadOp.Load, - StoreOp = StoreOp.Store - }); + imGui.Render(commandBuffer, ColorAttachment.Load(swapChain.CurrentTexture)); swapChain.Present(commandBuffer.Submit()).Wait(); }; diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 14c5c53a..220427d3 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -13,12 +13,12 @@ internal unsafe class PathTracingRenderer : Renderer private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; - private readonly Buffer materialBuffer; private readonly Buffer constantBuffer; - private readonly BottomLevelAccelerationStructure blas; - private readonly TopLevelAccelerationStructure tlas; private readonly ComputePipeline pipeline; + private readonly BottomLevelAccelerationStructure blas; + private readonly TopLevelAccelerationStructure tlas; + private readonly Buffer materialBuffer; private Texture? accumulationTexture; private Matrix4x4 lastView; @@ -54,20 +54,11 @@ public PathTracingRenderer() indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); } - materialBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Material) * materials.Length), - StrideInBytes = (uint)sizeof(Material), - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly - }); + constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(PathTracingConstants))); - fixed (Material* pointer = materials) - { - materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); - } + using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); - constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(PathTracingConstants))); + pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); CommandBuffer commandBuffer = App.Context.ComputeQueue.AcquireCommandBuffer(); @@ -113,9 +104,18 @@ public PathTracingRenderer() commandBuffer.Submit().Wait(); - using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); + materialBuffer = App.Context.CreateBuffer(new() + { + SizeInBytes = (uint)(sizeof(Material) * materials.Length), + StrideInBytes = (uint)sizeof(Material), + Access = BufferAccess.GpuOnly, + Usages = BufferUsages.StorageReadOnly + }); - pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); + fixed (Material* pointer = materials) + { + materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); + } } public uint FrameCount { get; set; } @@ -191,12 +191,12 @@ public override void Dispose() base.Dispose(); accumulationTexture?.Dispose(); - - pipeline.Dispose(); tlas.Dispose(); blas.Dispose(); - constantBuffer.Dispose(); materialBuffer.Dispose(); + + pipeline.Dispose(); + constantBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); } diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 63d42ce1..c233af92 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -3,143 +3,98 @@ using CornellBox.Handlers; using CornellBox.Helpers; using Zenith.NET; -using Zenith.NET.Extensions.Slang; using Buffer = Zenith.NET.Buffer; namespace CornellBox.Renderers; internal unsafe class RasterizationRenderer : Renderer { - private static readonly ResourceBinding[] ResourceBindings = - [ - new() { Type = ResourceType.ConstantBuffer, Count = 1 }, - new() { Type = ResourceType.StructuredBuffer, Count = 1 } - ]; + private readonly uint indexCount; private readonly Buffer vertexBuffer; private readonly Buffer indexBuffer; - private readonly Buffer materialBuffer; private readonly Buffer constantBuffer; - private readonly uint indexCount; - private readonly ResourceTable resourceTable; private readonly GraphicsPipeline pipeline; + private readonly Buffer materialBuffer; + public RasterizationRenderer() { CornellBoxGeometry.Create(out Vertex[] vertices, out uint[] indices, out Material[] materials); indexCount = (uint)indices.Length; - vertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.Vertex - }); + vertexBuffer = App.Context.CreateBuffer(BufferDesc.Vertex((uint)(sizeof(Vertex) * vertices.Length), (uint)sizeof(Vertex))); fixed (Vertex* pointer = vertices) { vertexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) }); } - indexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(uint) * indices.Length), - StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.Index - }); + indexBuffer = App.Context.CreateBuffer(BufferDesc.Index((uint)(sizeof(uint) * indices.Length), sizeof(uint))); fixed (uint* pointer = indices) { indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); } - materialBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Material) * materials.Length), - StrideInBytes = (uint)sizeof(Material), - Flags = BufferUsageFlags.ShaderResource - }); - - fixed (Material* pointer = materials) - { - materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); - } - - constantBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(RasterConstants), - StrideInBytes = (uint)sizeof(RasterConstants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite - }); - - resourceTable = App.Context.CreateResourceTable(new() { Bindings = ResourceBindings }); - resourceTable.Write(0, constantBuffer); - resourceTable.Write(1, materialBuffer); + constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(RasterizationConstants))); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Normal }); - using Shader vertexShader = App.Context.LoadShaderFromFile(ShaderPath("Rasterization.slang"), "VSMain", ShaderStageFlags.Vertex); - using Shader pixelShader = App.Context.LoadShaderFromFile(ShaderPath("Rasterization.slang"), "PSMain", ShaderStageFlags.Pixel); + using Shader vertexShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "VSMain", ShaderStages.Vertex); + using Shader fragmentShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "PSMain", ShaderStages.Fragment); pipeline = App.Context.CreateGraphicsPipeline(new() { - RenderStates = new() + VertexShader = vertexShader, + FragmentShader = fragmentShader, + InputLayouts = [inputLayout], + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = AttachmentFormats, + RenderState = new() { RasterizerState = RasterizerState.CullNone(), DepthStencilState = DepthStencilState.DepthReadWrite(), BlendState = BlendState.Opaque() - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceBindings = ResourceBindings, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = AttachmentFormats + } }); + + materialBuffer = App.Context.CreateBuffer(BufferDesc.StorageReadOnly((uint)(sizeof(Material) * materials.Length), (uint)sizeof(Material))); + + fixed (Material* pointer = materials) + { + materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); + } } public override void Update(CameraHandler camera) { - RasterConstants constants = new() + RasterizationConstants parameters = new() { Model = Matrix4x4.Identity, View = camera.View, Projection = camera.Projection, LightPos = new(278.0f, 547.0f, 280.0f), LightColor = new(2.0f, 1.8f, 1.4f), - CameraPos = camera.Position + CameraPos = camera.Position, + Materials = materialBuffer.StorageReadOnlyHandle }; - constantBuffer.Upload(0, new() { Pointer = (nint)(&constants), SizeInBytes = (uint)sizeof(RasterConstants) }); + constantBuffer.Upload(0, new() { Pointer = (nint)(¶meters), SizeInBytes = (uint)sizeof(RasterizationConstants) }); } public override void Render(CommandBuffer commandBuffer) { - commandBuffer.BeginRenderPass([new() - { - Texture = Color, - LoadOp = LoadOp.Clear, - StoreOp = StoreOp.Store, - ClearColor = new(0.51f, 0.518f, 0.557f, 1.0f) - }], - new() - { - Texture = DepthStencil, - DepthLoadOp = LoadOp.Clear, - DepthStoreOp = StoreOp.Store, - StencilLoadOp = LoadOp.Clear, - StencilStoreOp = StoreOp.Store, - ClearDepth = 1.0f, - ClearStencil = 0 - }); + commandBuffer.BeginRenderPass([ColorAttachment.Clear(Color, new(0.51f, 0.518f, 0.557f, 1.0f))], DepthStencilAttachment.Clear(DepthStencil, 1.0f, 0)); commandBuffer.SetPipeline(pipeline); - commandBuffer.PushResourceTable(resourceTable); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); + commandBuffer.SetConstantBuffer(constantBuffer, 0); + commandBuffer.DrawIndexed(indexCount, 1, 0, 0, 0); commandBuffer.EndRenderPass(); @@ -149,17 +104,17 @@ public override void Dispose() { base.Dispose(); + materialBuffer.Dispose(); + pipeline.Dispose(); - resourceTable.Dispose(); constantBuffer.Dispose(); - materialBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); } } -[StructLayout(LayoutKind.Explicit, Size = 240)] -file struct RasterConstants +[StructLayout(LayoutKind.Explicit, Size = 256)] +file struct RasterizationConstants { [FieldOffset(0)] public Matrix4x4 Model; @@ -178,4 +133,7 @@ file struct RasterConstants [FieldOffset(224)] public Vector3 CameraPos; + + [FieldOffset(240)] + public ResourceHandle Materials; } From 26263541e0bb73a2c8b3297183a3406ec645da7c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 10:47:09 +0800 Subject: [PATCH 123/466] Standardize naming conventions in shaders and C# code Renamed MaterialID to MaterialId, AccumTexture to AccumulationTexture, LightPos to LightPosition, and CameraPos to CameraPosition across both shader and C# files. Updated all related properties, struct fields, and method parameters for consistency between path tracing and rasterization pipelines. Removed redundant Dispose calls to prevent double disposal. --- .../Assets/Shaders/PathTracing.slang | 16 +++++----- .../Assets/Shaders/Rasterization.slang | 30 +++++++++---------- .../CornellBox/Helpers/CornellBoxGeometry.cs | 12 ++++---- .../Renderers/PathTracingRenderer.cs | 6 ++-- .../Renderers/RasterizationRenderer.cs | 8 ++--- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang index 027c3ea7..2e5364a0 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang @@ -8,7 +8,7 @@ struct Vertex { private float4 PositionAndPadding; - private float4 NormalAndMaterialID; + private float4 NormalAndMaterialId; property float3 Position { @@ -20,14 +20,14 @@ struct Vertex property float3 Normal { get { - return NormalAndMaterialID.xyz; + return NormalAndMaterialId.xyz; } } - property uint MaterialID + property uint MaterialId { get { - return asuint(NormalAndMaterialID.w); + return asuint(NormalAndMaterialId.w); } } }; @@ -85,7 +85,7 @@ struct PathTracingConstants DescriptorHandle> Materials; - DescriptorHandle> AccumTexture; + DescriptorHandle> AccumulationTexture; DescriptorHandle> OutputTexture; @@ -366,7 +366,7 @@ float3 TracePath(float3 origin, float3 direction, inout uint rng) normal = -normal; } - Material mat = (*pathTracing.Materials)[v0.MaterialID]; + Material mat = (*pathTracing.Materials)[v0.MaterialId]; if (mat.Emission > 0.0) { @@ -491,7 +491,7 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) float3 color = TracePath(origin, direction, rng); color = min(color, float3(30.0, 30.0, 30.0)); - float4 prev = (*pathTracing.AccumTexture)[pixel]; + float4 prev = (*pathTracing.AccumulationTexture)[pixel]; float4 accumulated; if (pathTracing.FrameCount == 0) @@ -503,7 +503,7 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) accumulated = prev + float4(color, 1.0); } - (*pathTracing.AccumTexture)[pixel] = accumulated; + (*pathTracing.AccumulationTexture)[pixel] = accumulated; float3 avg = accumulated.rgb / float(pathTracing.FrameCount + 1); diff --git a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang index 223d57b3..3f942a82 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang @@ -43,18 +43,18 @@ struct RasterizationConstants float4x4 Projection; - private float4 LightPosAndPadding; + private float4 LightPositionAndPadding; private float4 LightColorAndPadding; - private float4 CameraPosAndPadding; + private float4 CameraPositionAndPadding; DescriptorHandle> Materials; - property float3 LightPos + property float3 LightPosition { get { - return LightPosAndPadding.xyz; + return LightPositionAndPadding.xyz; } } @@ -65,10 +65,10 @@ struct RasterizationConstants } } - property float3 CameraPos + property float3 CameraPosition { get { - return CameraPosAndPadding.xyz; + return CameraPositionAndPadding.xyz; } } }; @@ -77,7 +77,7 @@ struct VSInput { private float4 PositionAndPadding : POSITION0; - private float4 NormalAndMaterialID : NORMAL0; + private float4 NormalAndMaterialId : NORMAL0; property float3 Position { @@ -89,14 +89,14 @@ struct VSInput property float3 Normal { get { - return NormalAndMaterialID.xyz; + return NormalAndMaterialId.xyz; } } - property uint MaterialID + property uint MaterialId { get { - return asuint(NormalAndMaterialID.w); + return asuint(NormalAndMaterialId.w); } } }; @@ -109,7 +109,7 @@ struct PSInput float3 Normal : TEXCOORD1; - nointerpolation uint MaterialID : TEXCOORD2; + nointerpolation uint MaterialId : TEXCOORD2; }; uniform RasterizationConstants rasterization; @@ -159,14 +159,14 @@ PSInput VSMain(VSInput input) output.Position = mul(mul(worldPos, rasterization.View), rasterization.Projection); output.WorldPos = worldPos.xyz; output.Normal = normalize(mul(float4(input.Normal, 0.0), rasterization.Model).xyz); - output.MaterialID = input.MaterialID; + output.MaterialId = input.MaterialId; return output; } float4 PSMain(PSInput input) : SV_TARGET { - Material mat = (*rasterization.Materials)[input.MaterialID]; + Material mat = (*rasterization.Materials)[input.MaterialId]; if (mat.Emission > 0.0) { @@ -178,8 +178,8 @@ float4 PSMain(PSInput input) : SV_TARGET float3 N = normalize(input.Normal); float3 worldPos = input.WorldPos; - float3 V = normalize(rasterization.CameraPos - worldPos); - float3 toLight = rasterization.LightPos - worldPos; + float3 V = normalize(rasterization.CameraPosition - worldPos); + float3 toLight = rasterization.LightPosition - worldPos; float dist = length(toLight); float3 L = toLight / dist; float3 H = normalize(L + V); diff --git a/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs b/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs index 0d378131..3da4d69e 100644 --- a/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs +++ b/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs @@ -97,16 +97,16 @@ private static void AddQuad(List vertices, Vector3 v1, Vector3 v2, Vector3 v3, - uint materialID) + uint materialId) { Vector3 normal = Vector3.Normalize(Vector3.Cross(v1 - v0, v2 - v0)); uint startIndex = (uint)vertices.Count; - vertices.Add(new() { Position = v0, Normal = normal, MaterialID = materialID }); - vertices.Add(new() { Position = v1, Normal = normal, MaterialID = materialID }); - vertices.Add(new() { Position = v2, Normal = normal, MaterialID = materialID }); - vertices.Add(new() { Position = v3, Normal = normal, MaterialID = materialID }); + vertices.Add(new() { Position = v0, Normal = normal, MaterialId = materialId }); + vertices.Add(new() { Position = v1, Normal = normal, MaterialId = materialId }); + vertices.Add(new() { Position = v2, Normal = normal, MaterialId = materialId }); + vertices.Add(new() { Position = v3, Normal = normal, MaterialId = materialId }); indices.Add(startIndex); indices.Add(startIndex + 1); @@ -127,7 +127,7 @@ internal struct Vertex public Vector3 Normal; [FieldOffset(28)] - public uint MaterialID; + public uint MaterialId; } [StructLayout(LayoutKind.Explicit, Size = 32)] diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 220427d3..b45e6f0e 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -148,7 +148,7 @@ public override void Update(CameraHandler camera) Vertices = vertexBuffer.StorageReadOnlyHandle, Indices = indexBuffer.StorageReadOnlyHandle, Materials = materialBuffer.StorageReadOnlyHandle, - AccumTexture = accumulationTexture!.StorageHandle, + AccumulationTexture = accumulationTexture!.StorageHandle, OutputTexture = Color.StorageHandle }; @@ -191,9 +191,9 @@ public override void Dispose() base.Dispose(); accumulationTexture?.Dispose(); + materialBuffer.Dispose(); tlas.Dispose(); blas.Dispose(); - materialBuffer.Dispose(); pipeline.Dispose(); constantBuffer.Dispose(); @@ -236,7 +236,7 @@ file struct PathTracingConstants public ResourceHandle Materials; [FieldOffset(192)] - public ResourceHandle AccumTexture; + public ResourceHandle AccumulationTexture; [FieldOffset(200)] public ResourceHandle OutputTexture; diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index c233af92..20023538 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -77,9 +77,9 @@ public override void Update(CameraHandler camera) Model = Matrix4x4.Identity, View = camera.View, Projection = camera.Projection, - LightPos = new(278.0f, 547.0f, 280.0f), + LightPosition = new(278.0f, 547.0f, 280.0f), LightColor = new(2.0f, 1.8f, 1.4f), - CameraPos = camera.Position, + CameraPosition = camera.Position, Materials = materialBuffer.StorageReadOnlyHandle }; @@ -126,13 +126,13 @@ file struct RasterizationConstants public Matrix4x4 Projection; [FieldOffset(192)] - public Vector3 LightPos; + public Vector3 LightPosition; [FieldOffset(208)] public Vector3 LightColor; [FieldOffset(224)] - public Vector3 CameraPos; + public Vector3 CameraPosition; [FieldOffset(240)] public ResourceHandle Materials; From 2534e62dda9fc6cac6f8b06115432e75db674447 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 10:55:03 +0800 Subject: [PATCH 124/466] Refactor Constant struct to Constants in ImGuiRenderer.cs Renamed the Constant struct to Constants and updated all related references, including variable names, array pools, and shader usages. No functional changes were made. --- .../ImGuiRenderer.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 6e49df80..9b1674dd 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -26,7 +26,7 @@ struct VSOutput float4 Color : COLOR0; }; -struct Constant +struct Constants { float4x4 Projection; @@ -35,7 +35,7 @@ struct Constant DescriptorHandle Sampler; }; -uniform Constant constant; +uniform Constants constants; float3 SrgbToLinear(float3 srgb) { @@ -46,7 +46,7 @@ VSOutput VSMain(VSInput input) { VSOutput output; - output.Position = mul(float4(input.Position, 0.0, 1.0), constant.Projection); + output.Position = mul(float4(input.Position, 0.0, 1.0), constants.Projection); output.UV = input.UV; output.Color = input.Color; @@ -59,7 +59,7 @@ VSOutput VSMain(VSInput input) float4 FSMain(VSOutput input) : SV_TARGET { - return input.Color * (*constant.Texture).Sample(*constant.Sampler, input.UV); + return input.Color * (*constants.Texture).Sample(*constants.Sampler, input.UV); } "; @@ -362,7 +362,7 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, indexBuffer = Context.CreateBuffer(BufferDesc.Index(totalIndexSizeInBytes, sizeof(ushort))); } - uint constantBufferSizeInBytes = (uint)(sizeof(Constant) * (int)(resourceHandleBindings.Count * 1.2)); + uint constantBufferSizeInBytes = (uint)(sizeof(Constants) * (int)(resourceHandleBindings.Count * 1.2)); if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < constantBufferSizeInBytes) { constantBuffer?.Dispose(); @@ -380,9 +380,9 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, indexOffset += drawListPtr.IdxBuffer.Size; } - Constant[] constants = ArrayPool.Shared.Rent(resourceHandleBindings.Count); + Constants[] constantsArray = ArrayPool.Shared.Rent(resourceHandleBindings.Count); - Constant constant = new() + Constants constants = new() { Projection = Matrix4x4.CreateOrthographicOffCenter(drawData.DisplayPos.X, drawData.DisplayPos.X + drawData.DisplaySize.X, @@ -395,15 +395,15 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, for (int i = 0; i < resourceHandleBindings.Count; i++) { - constants[i] = constant with { Texture = resourceHandleBindings[i] }; + constantsArray[i] = constants with { Texture = resourceHandleBindings[i] }; } - fixed (Constant* pointer = constants) + fixed (Constants* pointer = constantsArray) { - constantBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Constant) * resourceHandleBindings.Count) }); + constantBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Constants) * resourceHandleBindings.Count) }); } - ArrayPool.Shared.Return(constants); + ArrayPool.Shared.Return(constantsArray); commandBuffer.BeginDebugEvent("ImGui"); @@ -445,7 +445,7 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, } commandBuffer.SetScissors([scissor]); - commandBuffer.SetConstantBuffer(constantBuffer, (uint)(sizeof(Constant) * resourceHandleBindings.IndexOfKey(drawCmd.TexRef.GetTexID()))); + commandBuffer.SetConstantBuffer(constantBuffer, (uint)(sizeof(Constants) * resourceHandleBindings.IndexOfKey(drawCmd.TexRef.GetTexID()))); commandBuffer.DrawIndexed(drawCmd.ElemCount, 1, (uint)(drawCmd.IdxOffset + indexOffset), (int)(drawCmd.VtxOffset + vertexOffset), 0); } } @@ -514,7 +514,7 @@ private void RemoveDestroyedBindings() } [StructLayout(LayoutKind.Explicit, Size = 256)] -file struct Constant +file struct Constants { [FieldOffset(0)] public Matrix4x4 Projection; From d700c76cf2487f2feb1317c45af519b8357e863e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 11:13:43 +0800 Subject: [PATCH 125/466] Refactor shader loading to use dynamic source generation Removed embedded shader bytecode arrays and related selection logic. Shaders are now generated from source at runtime based on color space, improving maintainability and reducing binary size. --- .../ImGuiRenderer.cs | 73 +------------------ 1 file changed, 4 insertions(+), 69 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 9b1674dd..9553f59b 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -63,42 +63,6 @@ float4 FSMain(VSOutput input) : SV_TARGET } "; - private readonly string[] Dxil = - [ - // Vertex Shader - Legacy - "4458424322A36D6E02DEF08BE6B9959E5635FACE0100000038120000070000003C0000004C000000D40000006001000090020000DC090000F8090000534649300800000000000000000000004953473180000000030000000800000000000000680000000000000000000000030000000000000003030000000000000000000071000000000000000000000003000000010000000303000000000000000000007A000000000000000000000003000000020000000F0F000000000000504F534954494F4E00544558434F4F524400434F4C4F52004F5347318400000003000000080000000000000068000000000000000100000003000000000000000F00000000000000000000007400000000000000000000000300000001000000030C000000000000000000007D000000000000000000000003000000020000000F0000000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200005053563028010000340000000100000000000000000000000000000000000000FFFFFFFF010000000303000303000000000000000000000000000000280000000100000018000000020000000000000000000000000000000D000000000000003000000000504F534954494F4E00544558434F4F524400434F4C4F5200544558434F4F524400434F4C4F520056534D61696E0000010000000000000010000000010000000000000001004200030000000A000000000000000101420003000000130000000000000001024400030000000000000000000000010044030304000019000000000000000101420003020000220000000000000001024400030200000F0000000F00000000000000000000001000000020000000000000000000000000010000000200000004000000080000535441544407000066000100D10100004458494C06010000100000002C0700004243C0DE210C0000C80100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010016000000321E981419114C908C092647C60443224AA01846000A30A00C8AA010CAA124CAA3200AAC28A82889118022288432A0B000010111A89B01206F0680BE190002C77218E679000002030000111002C1000405E2660000000000791800009F0000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241300C0A76731B06842026081F47E78CED4D2C8C0D2A4C2EAC6DEE0B668240281B1043590C6360800D41B3810000079820741A97B137B739BA3037BAB92F980902B16C208848323608CC3441E03C3250726F6A656374696F6E5F30130482992010CD8601C3860902E14C108867034258572619DAB64160B80982B76D208845323608CCB7E13020AAEA3C30982008C00660C3608CC1186C08C860C3308841194C10C0A0DB109C010DABA9A6B0343722504F534954494F4E1384829A2014D586C0982014D6064192362C861AAC011BB4011B0C6E60B0C11B10A12AC21A7A7A9222DAB00C71B0066CD0066C30B8C1C0066FC062E889E9496A82505C130402DA204875B061B9E6600DD8A00DD860A0838B0DEC60C300077270075CA6ACBEA0DEE6D2E8D2DEDC260805B66131F2600DF4A00DE860A003830DEC60C332C4C11AB0411BB8C1E006031BBCC186E59A83356083367083810E2E36B0830DC31EF0411F6C18F0C00F80094218641B04031436146290067FF084020D33B6B730BAB9090211B148739BA39B9B2010128DB9B4B32F36321A7369675F7374130462DA808C02299482299C0229A0422A546163B36B7349232B73A39B120455C8F05CECCAE4E6D2DEDCA6044413323C17BB3036BB32B9298151870CCF650E2D8CAC4CAEE98DAC8C6D4A809421C373912B9B7BAB931B2B9B9B123895C8F05CE8F2E0CA82DCDCDEE8C2E8D2DEDCE6A60860500675C8F05CECD2CAEE92C8A6E8C2E8CAA6046750870CCFA5CC8D4E2E0FEA2DCD8D6E6E4A100A5DC8F05CC6DEEADCE8CAE4E6A604A900791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C38CC821077C70037210877370037B080779608770C88777A8077A98813CE4800F6E400FE5D00EF0000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC360000000000000048415348140000000000000082D156255E85851D3995C0A0E0DA81FE4458494C38080000660001000E0200004458494C0601000010000000200800004243C0DE210C0000050200000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010011000000321E981419114C908C092647C60443224AA0188A6204A00003CAA008CA838A92180128824228030A0B10101081C0B11C86791E0080C000004004844030004181BE19000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241180C0A76731B0684202608843241F82C02130462D980280BA32843036C089C0D04003CC004010CAA0D41344110001A56534D61696E44A09EA692A8929E9C2608C5334128A00D81324128A20902C16C10346DC3A254D6855D43A65C1B11AA22ACA1A72729A20DCBD05917760DD9706D1304A26131F4C4F424354128A40902E16C103431D8B07C60605DD83584C1778DC18681F3C880CB94D517D4DB5C1A5DDA9BDB04A198362C8A1958678085C11006CA35061B96A1B32E2C1BB2E1DA362C1F185817960D61F05D63B06140833450830D4319AC01304108036A83A0B4C18662A2D80072832A6C6C766D2E6964656E745382A00A199E8B5D99DC5CDA9BDB9480684286E76217C6665726372530EA90E1B9CCA1859195C935BD9195B14D09903264782E7265736F75726365735382A70E199E8B5D5AD95D12D9145D185DD99420AA4386E752E646279707F596E646373725700300000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C38CC821077C70037210877370037B080779608770C88777A8077A98813CE4800F6E400FE5D00EF0000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC36000000612000007A0000001304412C100000000900000044944221CC001457D995EC40C10E9406152540D508005173108AA24414CD21780B000000230608008260607587900D230607008260207D47208C18240008828141060DB655CC8841028020181865E064DCD18C18240008828161068FD659CE8841028020181867006D5EF58C182400088281810691187C1934629000200806461A486300064B34629000200806861A4C6110069B34629000200806C61A50622006CD346270002008068D1A48C9188C2604C06882108C260CC26802318C181C00088241F30697B306A30901309A2004A30983309A400C230607008260D0D00137ADC16842008C2608C168C2208C26108339917C460C100004C1E0C983318894C08C003A0651F219314000100483870FCC8062020B10E89874C967C4000140100C9E3F4883CB092C50A06394269F1103040041307844810D3428B08081CE88410280201820A610077EE00778A08C182400088201620A71E0077E9006C788410280201820A610077EE00777408C182400088201620A71E0077E5007C188410280201820A61007A1E00778F08D182400088201620A71100A7E9006DE88410280201820A610077BE0077830062306090082608098421CEC811FA481188C182400088201620A71B0077E700761306290002008068829C4C11EF8411D80010200000000", - - // Vertex Shader - Linear - "44584243D753A620DDB4C72961AF9BB49563BC6A0100000094120000070000003C0000004C000000D40000006001000090020000E0090000FC090000534649300800000000000000000000004953473180000000030000000800000000000000680000000000000000000000030000000000000003030000000000000000000071000000000000000000000003000000010000000303000000000000000000007A000000000000000000000003000000020000000F0F000000000000504F534954494F4E00544558434F4F524400434F4C4F52004F5347318400000003000000080000000000000068000000000000000100000003000000000000000F00000000000000000000007400000000000000000000000300000001000000030C000000000000000000007D000000000000000000000003000000020000000F0000000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200005053563028010000340000000100000000000000000000000000000000000000FFFFFFFF010000000303000303000000000000000000000000000000280000000100000018000000020000000000000000000000000000000D000000000000003000000000504F534954494F4E00544558434F4F524400434F4C4F5200544558434F4F524400434F4C4F520056534D61696E0000010000000000000010000000010000000000000001004200030000000A000000000000000101420003000000130000000000000001024400030000000000000000000000010044030304000019000000000000000101420003020000220000000000000001024400030200000F0000000F00000000000000000000001000000020000000000000000000000000010000000200000004000000080000535441544807000066000100D20100004458494C0601000010000000300700004243C0DE210C0000C90100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010016000000321E981819114C908C092647C60443224AA01846000A30A00C8AA010CAA124CAA35CCAAF208A828A92180128824228030A0B10101081BA1900F26600E89B0120702C87619E070020300000100121100C4050206E0600000079180000A00000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241300C0A76731B06842026085F47E78CED4D2C8C0D2A4C2EAC6DEE0B668240281B1043590C6360800D41B3810000079820741B97B137B739BA3037BAB92F980902B16C208848323608CC3441E03E3250726F6A656374696F6E5F30130482992010CD8601C3860902E14C108867034258572619DAB64160B80982C76D208845323608CCB7E13020AAEA3C30982008C00660C3608CC1186C08C860C3308841194C10C0C0DB109C010DABA9A6B0343722504F534954494F4E1384A29A2014D686C0982014D7064192362C861AAC011BB4011B0C6E60B0C11B10A12AC21A7A7A9222DAB00C71B0066CD0066C30B8C1C0066FC062E889E9496A825060130402DA204875B061B9E6600DD8A00DD860A0838B0DEC60C300077270075CA6ACBEA0DEE6D2E8D2DEDC260845B66131F2600DF4A00DE860A003830DEC60C332C4C11AB0411BB8C1E006031BBCC186E59A83356083367083810E2E36B0830DC31EF0411F6C18F0C00F80094218681B04031436146290067FF084020D33B6B730BAB9090211B148739BA39B9B2010128DB9B4B32F36B2090231D1984B3BFB9AA39B2010D406641448A1144CE11450211554A10A1B9B5D9B4B1A59991BDD9420A84286E76257263797F6E63625209A90E1B9D885B1D995C94D098C3A64782E7368616465724D6F64656C5302A40C199E8B5CD9DC5B9DDC58D9DC94C0A94486E74297075716E4E6F646174697F6E6363745008332A84386E76297567697443645174657362538833A64782E656E747279506F696E7473538250E84286E732F656E7465726373725500500791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF4000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC3600000000000000484153481400000000000000F9DD1AE5250EB9E439EBECBF95D9943F4458494C9008000066000100240200004458494C0601000010000000780800004243C0DE210C00001B0200000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010011000000321E981419114C908C092647C60443224AA0188A6204A00003CAA008CA838A92180128824228030A0B10101081C0B11C86791E0080C000004004844030004181BE19000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241180C0A76731B0684202608843241F82C02130462D980280BA32843036C089C0D04003CC004010CAA0D41344110001A56534D61696E44A09EA692A8929E9C2608C5334128A00D81324128A20902C16C10346DC3A254D6855D43A65C1B11AA22ACA1A72729A20DCBD05917760DD9706D1304A26131F4C4F424354128A40902E16C103431D8B07C60605DD83584C1778DC18681F3C880CB94D517D4DB5C1A5DDA9BDB04A198362C8A1958678085C11006CA35061B96A1B32E2C1BB2E1DA362C1F185817960D61F05D63B06140833450830D4319AC01304108036A83A0B4C18662A2D80072832A6C6C766D2E6964656E745382A00A199E8B5D99DC5CDA9BDB9480684286E76217C6665726372530EA90E1B9CCA1859195C935BD9195B14D09903264782E7265736F75726365735382A70E199E8B5D5AD95D12D9145D185DD99420AA4386E752E646279707F596E646373725700300000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF4000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC3600000061200000900000001304412C100000000E000000548D005051024494462914C20C4071955DC90E14EC000D6304A2B9EAA4374640DA682F7F14CD21640B5173108AA244348C11C8288DA71F002306080082606081C12170CF88C10180201848627004C48841028020181867D06C1EC68C18240008828181060EF745CD8841028020181869F07460903923060900826060A801E48501F68C182400088281B10651198801078D182400088281C10692198C41158D182400088281D10613199081278D182400088281E106541994C1358D181C00088241D306526206A30901309A2004A30983309A400C230607008260D0C8C1E5B8C16842008C2608C168C2208C2610C388C1018020183477C04D6E309A1000A30942309A3008A309C4604E249F11030400413078F8600C22253023808E41947C460C100004C1E0F90333A098C002043A265DF2193140001004834714D2E072020B14E818A5C967C4000140100C9E5260030D0A2C60A063636006F2B1313803F9D818A0817C6C6003F8D8D006F0B1C10DE063431AC8C78634908F0D69201F1BE6003E36D0017C6CA803F8D8F006F2B1E10DE463C31BC867C4200140100C105C000558800555C8460C120004C100C1055080055810056BC4200140100C105C00055880855498460C120004C100C10550800558380568C4200140100C105C00855980055570831183040041304070011466011644A10D460C120004C100C10550680558508561C4200140100C105C00855680055110460C120004C100C10550680558488560C4200140100C105C0085568085537803040000000000", - - // Fragment Shader - "44584243EE63AC19F937568890164F4A4BFF155601000000D4100000070000003C0000004C000000D800000014010000240200001C0900003809000053464930080000000000000000000000495347318400000003000000080000000000000068000000000000000100000003000000000000000F000000000000000000000074000000000000000000000003000000010000000303000000000000000000007D000000000000000000000003000000020000000F0F00000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200004F5347313400000001000000080000000000000028000000000000004000000003000000000000000F0000000000000053565F5461726765740000005053563008010000340000000000000000000000000000000000000000000000FFFFFFFF000000000301000301000000000000000000000000000000100000000200000018000000010000000000000000000000000000000E000000000000000300000000000000000000000000000002000000000000001800000000544558434F4F524400434F4C4F520050534D61696E000001000000000000001000000000000000000000000100440303040000010000000000000001014200030200000A00000000000000010244000302000000000000000000000100441003000000000000000000000000000000000000000F0000000F00000000000000000000000100000002000000040000000800000053544154F006000066000000BC0100004458494C0601000010000000D80600004243C0DE210C0000B30100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A32528848901420434688A500193242E4480E909122C4504151818CE183E58A0429460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000005000000032224809206485049322A484049322E384A19014124C8A8C0B84A44C107C23002500146600E608C0608E0029C620841442A61880105206A19B86CB9FB08790FC95905662F28BDB46C51863102AF70C973F610F21F921D00C0B8182551845181B630C42C8A076DB70F913F61092BF12924345029146CE43441342484820A4108CB047F0A0E1F227EC21247F25A40D6906441042CA1C41500A46249944070286118861A636180776088779980737A08572C0077AA8077928073920053EB0877218077A780779E0037360877708077A600330A0033F00033FD0033D688774808779F8057AC807782807149099C4601CD8211CE6611EDC8016CA011FE8A11EE4A11CE48014F8C01ECA611CE8E11DE4810FCC811DDE211CE8810DC0800EFC000CFC00091753BE499A224A987C16609E8588D80998081410DAE940E608400100000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000431E0808800000000000000000863C1410000100000000000000648100000011000000321E981419114C908C092647C604432225500C230045501265501E855030544AA20C0A6104A0080A847C01020222109F01A03E96C310000000CF0300814020A46700000079180000870000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304C2982010C7066120260804B241300C0A70731B06842026081AC684AE0C8FAE4EAEEC0B66824024130442D92018CD86C45016C31818C3D9103C1304CE623217D606C75626F705B3013122C9300603D8104C1B080800A80982006C00360CC6756D08B00DC360651384EEDA106C34A0A69AC2D2DCB84C597D41BDCDA5D1A5BDB94D108A678250401B0263825044138442DAB018DE0706612006831818630010A12AC21A7A7A92229A2014D304815836086770061B96A10CBE3108033318CC601803346031F4C4F424354120980DC219ACC1868551836F0CC2C00C063160C6800D360C6490066DC064CAEA8B2A4CEEAC8C6E8250501B16E30D3E3808833118C4C0180336D810C4C186C10DE400982078D506C1A0830D85D5CD41550734CCD8DEC2E8E6260844C322CD6D8E6E6E82403834E6D2CEBED8C868CCA59D7DCDD111A12BC3FB727B936BDBA0DC011EE4811EEC01C10778D00743153636BB369734B23237BA294150850CCFC5AE4C6E2EEDCD6D4A403421C373B10B63B32B939B121875C8F05CE6D0C2C8CAE49ADEC8CAD8A6044819323C17B9B2B9B73AB9B1B2B9290155890CCF852E0FAE2CC8CDED8D2E8C2EEDCD6D6E4A90D521C373B14B2BBB4B229BA20BA32B9B126C75C8F05CCADCE8E4F2A0DED2DCE8E6A60475D0850CCF65ECADCE8DAE4C6E6E4AD007000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF400000071200000190000000660A4AC09208D1150C3E53B8F0F348D3301131102CDB01056D00D97EF3CBE1011C0448440332CC41739CC863403D21816300D97EF3CFEE20083D83CD4E417B76D03D070F9CEE34B00F32C845FDCB60954C3E53B8F2F4D4E44A0D4F450935FDC36000000000000004841534814000000000000009F974238F30DDDF016CFF4F093C3E59C4458494C9407000066000000E50100004458494C06010000100000007C0700004243C0DE210C0000DC0100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A32528848901420434688A500193242E4480E909122C4504151818CE183E58A0429460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000005000000032224809206485049322A484049322E384A19014124C8A8C0B84A44C107C23002500146600E608C0608E0029C620841442A61880105206A19B86CB9FB08790FC95905662F28BDB46C51863102AF70C973F610F21F921D00C0B8182551845181B630C42C8A076DB70F913F61092BF12924345029146CE43441342484820A4108CB047F0A0E1F227EC21247F25A40D6906441042CA1C41500A46249944070286118861A636180776088779980737A08572C0077AA8077928073920053EB0877218077A780779E0037360877708077A600330A0033F00033FD0033D688774808779F8057AC807782807149099C4601CD8211CE6611EDC8016CA011FE8A11EE4A11CE48014F8C01ECA611CE8E11DE4810FCC811DDE211CE8810DC0800EFC000CFC00091753BE499A224A987C16609E8588D80998081410DAE940E608400100000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000431E0808800000000000000000863C1410000100000000000000648100000011000000321E981419114C908C092647C604432225500C4531025004255106E541A524CAA01046008AA040C81720202002F11900EA63390C010000F03C00100804427A060000000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304C2982010C7066120260804B241180C0A70731B0684202608443241D02E02130442992010CB0641713624CAC228CAD028CF86009A2070D5064491184519146043306D202200A026089DB521B026080240036AAA292CCD8DCB94D517D4DB5C1A5DDA9BDB04A1702608C5B32150260805344128A20D8BA26D5CE70D9EF20144A88AB0869E9EA488260885344120980DC2188CC186650883EDEBC4601083E1230316434F4C4F521304A2D9208CC1196C581A33D8BE4E0C06AFF9D060C300066590064CA6ACBEA8C2E4CECAE82608C5B46151D6606383EE1B3CE543830D411B6C18D4C00D800982476D101438D85060D91B547150858DCDAECD258DACCC8D6E4A105421C373B12B939B4B7B739B12104DC8F05CECC2D8ECCAE4A604461D323C9739B430B232B9A637B232B6290152860CCF45AE6CEEAD4E6EAC6C6E4A40D521C373B14B2BBB4B229BA20BA32B9B125875C8F05CCADCE8E4F2A0DED2DCE8E6A6047100000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF400000071200000190000000660A4AC09208D1150C3E53B8F0F348D3301131102CDB01056D00D97EF3CBE1011C0448440332CC41739CC863403D21816300D97EF3CFEE20083D83CD4E417B76D03D070F9CEE34B00F32C845FDCB60954C3E53B8F2F4D4E44A0D4F450935FDC3600000061200000490000001304412C100000000B00000034470088CC0014422994EC40C10E94244471141E9512A03787A07873085F427204600E22499287C60C0000002306080082604089C13278CF8801028020185063C00C1F34629000200806061A3C1F1860D088410280201818690081411824D1884102802018186A1085811864D2884102802018186B2089C11874D3884102802018186C309D011970D4884102802018186D40A1411934D588C101802018406B2021CD88C101802018406C3021CD88C103802018346E100901312C0B1AA001B68C2604C06882108C260CC268023118D1C8C788463E4634F231A291CF8841028020182078F0C5411CA40131629000200806081E7C711007D930629000200806081E7C71100768208C18240008820182075F1CC4C11A0408000000000000" - ]; - - private readonly string[] Spirv = - [ - // Vertex Shader - Legacy - "030223070006010000002800490000000000000011000200010000000E00030000000000010000000F000C00000000000200000056534D61696E0000360000003A0000003D000000150000002B000000310000001C000000030003000B00000001000000050009000B000000436F6E7374616E74735F7374643134305F6C6F676963616C00000000060006000B0000000000000050726F6A656374696F6E00000500060015000000696E7075742E506F736974696F6E0000050007001A000000436F6E7374616E74735F73746431343000000000060006001A0000000000000050726F6A656374696F6E00000500070019000000476C6F62616C506172616D735F73746431343000060006001900000000000000636F6E7374616E7473000000050006001C000000676C6F62616C506172616D7300000000050005002B000000696E7075742E5556000000000500050031000000696E7075742E436F6C6F7200050009003A000000656E747279506F696E74506172616D5F56534D61696E2E555600000005000A003D000000656E747279506F696E74506172616D5F56534D61696E2E436F6C6F7200000000050004000200000056534D61696E000047000400150000001E00000000000000480005001A000000000000002300000000000000480004001A0000000000000005000000480005001A0000000000000007000000100000004700030019000000020000004800050019000000000000002300000000000000470004001C0000002100000000000000470004001C0000002200000000000000470004002B0000001E0000000100000047000400310000001E0000000200000047000400360000000B00000000000000470004003A0000001E00000000000000470004003D0000001E0000000100000013000200010000002100030003000000010000001600030006000000200000001700040007000000060000000400000017000400080000000600000002000000180004000C00000007000000040000001E0003000B0000000C000000150004000F00000020000000010000002B0004000F0000001000000000000000200004001400000001000000080000002B0004000600000017000000000000002B00040006000000180000000000803F1E0003001A0000000C0000001E000300190000001A000000200004001B0000000200000019000000200004001D000000020000001A0000002000040030000000010000000700000020000400350000000300000007000000200004003900000003000000080000003B0004001400000015000000010000003B0004001B0000001C000000020000003B000400140000002B000000010000003B0004003000000031000000010000003B0004003500000036000000030000003B000400390000003A000000030000003B000400350000003D000000030000003600050001000000020000000000000003000000F8000200040000003D000400080000001300000015000000500006000700000016000000130000001700000018000000410005001D0000001E0000001C000000100000003D0004001A0000001F0000001E000000900104000B000000200000001F000000510005000C00000048000000200000000000000091000500070000002500000048000000160000003D000400080000002A0000002B0000003D000400070000002F000000310000003E00030036000000250000003E0003003A0000002A0000003E0003003D0000002F000000FD00010038000100", - - // Vertex Shader - Linear - "030223070006010000002800750000000000000011000200010000000E00030000000000010000000F000C00000000000200000056534D61696E0000500000005400000057000000150000002B000000310000001C000000030003000B00000001000000050009000B000000436F6E7374616E74735F7374643134305F6C6F676963616C00000000060006000B0000000000000050726F6A656374696F6E00000500060015000000696E7075742E506F736974696F6E0000050007001A000000436F6E7374616E74735F73746431343000000000060006001A0000000000000050726F6A656374696F6E00000500070019000000476C6F62616C506172616D735F73746431343000060006001900000000000000636F6E7374616E7473000000050006001C000000676C6F62616C506172616D7300000000050005002B000000696E7075742E5556000000000500050031000000696E7075742E436F6C6F72000500090054000000656E747279506F696E74506172616D5F56534D61696E2E555600000005000A0057000000656E747279506F696E74506172616D5F56534D61696E2E436F6C6F7200000000050004000200000056534D61696E000047000400150000001E00000000000000480005001A000000000000002300000000000000480004001A0000000000000005000000480005001A0000000000000007000000100000004700030019000000020000004800050019000000000000002300000000000000470004001C0000002100000000000000470004001C0000002200000000000000470004002B0000001E0000000100000047000400310000001E0000000200000047000400500000000B0000000000000047000400540000001E0000000000000047000400570000001E0000000100000013000200010000002100030003000000010000001600030006000000200000001700040007000000060000000400000017000400080000000600000002000000180004000C00000007000000040000001E0003000B0000000C000000150004000F00000020000000010000002B0004000F0000001000000000000000200004001400000001000000080000002B0004000600000017000000000000002B00040006000000180000000000803F1E0003001A0000000C0000001E000300190000001A000000200004001B0000000200000019000000200004001D000000020000001A00000020000400300000000100000007000000170004003300000006000000030000002B000400060000003B00000012519C3E2B000400060000003D000000C4A22E3F2B0004000600000041000000C22C4D3C200004004F0000000300000007000000200004005300000003000000080000003B0004001400000015000000010000003B0004001B0000001C000000020000003B000400140000002B000000010000003B0004003000000031000000010000003B0004004F00000050000000030000003B0004005300000054000000030000003B0004004F00000057000000030000002C000600330000006D0000003D0000003D0000003D0000002C000600330000006E0000004100000041000000410000003600050001000000020000000000000003000000F8000200040000003D000400080000001300000015000000500006000700000016000000130000001700000018000000410005001D0000001E0000001C000000100000003D0004001A0000001F0000001E000000900104000B000000200000001F000000510005000C0000006C00000020000000000000009100050007000000250000006C000000160000003D000400080000002A0000002B0000003D000400070000002F000000310000004F00080033000000340000002F0000002F0000000000000001000000020000008E000500330000005D000000340000003B00000081000500330000005F0000005D0000006D000000850005003300000060000000340000005F000000810005003300000062000000600000006E00000085000500330000006300000034000000620000005100050006000000470000006300000000000000520006000700000070000000470000002F00000000000000510005000600000049000000630000000100000052000600070000007200000049000000700000000100000051000500060000004B00000063000000020000005200060007000000740000004B00000072000000020000003E00030050000000250000003E000300540000002A0000003E0003005700000074000000FD00010038000100", - - // Fragment Shader - "0302230700060100000028001E0000000000000011000200010000000E00030000000000010000000F000A00040000000200000050534D61696E000011000000150000001D0000000D00000009000000100003000200000007000000030003000B000000010000000500050009000000696E7075742E436F6C6F7200050005000D000000696E7075742E55560000000005000400110000007465787475726500050004001500000073616D706C657200050006001800000073616D706C6564496D61676500000000050004001900000073616D706C656400050008001D000000656E747279506F696E74506172616D5F50534D61696E0000050004000200000050534D61696E000047000400090000001E00000001000000470004000D0000001E0000000000000047000400110000002100000001000000470004001100000022000000000000004700040015000000210000000200000047000400150000002200000000000000470004001D0000001E0000000000000013000200010000002100030003000000010000001600030005000000200000001700040006000000050000000400000020000400080000000100000006000000170004000A0000000500000002000000200004000C000000010000000A000000190009000E000000050000000100000002000000000000000000000001000000000000002000040010000000000000000E0000001A00020012000000200004001400000000000000120000001B000300170000000E000000200004001C00000003000000060000003B0004000800000009000000010000003B0004000C0000000D000000010000003B0004001000000011000000000000003B0004001400000015000000000000003B0004001C0000001D000000030000003600050001000000020000000000000003000000F8000200040000003D0004000600000007000000090000003D0004000A0000000B0000000D0000003D0004000E0000000F000000110000003D0004001200000013000000150000005600050017000000180000000F00000013000000570006000600000019000000180000000B0000000000000085000500060000001B00000007000000190000003E0003001D0000001B000000FD00010038000100" - ]; - - private readonly string[] Metallib = - [ - // Vertex Shader - Legacy - "4D544C4201800200070000810E000000181300000000000058000000000000008A00000000000000160100000000000039000000000000004F0100000000000008000000000000005701000000000000300E000000000000010000008A0000004E414D45070056534D61696E00545950450100004841534820000EAC3941BE1430927847F5EEDB1D3E2EC5534205A80228D8C9BA1EB0FC38937B4F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800300E00000000000052464C5408000400000000000000454E4454524C53541000870F000000000000910300000000000055554944100064EF3F7D1C4B30FF8EC74A1716027506454E4454390000005641545420000300506F736974696F6E5F3100008055565F31000180436F6C6F725F310002805641545905000300040406454E445408000000454E4454DEC0170B0000000014000000140E0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C00004C0300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C800000089200000250000003222480920624600212B249814212524981419270C85A4906052645C20246582209A0118462080610401B843104109C24CD43CD0833CD4C338D0831BB44339D04338B0831EE8413B84033DC8433AE0430A084AD21451C2E4734E234D4033493F9D82829534459430F9E0E204C0920211C0483F9D0233887008A6184204446920608E000CE608406110211086110866040000000051180000630000001BFA23F8FFFFFFFF013005C00F003800FE00908009A0803E20C2011EE0411EDE011FDAC01CEAC11DC6A10DCC011EDAA01DC2811ED001A00779A8877200087390877068877268037878877470077A28077900C2811DD80120DA211DDCA10DD8A11CCE211CD8A10DECA11CC6811EDE411EDAE01ED2811CE8011D803890033C00067778873610877A480776A08774708779000877788736480777308779680373808736688770A0077400E8411EEAA11C00C21DDEA10DDC211CDC611EDAC01CE0A10DDA211CE8011D007A90877A280780708777688379488773708772208736D0877290877798873630077868837608077A4007801EE4A11ECA0120DCE11DDA801EE4211CE0011ED2C11DCEA10DDA211CE8011D007A90877A28078098077A08877158873680077978077A288771A0877790873610877A30077328077968837948077D2807000F00A21EDC611EC2C11CCAA10DCC011EDAA01DC2811ED001A00779A8877200367402012C002900D5100EE9200F6D200EF5600EE6500EF2D006EEF00E6D100EEC900EE1300F00000049180000010000001384400013B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69BCAC0920CD900A30120000020000000100000000000390D82050D4690000200B0400000A000000321E981019114C908C092647C6044362255008E55004E5538002055120C530025006051800000000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A80778980700000000792000001D010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A398066400560000004A63611BB44171506C1C193091C128916310CB01490A7128484421CCB24497E30000000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E706F736974696F6E6169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D65506F736974696F6E5F306169722E7665727465785F6F75747075747573657228544558434F4F524429666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E7665727465785F696E7075746169722E6C6F636174696F6E5F696E646578506F736974696F6E5F3155565F31436F6C6F725F316169722E6275666665726169722E726561646169722E616464726573735F73706163656169722E7374727563745F747970655F696E666F666C6F617434783450726F6A656374696F6E5F30436F6E7374616E74735F30636F6E7374616E74735F306169722E6172675F747970655F73697A656169722E6172675F747970655F616C69676E5F73697A65476C6F62616C506172616D735F30676C6F62616C506172616D735F3026760000000000003082C00423088C3082C00C23080C3182C01423088C3182C01C23080C3282C02423088C3282C02C23080A3082C030330C6A10ACC10C031B086D30C3C006831BCC30B001E106330C6C50B8C10C031B186F30C3C006071CCC30B00112073304C90C431BC8C11CCC402874A006733043B0CC10303304CD0C85F34091348331514F15593318D3F5401136C3500AA6700A332473906973A0064F156D33246A90696AA0064F157133246D90696DA0060F147533147360077320066330C31106AC3007763007645006334874E06973A0061F18B44118B48219D8C119C4C18306511ACC40A4822AAC822BCC30D4012ABCC29D01C0711CC7711CC7711CC7B9811BB8811BB8811BB8811B5874A0079665B9011DD0011DE0022EE0022B90836A80828C042628233636BB3697B637B23AB6321733B6B0B3B951103BB8033CC8033DD8033EE88354D8D8ECDA5CD2C8CADCE84609FC2097B0343917BB32B9B9B437B751823F482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D940014720A4B9373197B6B834B632BFB7A83A34B7B739B1B65080551188554C2D2E45CECCAE4E8CAF046095E01000000A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000360000001304412C1000000006000000D446004AA00CE88D008C45044110909801A0310300000000F1300000140000002247C890510A841C00000000980500006169722D616C6961732D73636F7065732856534D61696E296169722D616C6961732D73636F70652D61726728332900002B8459888515032DCC822C6C08680100FD18C8E138CE414128830C0D61A010887F3F06B34892A45010CA204384242804E28F4101FEFD1811745D974341283804E03FDBD004C06C437005B30DC1256C1010030000040000005B86E0A0852D4371D0C2964139680100000000007120000003000000320E1022840096060000000000000000650C00001F000000120394F0000000000300000006000000090000004C000000010000005800000000000000580000000100000070000000000000000F0000001C00000000000000060000000600000000000000700000000000000000000000010000000000000000000000060000000000000006000000FFFFFFFF00240000000000005D0C00000E0000001203946B0000000056534D61696E33323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000000000000000000010000008D030000524255467D03000000000000000000000018000000414952521000180004001000000000000000140010000000000000000400000000000000100000000400000001000000000000000A000000E00200009002000034020000F0010000AC0100007C0100004C010000D40000006800000004000000F4FDFFFF01000800040000009EFFFFFF04000000010000001400000010001000000000000400000008000C0010000000400000001C000000040000000C00000050726F6A656374696F6E5F300000000008000000666C6F617434783400000000B0FDFFFF010008000C0000000000060008000400060000000400000001000000140000001000140004000000080000000C00100010000000090000004000000018000000040000000B000000636F6E7374616E74735F30000B000000436F6E7374616E74735F3000BCFEFFFF00000400200000001C0024000000000008000C000600070010000000140018001C0020001C0000000000010200000000010000000800000040000000100000001C000000040000000E000000676C6F62616C506172616D735F3000000E000000476C6F62616C506172616D735F30000030FFFFFF0420040004000000A6FFFFFF0200000001000000500100000400000007000000436F6C6F725F31005CFFFFFF0420040004000000D2FFFFFF0100000001000000CC000000040000000400000055565F3100000000E4FEFFFF042004001400000000000E0014000000040008000C0010000E000000000000000100000090000000040000000A000000506F736974696F6E5F310000C8FFFFFF0400020004000000C0FFFFFF18000000BC0000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290008000C000400080008000000040002001400000010001000000004000000000008000C00100000002400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000BCFFFFFF020002001400000000000E000C00000000000000040008000E00000018000000040000000A000000506F736974696F6E5F30000006000000666C6F617434000008000E000400080008000000030000001000000000000A001000040008000C000A000000300000001C000000040000000400000004000000050000000600000007000000030000000100000002000000030000000600000056534D61696E0000454E4454", - - // Vertex Shader - Linear - "4D544C4201800200070000810E000000881300000000000058000000000000008A00000000000000160100000000000039000000000000004F0100000000000008000000000000005701000000000000A00E000000000000010000008A0000004E414D45070056534D61696E005459504501000048415348200018279E3BBD163C92118F96D26DD622BB504BD02519E79C64165EE9EDB89C0D444F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800A00E00000000000052464C5408000400000000000000454E4454524C53541000F70F0000000000009103000000000000555549441000C24CC1BA27A533429FAEB8AF316BA7F7454E4454390000005641545420000300506F736974696F6E5F3100008055565F31000180436F6C6F725F310002805641545905000300040406454E445408000000454E4454DEC0170B0000000014000000840E0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000680300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C800000089200000270000003222480920624600212B249814212524981419270C85A4906052645C20246582609A0118462080610401B843104109C24CD43CD0833CD4C338D0831BB44339D04338B0831EE8413B84033DC8433AE0430A084AD21451C2E4734E234D4033493F9D82829534459430F9E0E204C0920211C0483F9D0233887008A6184204446920608E000CE6084061102110861108661861608611066004000000000051180000630000001BFA23F8FFFFFFFF013005C00F003800FE00908009A0803E20C2011EE0411EDE011FDAC01CEAC11DC6A10DCC011EDAA01DC2811ED001A00779A8877200087390877068877268037878877470077A28077900C2811DD80120DA211DDCA10DD8A11CCE211CD8A10DECA11CC6811EDE411EDAE01ED2811CE8011D803890033C00067778873610877A480776A08774708779000877788736480777308779680373808736688770A0077400E8411EEAA11C00C21DDEA10DDC211CDC611EDAC01CE0A10DDA211CE8011D007A90877A280780708777688379488773708772208736D0877290877798873630077868837608077A4007801EE4A11ECA0120DCE11DDA801EE4211CE0011ED2C11DCEA10DDA211CE8011D007A90877A28078098077A08877158873680077978077A288771A0877790873610877A30077328077968837948077D2807000F00A21EDC611EC2C11CCAA10DCC011EDAA01DC2811ED001A00779A8877200367402012C002900D5100EE9200F6D200EF5600EE6500EF2D006EEF00E6D100EEC900EE1300F00000049180000010000001384400013B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69BCAC0920CD900A30120000020000000100000000000390D82050546D0000200B0400000A000000321E981019114C908C092647C6044362255008E55004E5538002055120C530025006051800000000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A80778980700000000792000001D010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A398066400560000004A63611BB44171506C1C193091C128916310CB01490A7128484421CCB24497E30000000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E706F736974696F6E6169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D65506F736974696F6E5F306169722E7665727465785F6F75747075747573657228544558434F4F524429666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E7665727465785F696E7075746169722E6C6F636174696F6E5F696E646578506F736974696F6E5F3155565F31436F6C6F725F316169722E6275666665726169722E726561646169722E616464726573735F73706163656169722E7374727563745F747970655F696E666F666C6F617434783450726F6A656374696F6E5F30436F6E7374616E74735F30636F6E7374616E74735F306169722E6172675F747970655F73697A656169722E6172675F747970655F616C69676E5F73697A65476C6F62616C506172616D735F30676C6F62616C506172616D735F3026760000000000003082C00423088C3082C00C23080C3182C01423088C3182C01C23080C3282C02423088C3282C02C23080A3082C030330C6A10ACC10C031B086D30C3C006831BCC30B001E106330C6C50B8C10C031B186F30C3C006071CCC30B00112073304C90C431BC8C11CCC402874A006733043B0CC10303304CD0C85F34091348331514F15593318D3F5401136C3500AA6700A332473906973A0064F156D33246A90696AA0064F157133246D90696DA0060F147533147360077320066330C31106AC3007763007645006334874E06973A0061F18B44118B48219D8C119C4C18306511ACC40A4822AAC822BCC30D4012ABCC29D01C0711CC7711CC7711CC7B9811BB8811BB8811BB8811B5874A0079665B9011DD0011DE0022EE0022B90836A80828C042628233636BB3697B637B23AB6321733B6B0B3B951103BB8033CC8033DD8033EE88354D8D8ECDA5CD2C8CADCE84609FC2097B0343917BB32B9B9B437B751823F482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D940014720A4B9373197B6B834B632BFB7A83A34B7B739B1B65080551188554C2D2E45CECCAE4E8CAF046095E01000000A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000500000001304412C1000000018000000C46600A88D00944019D01B01188B08822098835002221A8B0002E12038D600040285D100123300346600288E35C8288DA79F8CD278FAC9288DA7DF580369A3BDFC9136DACB1F69A3BDFC8D3588E6AA939E68AE3AE989E6AA93DE680000000000F1300000140000002247C890510A841C00000000980500006169722D616C6961732D73636F7065732856534D61696E296169722D616C6961732D73636F70652D61726728332900002B8459888515032DCC822C6C08680100FD18D1735DD7434128830C16E2A010887F3F46356DDB265110CA2083C6442804E28F4101FEFD181A06066000061605A1E01080FF208337511844E28F4104FE180CE28F8104FE1814E23FC8104CDC20435006DC6C439600B30D011A04B30DC1206C101003040000005B86E0A0852D4371D0C2964139680100000000007120000003000000320E1022840098060000000000000000650C00001F000000120394F0000000000300000006000000090000004C000000010000005800000000000000580000000100000070000000000000000F0000001C00000000000000060000000600000000000000700000000000000000000000010000000000000000000000060000000000000006000000FFFFFFFF00240000000000005D0C00000E0000001203946B0000000056534D61696E33323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000000000000000000010000008D030000524255467D03000000000000000000000018000000414952521000180004001000000000000000140010000000000000000400000000000000100000000400000001000000000000000A000000E00200009002000034020000F0010000AC0100007C0100004C010000D40000006800000004000000F4FDFFFF01000800040000009EFFFFFF04000000010000001400000010001000000000000400000008000C0010000000400000001C000000040000000C00000050726F6A656374696F6E5F300000000008000000666C6F617434783400000000B0FDFFFF010008000C0000000000060008000400060000000400000001000000140000001000140004000000080000000C00100010000000090000004000000018000000040000000B000000636F6E7374616E74735F30000B000000436F6E7374616E74735F3000BCFEFFFF00000400200000001C0024000000000008000C000600070010000000140018001C0020001C0000000000010200000000010000000800000040000000100000001C000000040000000E000000676C6F62616C506172616D735F3000000E000000476C6F62616C506172616D735F30000030FFFFFF0420040004000000A6FFFFFF0200000001000000500100000400000007000000436F6C6F725F31005CFFFFFF0420040004000000D2FFFFFF0100000001000000CC000000040000000400000055565F3100000000E4FEFFFF042004001400000000000E0014000000040008000C0010000E000000000000000100000090000000040000000A000000506F736974696F6E5F310000C8FFFFFF0400020004000000C0FFFFFF18000000BC0000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290008000C000400080008000000040002001400000010001000000004000000000008000C00100000002400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000BCFFFFFF020002001400000000000E000C00000000000000040008000E00000018000000040000000A000000506F736974696F6E5F30000006000000666C6F617434000008000E000400080008000000030000001000000000000A001000040008000C000A000000300000001C000000040000000400000004000000050000000600000007000000030000000100000002000000030000000600000056534D61696E0000454E4454", - - // Fragment Shader - "4D544C4201800200070000810E000000D81100000000000058000000000000008A00000000000000160100000000000008000000000000001E0100000000000008000000000000002601000000000000000E000000000000010000008A0000004E414D45070050534D61696E00545950450100014841534820008335EA1F82646DCCC623827DA6EBF0258B1E224AA4218D60D70CC077494E05494F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800000E00000000000052464C5408000400000000000000454E4454524C53541000260F000000000000B202000000000000555549441000C710A16106E43B598CE29DE59990D629454E445408000000454E445408000000454E4454DEC0170B0000000014000000EC0D0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000350300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C8000000892000001F0000003222480920624600212B249814212524981419270C85A4906052645C20246582609A01184620801B84610401404A9A224A98FC7F22AE898A88DF1EFE698C00184420028CA429A284C9FF2580791622FAA731026010C1108C214608E5109A23408E10D41C4130470006C30842639455CE608E01D0E80D048C0000000051180000670000001BF623F8FFFFFFFF015803C014003F002460022AA00F8870800778908777C0873630877A708771680373808736688770A0077400E8411EEAA11C00C21CE4211CDAA11CDA001EDE211DDC811ECA411E807060077600887648077768037628877308077668037B288771A08777908736B8877420077A4007200EE4000F80C11DDEA10DC4A11ED2811DE8211DDC611E00C21DDEA10DD2C11DCC611EDAC01CE0A10DDA211CE8011D007A90877A28078070877768037708077798873630077868837608077A4007801EE4A11ECA0120DCE11DDA601ED2E11CDCA11CC8A10DF4A11CE4E11DE6A10DCC011EDAA01DC2811ED001A00779A88772000877788736A0077908077880877470877368837608077A4007801EE4A11ECA0120E6811EC2611CD6A10DE0411EDE811ECA611CE8E11DE4A10DC4A11ECCC11CCA411EDA601ED2411FCA01C00380A80777988770308772680373808736688770A0077400E8411EEAA11C800D843000A4B0011AFEFFFFFF7F00DA005803C014003F0024A002FA60834104C002541B8C420016A0DAC018FFFFFFFF3F006D00AC01200115D00700491800000300000013844098300C44316130880213B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69A4AC0920CD900A301000000200000001000000000003185219DB0304800000002000000000006000121B048ADE0C000064810009000000321E981019114C908C092647C604436A255008E55004E5538002055120C5300250060000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A807789807000000007920000008010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A3680660604F0000004A63611BB44171506C1C194491C12092A33C06B11C8CA4388BA460C97224000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E72656E6465725F7461726765746169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D656F75747075745F306169722E667261676D656E745F696E7075747573657228544558434F4F5244296169722E63656E7465726169722E7065727370656374697665666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E706F736974696F6E6169722E6E6F5F7065727370656374697665506F736974696F6E5F306169722E6172675F756E757365646169722E746578747572656169722E6C6F636174696F6E5F696E6465786169722E73616D706C657465787475726532643C666C6F61742C2073616D706C653E746578747572655F316169722E73616D706C657273616D706C657273616D706C65725F31000026630000000000003082D0082308CD3082D01023084D3182D0182308CD3182D02023084D3282D0282308CD3282D03023080930C37006011ACC30A481A006330C6930ACC10C431A106B30C39006C51ACC30A481C106330C6970B4C10C431A206E304390CC30A8C11BC0C10C8412076700073304CB0C013343D0CC70387000070F14493304A130430207135559CF1561332467306595F540913643A2065BC53D50D479332871F081011C9C41183C62108DC10C091A900118C0C1193C651099C10CC52890422998C229CC30C88128A0C28D01C0711CC7711CC7711CE7066EE0066EE0066EE0066E60D1811E5896650A1C2BB0022BD8033CB0828C042628233636BB3697B637B23AB6321733B6B0B3B951103998033AA8033BB8033CC88354D8D8ECDA5CD2C8CADCE84609F42097B0343917BB32B9B9B437B751823D482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D94800F720A4B9373197B6B834B632BFB7A83A34B7B739B1B65E8033FF88364C2D2E45CCCE4C2CEDACADCE84609500100A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000310000001304412C1000000004000000C46A600480DC08008111001233000000F13000001C0000002247C890510E042B00000000188601006169722D616C6961732D73636F7065732850534D61696E296169722D616C6961732D73636F70652D73616D706C6572736169722D616C6961732D73636F70652D74657874757265732B8456508515832BB4C22AAC185EA115586183F00AAE00002306CD108260F05887A114032108CC68420060B088FF6C0311001B04C4000000020000005B06E07805000000000000007120000003000000320E1022840084060000000000000000650C00002500000012039428010000000300000021000000090000004C000000010000005800000000000000580000000200000088000000000000002A0000001C00000000000000060000000600000000000000880000000000000000000000020000000000000000000000060000000000000006000000FFFFFFFF00240000060000001B000000060000001B000000FFFFFFFF08240000000000005D0C000015000000120394A60000000050534D61696E6169722E73616D706C655F746578747572655F32642E763466333233323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000001000000AE020000524255469E02000000000000000000000000140000004149525200000A0018000400100014000A000000000000000400000000000000100000000400000001000000000000000700000008020000AC0100004801000008010000C000000054000000040000001CFEFFFF010004001400000000000E0014000000040008000C0010000E000000000000000100000018000000040000000900000073616D706C65725F310000000700000073616D706C65720068FEFFFF0200040018000000000012001800000008000C00070000001000140012000000000000040000000001000000180000000400000009000000746578747572655F31000000180000007465787475726532643C666C6F61742C2073616D706C653E00000000C4FFFFFF02300400140000001000100000000000000008000C000700100000000000000100010000040000000A000000506F736974696F6E5F30000008000C0004000800080000000030040004000000C2FFFFFF0000010118000000C80000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290050FFFFFF0030040018000000000012001400000008000000060007000C00100012000000000001012400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000B0FFFFFF0010020018000000000012001400000004000800000000000C0010001200000000000000000000001800000004000000080000006F75747075745F300000000006000000666C6F617434000008000E000400080008000000010000001000000000000A001000040008000C000A0000002C000000200000000400000005000000020000000300000004000000050000000600000001000000010000000600000050534D61696E0000454E4454" - ]; - private readonly Sampler sampler; private readonly GraphicsPipeline graphicsPipeline; private readonly Dictionary textureBindings = []; @@ -112,42 +76,13 @@ float4 FSMain(VSOutput input) : SV_TARGET public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormats, ImGuiColorSpace colorSpace) { - byte[] vertexBytecode = []; - byte[] fragmentBytecode = []; - switch (context.GraphicsApi) - { - case GraphicsApi.DirectX12: - vertexBytecode = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Dxil[0] : Dxil[1]); - fragmentBytecode = Convert.FromHexString(Dxil[2]); - break; - - case GraphicsApi.Vulkan: - vertexBytecode = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Spirv[0] : Spirv[1]); - fragmentBytecode = Convert.FromHexString(Spirv[2]); - break; - - case GraphicsApi.Metal: - vertexBytecode = Convert.FromHexString(colorSpace is ImGuiColorSpace.Legacy ? Metallib[0] : Metallib[1]); - fragmentBytecode = Convert.FromHexString(Metallib[2]); - break; - } - - using Shader vertex = context.CreateShader(new() - { - Bytecode = vertexBytecode, - EntryPoint = "VSMain", - Stage = ShaderStages.Vertex - }); - - using Shader fragment = context.CreateShader(new() - { - Bytecode = fragmentBytecode, - EntryPoint = "PSMain", - Stage = ShaderStages.Fragment - }); + string source = Source.Replace("#if 0", $"#if {(colorSpace is ImGuiColorSpace.Legacy ? 0 : 1)}"); sampler = context.CreateSampler(SamplerDesc.PointClamp()); + using Shader vertex = context.CreateShaderFromSource(source, "VSMain", ShaderStages.Vertex); + using Shader fragment = context.CreateShaderFromSource(source, "FSMain", ShaderStages.Fragment); + InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.Position }); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.TexCoord }); From 4cfe659e3a8a42f40a103e7d2852f8dff332203f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 11:15:27 +0800 Subject: [PATCH 126/466] Rename PSInput/PSMain to FSInput/FSMain in shaders Renamed PSInput struct to FSInput and updated all references in Rasterization.slang. Changed shader entry points: VSMain now returns FSInput, and PSMain is now FSMain with updated input type. Updated RasterizationRenderer.cs to use the new fragment shader entry point "FSMain". --- .../CornellBox/Assets/Shaders/Rasterization.slang | 8 ++++---- .../CornellBox/Renderers/RasterizationRenderer.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang index 3f942a82..71e98889 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang @@ -101,7 +101,7 @@ struct VSInput } }; -struct PSInput +struct FSInput { float4 Position : SV_POSITION; @@ -151,11 +151,11 @@ float3 FresnelSchlick(float cosTheta, float3 F0) return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0); } -PSInput VSMain(VSInput input) +FSInput VSMain(VSInput input) { float4 worldPos = mul(float4(input.Position, 1.0), rasterization.Model); - PSInput output; + FSInput output; output.Position = mul(mul(worldPos, rasterization.View), rasterization.Projection); output.WorldPos = worldPos.xyz; output.Normal = normalize(mul(float4(input.Normal, 0.0), rasterization.Model).xyz); @@ -164,7 +164,7 @@ PSInput VSMain(VSInput input) return output; } -float4 PSMain(PSInput input) : SV_TARGET +float4 FSMain(FSInput input) : SV_TARGET { Material mat = (*rasterization.Materials)[input.MaterialId]; diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 20023538..1e39740b 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -45,7 +45,7 @@ public RasterizationRenderer() inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Normal }); using Shader vertexShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "VSMain", ShaderStages.Vertex); - using Shader fragmentShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "PSMain", ShaderStages.Fragment); + using Shader fragmentShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "FSMain", ShaderStages.Fragment); pipeline = App.Context.CreateGraphicsPipeline(new() { From 93a82d844dae4127f553f713ba21a0fe370373e7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 11:23:18 +0800 Subject: [PATCH 127/466] Set buffer access to CpuWriteOnly in renderer classes Updated buffer creation in PathTracingRenderer, RasterizationRenderer, and ImGuiRenderer to use BufferAccess.CpuWriteOnly for constant, vertex, and index buffers. This change uses the with expression to set the Access property, optimizing these buffers for CPU write operations. --- .../Experiments/CornellBox/Renderers/PathTracingRenderer.cs | 2 +- .../CornellBox/Renderers/RasterizationRenderer.cs | 2 +- .../Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index b45e6f0e..21952288 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -54,7 +54,7 @@ public PathTracingRenderer() indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); } - constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(PathTracingConstants))); + constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(PathTracingConstants)) with { Access = BufferAccess.CpuWriteOnly }); using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 1e39740b..574ac15e 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -38,7 +38,7 @@ public RasterizationRenderer() indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); } - constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(RasterizationConstants))); + constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(RasterizationConstants)) with { Access = BufferAccess.CpuWriteOnly }); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 9553f59b..85104f62 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -287,21 +287,21 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, if (vertexBuffer is null || vertexBuffer.Desc.SizeInBytes < totalVertexSizeInBytes) { vertexBuffer?.Dispose(); - vertexBuffer = Context.CreateBuffer(BufferDesc.Vertex(totalVertexSizeInBytes, (uint)sizeof(ImDrawVert))); + vertexBuffer = Context.CreateBuffer(BufferDesc.Vertex(totalVertexSizeInBytes, (uint)sizeof(ImDrawVert)) with { Access = BufferAccess.CpuWriteOnly }); } uint totalIndexSizeInBytes = (uint)(sizeof(ushort) * (int)(drawData.TotalIdxCount * 1.2)); if (indexBuffer is null || indexBuffer.Desc.SizeInBytes < totalIndexSizeInBytes) { indexBuffer?.Dispose(); - indexBuffer = Context.CreateBuffer(BufferDesc.Index(totalIndexSizeInBytes, sizeof(ushort))); + indexBuffer = Context.CreateBuffer(BufferDesc.Index(totalIndexSizeInBytes, sizeof(ushort)) with { Access = BufferAccess.CpuWriteOnly }); } uint constantBufferSizeInBytes = (uint)(sizeof(Constants) * (int)(resourceHandleBindings.Count * 1.2)); if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < constantBufferSizeInBytes) { constantBuffer?.Dispose(); - constantBuffer = Context.CreateBuffer(BufferDesc.Constant(constantBufferSizeInBytes)); + constantBuffer = Context.CreateBuffer(BufferDesc.Constant(constantBufferSizeInBytes) with { Access = BufferAccess.CpuWriteOnly }); } for (int i = 0, vertexOffset = 0, indexOffset = 0; i < drawData.CmdListsCount; i++) From ceca76c95b27a7089fbf6f7914cd8ec47f410181 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 11:29:30 +0800 Subject: [PATCH 128/466] Refactor buffer creation to use explicit descriptors Replaces BufferDesc helper methods with explicit object initializers for buffer creation in PathTracingRenderer, RasterizationRenderer, and ImGuiRenderer. Sets buffer properties directly for clarity and maintainability. Also renames a variable in ImGuiRenderer for improved readability. --- .../Renderers/PathTracingRenderer.cs | 8 +++++- .../Renderers/RasterizationRenderer.cs | 8 +++++- .../ImGuiRenderer.cs | 28 +++++++++++++++---- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 21952288..0860aea6 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -54,7 +54,13 @@ public PathTracingRenderer() indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); } - constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(PathTracingConstants)) with { Access = BufferAccess.CpuWriteOnly }); + constantBuffer = App.Context.CreateBuffer(new() + { + SizeInBytes = (uint)sizeof(PathTracingConstants), + StrideInBytes = (uint)sizeof(PathTracingConstants), + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Constant + }); using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 574ac15e..50f4b40c 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -38,7 +38,13 @@ public RasterizationRenderer() indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); } - constantBuffer = App.Context.CreateBuffer(BufferDesc.Constant((uint)sizeof(RasterizationConstants)) with { Access = BufferAccess.CpuWriteOnly }); + constantBuffer = App.Context.CreateBuffer(new() + { + SizeInBytes = (uint)sizeof(RasterizationConstants), + StrideInBytes = (uint)sizeof(RasterizationConstants), + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Constant + }); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 85104f62..1af85231 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -287,21 +287,39 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, if (vertexBuffer is null || vertexBuffer.Desc.SizeInBytes < totalVertexSizeInBytes) { vertexBuffer?.Dispose(); - vertexBuffer = Context.CreateBuffer(BufferDesc.Vertex(totalVertexSizeInBytes, (uint)sizeof(ImDrawVert)) with { Access = BufferAccess.CpuWriteOnly }); + vertexBuffer = Context.CreateBuffer(new() + { + SizeInBytes = totalVertexSizeInBytes, + StrideInBytes = (uint)sizeof(ImDrawVert), + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Vertex + }); } uint totalIndexSizeInBytes = (uint)(sizeof(ushort) * (int)(drawData.TotalIdxCount * 1.2)); if (indexBuffer is null || indexBuffer.Desc.SizeInBytes < totalIndexSizeInBytes) { indexBuffer?.Dispose(); - indexBuffer = Context.CreateBuffer(BufferDesc.Index(totalIndexSizeInBytes, sizeof(ushort)) with { Access = BufferAccess.CpuWriteOnly }); + indexBuffer = Context.CreateBuffer(new() + { + SizeInBytes = totalIndexSizeInBytes, + StrideInBytes = sizeof(ushort), + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Index + }); } - uint constantBufferSizeInBytes = (uint)(sizeof(Constants) * (int)(resourceHandleBindings.Count * 1.2)); - if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < constantBufferSizeInBytes) + uint totalConstantBufferSizeInBytes = (uint)(sizeof(Constants) * (int)(resourceHandleBindings.Count * 1.2)); + if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < totalConstantBufferSizeInBytes) { constantBuffer?.Dispose(); - constantBuffer = Context.CreateBuffer(BufferDesc.Constant(constantBufferSizeInBytes) with { Access = BufferAccess.CpuWriteOnly }); + constantBuffer = Context.CreateBuffer(new() + { + SizeInBytes = totalConstantBufferSizeInBytes, + StrideInBytes = (uint)sizeof(Constants), + Access = BufferAccess.CpuWriteOnly, + Usages = BufferUsages.Constant + }); } for (int i = 0, vertexOffset = 0, indexOffset = 0; i < drawData.CmdListsCount; i++) From 410a68fad8f90db03db2f86287d69317004d798a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 14:28:23 +0800 Subject: [PATCH 129/466] Rename totalConstantBufferSizeInBytes for clarity Renamed totalConstantBufferSizeInBytes to totalConstantSizeInBytes in ImGuiRenderer.cs for improved clarity and consistency. No functional changes were made. --- .../Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 1af85231..3bc9eba1 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -309,13 +309,13 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, }); } - uint totalConstantBufferSizeInBytes = (uint)(sizeof(Constants) * (int)(resourceHandleBindings.Count * 1.2)); - if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < totalConstantBufferSizeInBytes) + uint totalConstantSizeInBytes = (uint)(sizeof(Constants) * (int)(resourceHandleBindings.Count * 1.2)); + if (constantBuffer is null || constantBuffer.Desc.SizeInBytes < totalConstantSizeInBytes) { constantBuffer?.Dispose(); constantBuffer = Context.CreateBuffer(new() { - SizeInBytes = totalConstantBufferSizeInBytes, + SizeInBytes = totalConstantSizeInBytes, StrideInBytes = (uint)sizeof(Constants), Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.Constant From 3ed04f83b81cf9aae0b6d5dcea988027064aab32 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 14:39:45 +0800 Subject: [PATCH 130/466] Add DirectX 12 pipeline implementations and helpers Implemented DX12 pipeline classes for graphics, compute, and mesh shading, managing pipeline state and resource cleanup. Updated DXGraphicsContext to use these implementations. Added topology mapping in DXFormats and DX12 casting helpers in Extensions. --- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 37 ++++++++++++++++++ sources/Zenith.NET.DirectX12/DXFormats.cs | 16 ++++++++ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 6 +-- .../DXGraphicsPipeline.cs | 38 +++++++++++++++++++ .../DXMeshShadingPipeline.cs | 34 +++++++++++++++++ sources/Zenith.NET.DirectX12/Extensions.cs | 28 ++++++++++++++ 6 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXComputePipeline.cs create mode 100644 sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs create mode 100644 sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs new file mode 100644 index 00000000..10c23075 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -0,0 +1,37 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXComputePipeline : ComputePipeline +{ + public ComPtr PipelineState; + + public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) + { + using ZenithMarshal.Scope scope = new(); + + ComputePipelineStateDesc computePipelineStateDesc = new() + { + PRootSignature = context.RootSignature, + CS = desc.ComputeShader.DirectX12().GetShaderBytecode(scope) + }; + + context.Device10.CreateComputePipelineState(&computePipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + PipelineState.SetName(name).Success(); + } + + protected override void Destroy() + { + PipelineState.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 07a27d08..cd065f88 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -250,4 +250,20 @@ public static (float R, float G, float B, float A) DirectX12(BorderColor borderC _ => default }; } + + public static PrimitiveTopologyType DirectX12(PrimitiveTopology primitiveTopology) + { + return primitiveTopology switch + { + PrimitiveTopology.PointList => PrimitiveTopologyType.Point, + + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, + + PrimitiveTopology.TriangleList or + PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, + + _ => default + }; + } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 9f97c898..7ee503f7 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -196,17 +196,17 @@ protected override Shader CreateShaderImpl(ShaderDesc desc) protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) { - throw new NotImplementedException(); + return new DXGraphicsPipeline(this, desc); } protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) { - throw new NotImplementedException(); + return new DXComputePipeline(this, desc); } protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) { - throw new NotImplementedException(); + return new DXMeshShadingPipeline(this, desc); } protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs new file mode 100644 index 00000000..cc432b6c --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -0,0 +1,38 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal class DXGraphicsPipeline : GraphicsPipeline +{ + public ComPtr PipelineState; + + public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) + { + using ZenithMarshal.Scope scope = new(); + + GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() + { + PRootSignature = context.RootSignature, + VS = desc.VertexShader.DirectX12().GetShaderBytecode(scope), + PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), + SampleMask = uint.MaxValue, + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology) + }; + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + PipelineState.SetName(name).Success(); + } + + protected override void Destroy() + { + PipelineState.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs new file mode 100644 index 00000000..8f41d72b --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -0,0 +1,34 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal class DXMeshShadingPipeline : MeshShadingPipeline +{ + public ComPtr PipelineState; + + public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) + { + using ZenithMarshal.Scope scope = new(); + + GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() + { + SampleMask = uint.MaxValue + }; + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + PipelineState.SetName(name).Success(); + } + + protected override void Destroy() + { + PipelineState.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 42c54bdb..3d7f1f14 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -83,4 +83,32 @@ internal DXShader DirectX12() return (DXShader)shader; } } + + extension(GraphicsPipeline graphicsPipeline) + { + internal DXGraphicsPipeline DirectX12() + { + return (DXGraphicsPipeline)graphicsPipeline; + } + } + + extension(ComputePipeline computePipeline) + { + internal DXComputePipeline DirectX12() + { + return (DXComputePipeline)computePipeline; + } + } + + extension(MeshShadingPipeline meshShadingPipeline) + { + internal DXMeshShadingPipeline DirectX12() + { + return (DXMeshShadingPipeline)meshShadingPipeline; + } + } + + extension(QueryHeap queryHeap) + { + } } From 4d2bf22c13c7bc7587f198251011e672c21f1e10 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 15:40:16 +0800 Subject: [PATCH 131/466] Map engine enums to DX12 types; cleanup RasterizerState Added DXFormats methods to map engine enums/types to DirectX 12 (Silk.NET) equivalents for input layouts, rasterizer, blend, and depth-stencil states. Updated DXGraphicsPipeline to use these mappings for pipeline state creation. Set root signature in DXMeshShadingPipeline. Added global usings for DX12 enums. Removed IsScissorEnabled from RasterizerState for simplification. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 156 ++++++++++++++++++ .../DXGraphicsPipeline.cs | 121 +++++++++++++- .../DXMeshShadingPipeline.cs | 1 + sources/Zenith.NET.DirectX12/Usings.cs | 6 +- sources/Zenith.NET/Structs/RasterizerState.cs | 20 +-- 5 files changed, 288 insertions(+), 16 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index cd065f88..b4ad1577 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -266,4 +266,160 @@ PrimitiveTopology.TriangleList or _ => default }; } + + public static Format DirectX12(ElementFormat elementFormat) + { + return elementFormat switch + { + ElementFormat.UByte1 => Format.FormatR8Uint, + ElementFormat.UByte2 => Format.FormatR8G8Uint, + ElementFormat.UByte4 => Format.FormatR8G8B8A8Uint, + + ElementFormat.Byte1 => Format.FormatR8Sint, + ElementFormat.Byte2 => Format.FormatR8G8Sint, + ElementFormat.Byte4 => Format.FormatR8G8B8A8Sint, + + ElementFormat.UByte1UNorm => Format.FormatR8Unorm, + ElementFormat.UByte2UNorm => Format.FormatR8G8Unorm, + ElementFormat.UByte4UNorm => Format.FormatR8G8B8A8Unorm, + + ElementFormat.Byte1SNorm => Format.FormatR8SNorm, + ElementFormat.Byte2SNorm => Format.FormatR8G8SNorm, + ElementFormat.Byte4SNorm => Format.FormatR8G8B8A8SNorm, + + ElementFormat.UShort1 => Format.FormatR16Uint, + ElementFormat.UShort2 => Format.FormatR16G16Uint, + ElementFormat.UShort4 => Format.FormatR16G16B16A16Uint, + + ElementFormat.Short1 => Format.FormatR16Sint, + ElementFormat.Short2 => Format.FormatR16G16Sint, + ElementFormat.Short4 => Format.FormatR16G16B16A16Sint, + + ElementFormat.UShort1UNorm => Format.FormatR16Unorm, + ElementFormat.UShort2UNorm => Format.FormatR16G16Unorm, + ElementFormat.UShort4UNorm => Format.FormatR16G16B16A16Unorm, + + ElementFormat.Short1SNorm => Format.FormatR16SNorm, + ElementFormat.Short2SNorm => Format.FormatR16G16SNorm, + ElementFormat.Short4SNorm => Format.FormatR16G16B16A16SNorm, + + ElementFormat.Half1 => Format.FormatR16Float, + ElementFormat.Half2 => Format.FormatR16G16Float, + ElementFormat.Half4 => Format.FormatR16G16B16A16Float, + + ElementFormat.Float1 => Format.FormatR32Float, + ElementFormat.Float2 => Format.FormatR32G32Float, + ElementFormat.Float3 => Format.FormatR32G32B32Float, + ElementFormat.Float4 => Format.FormatR32G32B32A32Float, + + ElementFormat.UInt1 => Format.FormatR32Uint, + ElementFormat.UInt2 => Format.FormatR32G32Uint, + ElementFormat.UInt3 => Format.FormatR32G32B32Uint, + ElementFormat.UInt4 => Format.FormatR32G32B32A32Uint, + + ElementFormat.Int1 => Format.FormatR32Sint, + ElementFormat.Int2 => Format.FormatR32G32Sint, + ElementFormat.Int3 => Format.FormatR32G32B32Sint, + ElementFormat.Int4 => Format.FormatR32G32B32A32Sint, + + _ => default + }; + } + + public static DxFillMode DirectX12(FillMode fillMode) + { + return fillMode switch + { + FillMode.Solid => DxFillMode.Solid, + FillMode.Wireframe => DxFillMode.Wireframe, + _ => default + }; + } + + public static DxCullMode DirectX12(CullMode cullMode) + { + return cullMode switch + { + CullMode.None => DxCullMode.None, + CullMode.Front => DxCullMode.Front, + CullMode.Back => DxCullMode.Back, + _ => default + }; + } + + public static Blend DirectX12(BlendFactor blendFactor) + { + return blendFactor switch + { + BlendFactor.Zero => Blend.Zero, + BlendFactor.One => Blend.One, + BlendFactor.SourceColor => Blend.SrcColor, + BlendFactor.OneMinusSourceColor => Blend.InvSrcColor, + BlendFactor.DestinationColor => Blend.DestColor, + BlendFactor.OneMinusDestinationColor => Blend.InvDestColor, + BlendFactor.SourceAlpha => Blend.SrcAlpha, + BlendFactor.OneMinusSourceAlpha => Blend.InvSrcAlpha, + BlendFactor.DestinationAlpha => Blend.DestAlpha, + BlendFactor.OneMinusDestinationAlpha => Blend.InvDestAlpha, + BlendFactor.Constant => Blend.BlendFactor, + BlendFactor.OneMinusConstant => Blend.InvBlendFactor, + _ => default + }; + } + + public static DxBlendOp DirectX12(BlendOp blendOp) + { + return blendOp switch + { + BlendOp.Add => DxBlendOp.Add, + BlendOp.Subtract => DxBlendOp.Subtract, + BlendOp.ReverseSubtract => DxBlendOp.RevSubtract, + BlendOp.Min => DxBlendOp.Min, + BlendOp.Max => DxBlendOp.Max, + _ => default + }; + } + + public static DxStencilOp DirectX12(StencilOp stencilOp) + { + return stencilOp switch + { + StencilOp.Keep => DxStencilOp.Keep, + StencilOp.Zero => DxStencilOp.Zero, + StencilOp.Replace => DxStencilOp.Replace, + StencilOp.IncrementAndClamp => DxStencilOp.IncrSat, + StencilOp.DecrementAndClamp => DxStencilOp.DecrSat, + StencilOp.Invert => DxStencilOp.Invert, + StencilOp.IncrementAndWrap => DxStencilOp.Incr, + StencilOp.DecrementAndWrap => DxStencilOp.Decr, + _ => default + }; + } + + public static ColorWriteEnable DirectX12(ColorWrites colorWrites) + { + ColorWriteEnable result = ColorWriteEnable.None; + + if (colorWrites.HasFlag(ColorWrites.Red)) + { + result |= ColorWriteEnable.Red; + } + + if (colorWrites.HasFlag(ColorWrites.Green)) + { + result |= ColorWriteEnable.Green; + } + + if (colorWrites.HasFlag(ColorWrites.Blue)) + { + result |= ColorWriteEnable.Blue; + } + + if (colorWrites.HasFlag(ColorWrites.Alpha)) + { + result |= ColorWriteEnable.Alpha; + } + + return result; + } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index cc432b6c..1a549381 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -1,9 +1,10 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; -internal class DXGraphicsPipeline : GraphicsPipeline +internal unsafe class DXGraphicsPipeline : GraphicsPipeline { public ComPtr PipelineState; @@ -19,6 +20,124 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) SampleMask = uint.MaxValue, PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology) }; + + // InputLayouts + { + List inputElementDescs = []; + for (int i = 0; i < desc.InputLayouts.Length; i++) + { + InputLayout inputLayout = desc.InputLayouts[i]; + + foreach (InputElement element in inputLayout.InputElements) + { + inputElementDescs.Add(new() + { + SemanticName = (byte*)ZenithMarshal.StringToPointer(scope, element.Semantic.ToString().ToUpper(), StringEncoding.UTF8), + SemanticIndex = element.SemanticIndex, + Format = DXFormats.DirectX12(element.Format), + InputSlot = (uint)i, + AlignedByteOffset = element.OffsetInBytes + }); + } + } + + graphicsPipelineStateDesc.InputLayout = new() + { + PInputElementDescs = (InputElementDesc*)ZenithMarshal.AllocateAndFill(scope, [.. inputElementDescs]), + NumElements = (uint)inputElementDescs.Count + }; + } + + // AttachmentFormats + { + graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; + + for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) + { + graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); + } + + graphicsPipelineStateDesc.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; + + graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); + } + + // RenderState + { + ColorAttachmentBlendState[] colorAttachmentBlendStates = + [ + desc.RenderState.BlendState.ColorAttachment0, + desc.RenderState.BlendState.ColorAttachment1, + desc.RenderState.BlendState.ColorAttachment2, + desc.RenderState.BlendState.ColorAttachment3, + desc.RenderState.BlendState.ColorAttachment4, + desc.RenderState.BlendState.ColorAttachment5, + desc.RenderState.BlendState.ColorAttachment6, + desc.RenderState.BlendState.ColorAttachment7 + ]; + + graphicsPipelineStateDesc.RasterizerState = new() + { + FillMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.FillMode), + CullMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.CullMode), + FrontCounterClockwise = desc.RenderState.RasterizerState.FrontFace is FrontFace.CounterClockwise, + DepthBias = desc.RenderState.RasterizerState.DepthBias, + DepthBiasClamp = desc.RenderState.RasterizerState.DepthBiasClamp, + SlopeScaledDepthBias = desc.RenderState.RasterizerState.DepthBiasSlopeScale, + DepthClipEnable = desc.RenderState.RasterizerState.IsDepthClipEnabled, + MultisampleEnable = desc.AttachmentFormats.SampleCount is not SampleCount.Count1 + }; + + graphicsPipelineStateDesc.DepthStencilState = new() + { + DepthEnable = desc.RenderState.DepthStencilState.IsDepthEnabled, + DepthWriteMask = desc.RenderState.DepthStencilState.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, + DepthFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.DepthCompareOp), + StencilEnable = desc.RenderState.DepthStencilState.IsStencilEnabled, + StencilReadMask = desc.RenderState.DepthStencilState.StencilReadMask, + StencilWriteMask = desc.RenderState.DepthStencilState.StencilWriteMask, + FrontFace = new() + { + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.CompareOp) + }, + BackFace = new() + { + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.CompareOp) + } + }; + + graphicsPipelineStateDesc.BlendState = new() + { + AlphaToCoverageEnable = desc.RenderState.BlendState.IsAlphaToCoverageEnabled, + IndependentBlendEnable = desc.RenderState.BlendState.IsIndependentBlendEnabled + }; + + for (int i = 0; i < colorAttachmentBlendStates.Length; i++) + { + ColorAttachmentBlendState blend = colorAttachmentBlendStates[i]; + + graphicsPipelineStateDesc.BlendState.RenderTarget[i] = new() + { + BlendEnable = blend.IsBlendingEnabled, + SrcBlend = DXFormats.DirectX12(blend.SrcRgbFactor), + DestBlend = DXFormats.DirectX12(blend.DstRgbFactor), + BlendOp = DXFormats.DirectX12(blend.RgbOp), + SrcBlendAlpha = DXFormats.DirectX12(blend.SrcAlphaFactor), + DestBlendAlpha = DXFormats.DirectX12(blend.DstAlphaFactor), + BlendOpAlpha = DXFormats.DirectX12(blend.AlphaOp), + LogicOp = LogicOp.Noop, + RenderTargetWriteMask = (byte)DXFormats.DirectX12(blend.ColorWrites) + }; + } + } + + context.Device10.CreateGraphicsPipelineState(&graphicsPipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 8f41d72b..8f574254 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -13,6 +13,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() { + PRootSignature = context.RootSignature, SampleMask = uint.MaxValue }; } diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 4ef5f2d7..d6827639 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,5 +1,9 @@ -global using DxHeapDesc = Silk.NET.Direct3D12.HeapDesc; +global using DxBlendOp = Silk.NET.Direct3D12.BlendOp; +global using DxCullMode = Silk.NET.Direct3D12.CullMode; +global using DxFillMode = Silk.NET.Direct3D12.FillMode; +global using DxHeapDesc = Silk.NET.Direct3D12.HeapDesc; global using DxHeapType = Silk.NET.Direct3D12.HeapType; global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxRange = Silk.NET.Direct3D12.Range; global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; +global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index e6caab98..8087380e 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -16,8 +16,6 @@ public struct RasterizerState public bool IsDepthClipEnabled; - public bool IsScissorEnabled; - public static RasterizerState CullFront() { return new() @@ -28,8 +26,7 @@ public static RasterizerState CullFront() DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false + IsDepthClipEnabled = true }; } @@ -43,8 +40,7 @@ public static RasterizerState CullBack() DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false + IsDepthClipEnabled = true }; } @@ -58,8 +54,7 @@ public static RasterizerState CullNone() DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false + IsDepthClipEnabled = true }; } @@ -73,8 +68,7 @@ public static RasterizerState Wireframe() DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false + IsDepthClipEnabled = true }; } @@ -88,8 +82,7 @@ public static RasterizerState WireframeCullFront() DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false + IsDepthClipEnabled = true }; } @@ -103,8 +96,7 @@ public static RasterizerState WireframeCullBack() DepthBias = 0, DepthBiasClamp = 0.0f, DepthBiasSlopeScale = 0.0f, - IsDepthClipEnabled = true, - IsScissorEnabled = false + IsDepthClipEnabled = true }; } } From c9d02c8ff18725d2812e877e2f6aae4dd0f93a9f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 15:44:27 +0800 Subject: [PATCH 132/466] Refactor DXMeshShadingPipeline for full D3D12 state setup Refactored DXMeshShadingPipeline to use explicit DirectX 12 pipeline state configuration, including attachment formats, blend, rasterizer, and depth-stencil states. Added support for task, mesh, and fragment shaders, and set up the pipeline state stream for more flexible and complete mesh shading pipeline initialization. --- .../DXMeshShadingPipeline.cs | 113 +++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 8f574254..3f5466a5 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -1,9 +1,10 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; -internal class DXMeshShadingPipeline : MeshShadingPipeline +internal unsafe class DXMeshShadingPipeline : MeshShadingPipeline { public ComPtr PipelineState; @@ -14,8 +15,116 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() { PRootSignature = context.RootSignature, - SampleMask = uint.MaxValue + PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), + SampleMask = uint.MaxValue, + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology) }; + + // AttachmentFormats + { + graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; + + for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) + { + graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); + } + + graphicsPipelineStateDesc.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; + + graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); + } + + // RenderState + { + ColorAttachmentBlendState[] colorAttachmentBlendStates = + [ + desc.RenderState.BlendState.ColorAttachment0, + desc.RenderState.BlendState.ColorAttachment1, + desc.RenderState.BlendState.ColorAttachment2, + desc.RenderState.BlendState.ColorAttachment3, + desc.RenderState.BlendState.ColorAttachment4, + desc.RenderState.BlendState.ColorAttachment5, + desc.RenderState.BlendState.ColorAttachment6, + desc.RenderState.BlendState.ColorAttachment7 + ]; + + graphicsPipelineStateDesc.RasterizerState = new() + { + FillMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.FillMode), + CullMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.CullMode), + FrontCounterClockwise = desc.RenderState.RasterizerState.FrontFace is FrontFace.CounterClockwise, + DepthBias = desc.RenderState.RasterizerState.DepthBias, + DepthBiasClamp = desc.RenderState.RasterizerState.DepthBiasClamp, + SlopeScaledDepthBias = desc.RenderState.RasterizerState.DepthBiasSlopeScale, + DepthClipEnable = desc.RenderState.RasterizerState.IsDepthClipEnabled, + MultisampleEnable = desc.AttachmentFormats.SampleCount is not SampleCount.Count1 + }; + + graphicsPipelineStateDesc.DepthStencilState = new() + { + DepthEnable = desc.RenderState.DepthStencilState.IsDepthEnabled, + DepthWriteMask = desc.RenderState.DepthStencilState.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, + DepthFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.DepthCompareOp), + StencilEnable = desc.RenderState.DepthStencilState.IsStencilEnabled, + StencilReadMask = desc.RenderState.DepthStencilState.StencilReadMask, + StencilWriteMask = desc.RenderState.DepthStencilState.StencilWriteMask, + FrontFace = new() + { + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.CompareOp) + }, + BackFace = new() + { + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.CompareOp) + } + }; + + graphicsPipelineStateDesc.BlendState = new() + { + AlphaToCoverageEnable = desc.RenderState.BlendState.IsAlphaToCoverageEnabled, + IndependentBlendEnable = desc.RenderState.BlendState.IsIndependentBlendEnabled + }; + + for (int i = 0; i < colorAttachmentBlendStates.Length; i++) + { + ColorAttachmentBlendState blend = colorAttachmentBlendStates[i]; + + graphicsPipelineStateDesc.BlendState.RenderTarget[i] = new() + { + BlendEnable = blend.IsBlendingEnabled, + SrcBlend = DXFormats.DirectX12(blend.SrcRgbFactor), + DestBlend = DXFormats.DirectX12(blend.DstRgbFactor), + BlendOp = DXFormats.DirectX12(blend.RgbOp), + SrcBlendAlpha = DXFormats.DirectX12(blend.SrcAlphaFactor), + DestBlendAlpha = DXFormats.DirectX12(blend.DstAlphaFactor), + BlendOpAlpha = DXFormats.DirectX12(blend.AlphaOp), + LogicOp = LogicOp.Noop, + RenderTargetWriteMask = (byte)DXFormats.DirectX12(blend.ColorWrites) + }; + } + } + + PipelineStateStream2 pipelineStateStream2 = (PipelineStateStream2)graphicsPipelineStateDesc; + + if (desc.TaskShader is not null) + { + pipelineStateStream2.AS.Data = desc.TaskShader.DirectX12().GetShaderBytecode(scope); + } + + pipelineStateStream2.MS.Data = desc.MeshShader.DirectX12().GetShaderBytecode(scope); + + PipelineStateStreamDesc pipelineStateStreamDesc = new() + { + SizeInBytes = (uint)sizeof(PipelineStateStream2), + PPipelineStateSubobjectStream = &pipelineStateStream2 + }; + + context.Device10.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) From 92760342ba3eb1c228a5fc5f30ce060370a4f528 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 16:02:18 +0800 Subject: [PATCH 133/466] Refactor buffer descs, add DXQueryHeap for DirectX 12 Removed StrideInBytes from most BufferDesc usages, defaulting to 0 for Vertex and Index buffers to simplify buffer creation. Introduced DXQueryHeap class for DirectX 12 query heap support and updated DXGraphicsContext to use it. Added a stub for DXFormats.DirectX12(QueryType). Updated global usings and cleaned up buffer management code. --- .../Renderers/PathTracingRenderer.cs | 1 - .../Renderers/RasterizationRenderer.cs | 5 +- .../ImGuiRenderer.cs | 3 -- sources/Zenith.NET.DirectX12/DXFormats.cs | 5 ++ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 2 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 50 +++++++++++++++++++ sources/Zenith.NET.DirectX12/Usings.cs | 1 + sources/Zenith.NET/Downloader.cs | 1 - sources/Zenith.NET/Structs/BufferDesc.cs | 8 +-- sources/Zenith.NET/Uploader.cs | 1 - 10 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXQueryHeap.cs diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 0860aea6..c99b7ced 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -57,7 +57,6 @@ public PathTracingRenderer() constantBuffer = App.Context.CreateBuffer(new() { SizeInBytes = (uint)sizeof(PathTracingConstants), - StrideInBytes = (uint)sizeof(PathTracingConstants), Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.Constant }); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 50f4b40c..561e6658 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -24,14 +24,14 @@ public RasterizationRenderer() indexCount = (uint)indices.Length; - vertexBuffer = App.Context.CreateBuffer(BufferDesc.Vertex((uint)(sizeof(Vertex) * vertices.Length), (uint)sizeof(Vertex))); + vertexBuffer = App.Context.CreateBuffer(BufferDesc.Vertex((uint)(sizeof(Vertex) * vertices.Length))); fixed (Vertex* pointer = vertices) { vertexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) }); } - indexBuffer = App.Context.CreateBuffer(BufferDesc.Index((uint)(sizeof(uint) * indices.Length), sizeof(uint))); + indexBuffer = App.Context.CreateBuffer(BufferDesc.Index((uint)(sizeof(uint) * indices.Length))); fixed (uint* pointer = indices) { @@ -41,7 +41,6 @@ public RasterizationRenderer() constantBuffer = App.Context.CreateBuffer(new() { SizeInBytes = (uint)sizeof(RasterizationConstants), - StrideInBytes = (uint)sizeof(RasterizationConstants), Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.Constant }); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 3bc9eba1..8d146f57 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -290,7 +290,6 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, vertexBuffer = Context.CreateBuffer(new() { SizeInBytes = totalVertexSizeInBytes, - StrideInBytes = (uint)sizeof(ImDrawVert), Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.Vertex }); @@ -303,7 +302,6 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, indexBuffer = Context.CreateBuffer(new() { SizeInBytes = totalIndexSizeInBytes, - StrideInBytes = sizeof(ushort), Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.Index }); @@ -316,7 +314,6 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, constantBuffer = Context.CreateBuffer(new() { SizeInBytes = totalConstantSizeInBytes, - StrideInBytes = (uint)sizeof(Constants), Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.Constant }); diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index b4ad1577..6c1cab76 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -422,4 +422,9 @@ public static ColorWriteEnable DirectX12(ColorWrites colorWrites) return result; } + + public static QueryHeapType DirectX12(QueryType queryType) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 7ee503f7..dd85d240 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -211,7 +211,7 @@ protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShading protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { - throw new NotImplementedException(); + return new DXQueryHeap(this, desc); } protected override void Destroy() diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs new file mode 100644 index 00000000..edb88f3a --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -0,0 +1,50 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXQueryHeap : QueryHeap +{ + public ComPtr QueryHeap; + + public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context, desc) + { + DxQueryHeapDesc queryHeapDesc = new() + { + Type = DXFormats.DirectX12(desc.Type), + Count = desc.Count + }; + + context.Device10.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); + + Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Access = BufferAccess.CpuReadOnly }, null); + } + + public DXBuffer Buffer { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void GetResultsImpl(Span results, uint startIndex) + { + MappedMemory mappedMemory = Buffer.Map(); + + new Span((void*)(mappedMemory.Pointer + (sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + + Buffer.Unmap(); + } + + protected override void SetResourceName(string name) + { + QueryHeap.SetName(name).Success(); + } + + protected override void Destroy() + { + Buffer.Dispose(); + + QueryHeap.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index d6827639..073f41d9 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -4,6 +4,7 @@ global using DxHeapDesc = Silk.NET.Direct3D12.HeapDesc; global using DxHeapType = Silk.NET.Direct3D12.HeapType; global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; +global using DxQueryHeapDesc = Silk.NET.Direct3D12.QueryHeapDesc; global using DxRange = Silk.NET.Direct3D12.Range; global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index dd59ce51..df6205ab 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -22,7 +22,6 @@ public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) lease = new(context.CreateBuffer(new() { SizeInBytes = data.SizeInBytes, - StrideInBytes = 1, Access = BufferAccess.CpuReadOnly, Usages = BufferUsages.CopyDst })); diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index 50f93c27..81bff8de 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -10,23 +10,23 @@ public struct BufferDesc public BufferUsages Usages; - public static BufferDesc Vertex(uint sizeInBytes, uint strideInBytes) + public static BufferDesc Vertex(uint sizeInBytes) { return new() { SizeInBytes = sizeInBytes, - StrideInBytes = strideInBytes, + StrideInBytes = 0, Access = BufferAccess.GpuOnly, Usages = BufferUsages.CopyDst | BufferUsages.Vertex }; } - public static BufferDesc Index(uint sizeInBytes, uint strideInBytes) + public static BufferDesc Index(uint sizeInBytes) { return new() { SizeInBytes = sizeInBytes, - StrideInBytes = strideInBytes, + StrideInBytes = 0, Access = BufferAccess.GpuOnly, Usages = BufferUsages.CopyDst | BufferUsages.Index }; diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index 1d3960b5..b97c620a 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -22,7 +22,6 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) lease = new(context.CreateBuffer(new() { SizeInBytes = sizeInBytes, - StrideInBytes = 1, Access = BufferAccess.CpuWriteOnly, Usages = BufferUsages.CopySrc })); From 8cb62480f75a0a13a2b07ba3a6abd96434d94b26 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 16:03:45 +0800 Subject: [PATCH 134/466] Implement DirectX12 query type mapping and extension Replaced NotImplementedException in DXFormats.DirectX12 with a switch-based mapping from QueryType to QueryHeapType. Added an internal DirectX12 extension method to cast QueryHeap to DXQueryHeap. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 7 ++++++- sources/Zenith.NET.DirectX12/Extensions.cs | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 6c1cab76..ffa6b56b 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -425,6 +425,11 @@ public static ColorWriteEnable DirectX12(ColorWrites colorWrites) public static QueryHeapType DirectX12(QueryType queryType) { - throw new NotImplementedException(); + return queryType switch + { + QueryType.Occlusion or QueryType.BinaryOcclusion => QueryHeapType.Occlusion, + QueryType.Timestamp => QueryHeapType.Timestamp, + _ => default + }; } } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 3d7f1f14..8f3147d4 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -110,5 +110,9 @@ internal DXMeshShadingPipeline DirectX12() extension(QueryHeap queryHeap) { + internal DXQueryHeap DirectX12() + { + return (DXQueryHeap)queryHeap; + } } } From 1f06f1f736867671bdae4437a67e3eb87daf5132 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 16:08:15 +0800 Subject: [PATCH 135/466] Update DXCommandQueue method implementations Replaced NotImplementedException in GetNativeObject with a 0 return value. Updated SetResourceName to set the command queue name using SetName(). --- sources/Zenith.NET.DirectX12/DXCommandQueue.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 33d2ca6c..19c78631 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -7,7 +7,7 @@ internal class DXCommandQueue(DXGraphicsContext context, CommandQueueType type, { public override nint GetNativeObject(NativeObjectType type) { - throw new NotImplementedException(); + return 0; } protected override ulong GetCompletedValue() @@ -32,6 +32,6 @@ protected override void WaitImpl(ulong waitValue) protected override void SetResourceName(string name) { - throw new NotImplementedException(); + commandQueue.SetName(name).Success(); } } From b0b43c6ac5f510fe48c68bd133c5637f7bf1244a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 16:23:39 +0800 Subject: [PATCH 136/466] Reorder static methods in DXFormats class Moved DirectX12(SampleCount), DirectX12(TextureUsages), and DirectX12(BorderColor) methods to new locations within the DXFormats class for improved organization. No changes to logic or functionality. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 136 +++++++++++----------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index ffa6b56b..44fe365c 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -44,33 +44,6 @@ public static DxHeapType DirectX12(BufferAccess bufferAccess) }; } - public static ResourceFlags DirectX12(TextureUsages textureUsages) - { - ResourceFlags result = ResourceFlags.None; - - if (!textureUsages.HasFlag(TextureUsages.Sampled)) - { - result |= ResourceFlags.DenyShaderResource; - } - - if (textureUsages.HasFlag(TextureUsages.Storage)) - { - result |= ResourceFlags.AllowUnorderedAccess; - } - - if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) - { - result |= ResourceFlags.AllowRenderTarget; - } - - if (textureUsages.HasFlag(TextureUsages.DepthStencil)) - { - result |= ResourceFlags.AllowDepthStencil; - } - - return result; - } - public static ResourceDimension DirectX12(TextureType textureType) { return textureType switch @@ -89,20 +62,6 @@ TextureType.TextureCube or }; } - public static SampleDesc DirectX12(SampleCount sampleCount) - { - return sampleCount switch - { - SampleCount.Count1 => new(1, 0), - SampleCount.Count2 => new(2, 0), - SampleCount.Count4 => new(4, 0), - SampleCount.Count8 => new(8, 0), - SampleCount.Count16 => new(16, 0), - SampleCount.Count32 => new(32, 0), - _ => default - }; - } - public static Format DirectX12(PixelFormat pixelFormat) { return pixelFormat switch @@ -181,6 +140,58 @@ public static Format DirectX12(PixelFormat pixelFormat) }; } + public static SampleDesc DirectX12(SampleCount sampleCount) + { + return sampleCount switch + { + SampleCount.Count1 => new(1, 0), + SampleCount.Count2 => new(2, 0), + SampleCount.Count4 => new(4, 0), + SampleCount.Count8 => new(8, 0), + SampleCount.Count16 => new(16, 0), + SampleCount.Count32 => new(32, 0), + _ => default + }; + } + + public static ResourceFlags DirectX12(TextureUsages textureUsages) + { + ResourceFlags result = ResourceFlags.None; + + if (!textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= ResourceFlags.DenyShaderResource; + } + + if (textureUsages.HasFlag(TextureUsages.Storage)) + { + result |= ResourceFlags.AllowUnorderedAccess; + } + + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) + { + result |= ResourceFlags.AllowRenderTarget; + } + + if (textureUsages.HasFlag(TextureUsages.DepthStencil)) + { + result |= ResourceFlags.AllowDepthStencil; + } + + return result; + } + + public static (float R, float G, float B, float A) DirectX12(BorderColor borderColor) + { + return borderColor switch + { + BorderColor.TransparentBlack => (0.0f, 0.0f, 0.0f, 0.0f), + BorderColor.OpaqueBlack => (0.0f, 0.0f, 0.0f, 1.0f), + BorderColor.OpaqueWhite => (1.0f, 1.0f, 1.0f, 1.0f), + _ => default + }; + } + public static Filter DirectX12(FilterMode minFilter, FilterMode magFilter, FilterMode mipFilter, uint maxAnisotropy, CompareOp compareOp) { if (maxAnisotropy > 1) @@ -240,17 +251,6 @@ public static ComparisonFunc DirectX12(CompareOp compareOp) }; } - public static (float R, float G, float B, float A) DirectX12(BorderColor borderColor) - { - return borderColor switch - { - BorderColor.TransparentBlack => (0.0f, 0.0f, 0.0f, 0.0f), - BorderColor.OpaqueBlack => (0.0f, 0.0f, 0.0f, 1.0f), - BorderColor.OpaqueWhite => (1.0f, 1.0f, 1.0f, 1.0f), - _ => default - }; - } - public static PrimitiveTopologyType DirectX12(PrimitiveTopology primitiveTopology) { return primitiveTopology switch @@ -347,6 +347,22 @@ public static DxCullMode DirectX12(CullMode cullMode) }; } + public static DxStencilOp DirectX12(StencilOp stencilOp) + { + return stencilOp switch + { + StencilOp.Keep => DxStencilOp.Keep, + StencilOp.Zero => DxStencilOp.Zero, + StencilOp.Replace => DxStencilOp.Replace, + StencilOp.IncrementAndClamp => DxStencilOp.IncrSat, + StencilOp.DecrementAndClamp => DxStencilOp.DecrSat, + StencilOp.Invert => DxStencilOp.Invert, + StencilOp.IncrementAndWrap => DxStencilOp.Incr, + StencilOp.DecrementAndWrap => DxStencilOp.Decr, + _ => default + }; + } + public static Blend DirectX12(BlendFactor blendFactor) { return blendFactor switch @@ -380,22 +396,6 @@ public static DxBlendOp DirectX12(BlendOp blendOp) }; } - public static DxStencilOp DirectX12(StencilOp stencilOp) - { - return stencilOp switch - { - StencilOp.Keep => DxStencilOp.Keep, - StencilOp.Zero => DxStencilOp.Zero, - StencilOp.Replace => DxStencilOp.Replace, - StencilOp.IncrementAndClamp => DxStencilOp.IncrSat, - StencilOp.DecrementAndClamp => DxStencilOp.DecrSat, - StencilOp.Invert => DxStencilOp.Invert, - StencilOp.IncrementAndWrap => DxStencilOp.Incr, - StencilOp.DecrementAndWrap => DxStencilOp.Decr, - _ => default - }; - } - public static ColorWriteEnable DirectX12(ColorWrites colorWrites) { ColorWriteEnable result = ColorWriteEnable.None; From a857b90f78ab03360bbac489ee598a0d81a2d2c5 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 18:26:36 +0800 Subject: [PATCH 137/466] Update to ID3D12Device14 in DirectX 12 backend Replaced all usage of ID3D12Device10 with ID3D12Device14 across the DirectX 12 backend. Updated device method calls, field declarations, and resource management to leverage the newer API, enabling access to the latest DirectX 12 features. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 2 +- sources/Zenith.NET.DirectX12/DXBufferView.cs | 6 ++-- .../Zenith.NET.DirectX12/DXCapabilities.cs | 4 +-- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 2 +- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 4 +-- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 28 +++++++++---------- .../DXGraphicsPipeline.cs | 2 +- sources/Zenith.NET.DirectX12/DXHeap.cs | 6 ++-- .../DXMeshShadingPipeline.cs | 2 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 2 +- sources/Zenith.NET.DirectX12/DXSampler.cs | 2 +- sources/Zenith.NET.DirectX12/DXTexture.cs | 2 +- sources/Zenith.NET.DirectX12/DXTextureView.cs | 4 +-- 13 files changed, 33 insertions(+), 33 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 09315030..d38f220f 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -18,7 +18,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr= RaytracingTier.Tier11; diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index 10c23075..dc7dcf5b 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -17,7 +17,7 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : CS = desc.ComputeShader.DirectX12().GetShaderBytecode(scope) }; - context.Device10.CreateComputePipelineState(&computePipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device14.CreateComputePipelineState(&computePipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 22df9d88..2840c3e9 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -26,11 +26,11 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None }; - context.Device10.CreateDescriptorHeap(&desc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); + context.Device14.CreateDescriptorHeap(&desc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); CpuStartHandle = Heap.GetCPUDescriptorHandleForHeapStart(); GpuStartHandle = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; - IncrementSize = context.Device10.GetDescriptorHandleIncrementSize(type); + IncrementSize = context.Device14.GetDescriptorHandleIncrementSize(type); } public DXDescriptorToken Allocate() diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index dd85d240..da34e4d8 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -14,7 +14,7 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public ComPtr Adapter4; - public ComPtr Device10; + public ComPtr Device14; public ComPtr? InfoQueue1; @@ -68,13 +68,13 @@ protected override void Initialize(bool useValidationLayer, Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, SilkMarshal.GuidPtrOf(), (void**)Adapter4.GetAddressOf()).Success(); - if (!D3D12.CreateDevice((IUnknown*)Adapter4.Handle, D3DFeatureLevel.Level122, SilkMarshal.GuidPtrOf(), (void**)Device10.GetAddressOf()).IsSuccess()) + if (!D3D12.CreateDevice((IUnknown*)Adapter4.Handle, D3DFeatureLevel.Level122, SilkMarshal.GuidPtrOf(), (void**)Device14.GetAddressOf()).IsSuccess()) { throw new NotSupportedException("Direct3D 12 Feature Level 12_2 is not supported on the selected adapter."); } ComPtr infoQueue1 = new(); - if (Device10.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)infoQueue1.GetAddressOf()).IsSuccess()) + if (Device14.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)infoQueue1.GetAddressOf()).IsSuccess()) { InfoQueue1 = infoQueue1; } @@ -102,32 +102,32 @@ protected override void Initialize(bool useValidationLayer, using ComPtr rootSignatureBlob = new(); using ComPtr rootSignatureError = new(); D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, rootSignatureBlob.GetAddressOf(), rootSignatureError.GetAddressOf()).Success(); - Device10.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); + Device14.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; - Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); + Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); + Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); + Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device10.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); + Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; - Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); + Device14.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Compute; - Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)ComputeCommandQueue.GetAddressOf()).Success(); + Device14.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)ComputeCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Copy; - Device10.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CopyCommandQueue.GetAddressOf()).Success(); + Device14.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CopyCommandQueue.GetAddressOf()).Success(); capabilities = new DXCapabilities(this); graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); @@ -150,7 +150,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); - ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); + ResourceAllocationInfo info = Device14.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } @@ -159,7 +159,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); - ResourceAllocationInfo info = Device10.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); + ResourceAllocationInfo info = Device14.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } @@ -236,7 +236,7 @@ protected override void Destroy() InfoQueue1?.Dispose(); - Device10.Dispose(); + Device14.Dispose(); Adapter4.Dispose(); Factory7.Dispose(); diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 1a549381..ec4f7ee1 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -137,7 +137,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) } } - context.Device10.CreateGraphicsPipelineState(&graphicsPipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device14.CreateGraphicsPipelineState(&graphicsPipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index d3700c58..d8287b2c 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -17,7 +17,7 @@ public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) Alignment = DXGraphicsContext.DefaultHeapAlignment }; - context.Device10.CreateHeap(&heapDesc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); + context.Device14.CreateHeap(&heapDesc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; @@ -32,7 +32,7 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); ComPtr resource = new(); - Context.Device10.CreatePlacedResource2(Heap, + Context.Device14.CreatePlacedResource2(Heap, offsetInBytes, &resourceDesc, BarrierLayout.Undefined, @@ -50,7 +50,7 @@ protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc de ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); ComPtr resource = new(); - Context.Device10.CreatePlacedResource2(Heap, + Context.Device14.CreatePlacedResource2(Heap, offsetInBytes, &resourceDesc, BarrierLayout.Undefined, diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 3f5466a5..912ee012 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -124,7 +124,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc PPipelineStateSubobjectStream = &pipelineStateStream2 }; - context.Device10.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device14.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index edb88f3a..2d790e78 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -15,7 +15,7 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context Count = desc.Count }; - context.Device10.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); + context.Device14.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Access = BufferAccess.CpuReadOnly }, null); } diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs index c8ba7d7a..f5a6a178 100644 --- a/sources/Zenith.NET.DirectX12/DXSampler.cs +++ b/sources/Zenith.NET.DirectX12/DXSampler.cs @@ -30,7 +30,7 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de samplerDesc.BorderColor[2] = b; samplerDesc.BorderColor[3] = a; - context.Device10.CreateSampler(&samplerDesc, Token.CpuHandle); + context.Device14.CreateSampler(&samplerDesc, Token.CpuHandle); } public override ResourceHandle Handle => Token.ResourceHandle; diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index f7cc3288..5002d379 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -16,7 +16,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr Date: Tue, 26 May 2026 18:45:25 +0800 Subject: [PATCH 138/466] Update to latest DirectX 12 API and structures Switch to ID3D12Debug6, RootSignatureDesc2, and SamplerDesc2 for improved validation and compatibility. Use GetResourceAllocationInfo3 for resource allocation. Remove obsolete DxSamplerDesc alias. --- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 17 +++++++++-------- sources/Zenith.NET.DirectX12/DXSampler.cs | 12 ++++++------ sources/Zenith.NET.DirectX12/Usings.cs | 1 - 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index da34e4d8..924a563a 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -58,10 +58,11 @@ protected override void Initialize(bool useValidationLayer, out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - using ComPtr debug = new(); - if (useValidationLayer && D3D12.GetDebugInterface(SilkMarshal.GuidPtrOf(), (void**)debug.GetAddressOf()).IsSuccess()) + using ComPtr debug6 = new(); + if (useValidationLayer && D3D12.GetDebugInterface(SilkMarshal.GuidPtrOf(), (void**)debug6.GetAddressOf()).IsSuccess()) { - debug.EnableDebugLayer(); + debug6.SetForceLegacyBarrierValidation(false); + debug6.EnableDebugLayer(); } DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), SilkMarshal.GuidPtrOf(), (void**)Factory7.GetAddressOf()).Success(); @@ -86,7 +87,7 @@ protected override void Initialize(bool useValidationLayer, Descriptor = new() { Flags = RootDescriptorFlags.DataVolatile } }; - RootSignatureDesc1 rootSignatureDesc = new() + RootSignatureDesc2 rootSignatureDesc = new() { NumParameters = 1, PParameters = &rootParameter, @@ -95,8 +96,8 @@ protected override void Initialize(bool useValidationLayer, VersionedRootSignatureDesc versionedRootSignatureDesc = new() { - Version = D3DRootSignatureVersion.Version11, - Desc11 = rootSignatureDesc + Version = D3DRootSignatureVersion.Version12, + Desc12 = rootSignatureDesc }; using ComPtr rootSignatureBlob = new(); @@ -150,7 +151,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); - ResourceAllocationInfo info = Device14.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); + ResourceAllocationInfo info = Device14.GetResourceAllocationInfo3(0, 1, &resourceDesc, default(uint*), default, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } @@ -159,7 +160,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); - ResourceAllocationInfo info = Device14.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); + ResourceAllocationInfo info = Device14.GetResourceAllocationInfo3(0, 1, &resourceDesc, default(uint*), default, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs index f5a6a178..b2c84e59 100644 --- a/sources/Zenith.NET.DirectX12/DXSampler.cs +++ b/sources/Zenith.NET.DirectX12/DXSampler.cs @@ -12,7 +12,7 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de (float r, float g, float b, float a) = DXFormats.DirectX12(desc.BorderColor); - DxSamplerDesc samplerDesc = new() + SamplerDesc2 samplerDesc = new() { Filter = DXFormats.DirectX12(desc.MinFilter, desc.MagFilter, desc.MipFilter, desc.MaxAnisotropy, desc.CompareOp), AddressU = DXFormats.DirectX12(desc.AddressU), @@ -25,12 +25,12 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de MaxLOD = desc.MaxLod }; - samplerDesc.BorderColor[0] = r; - samplerDesc.BorderColor[1] = g; - samplerDesc.BorderColor[2] = b; - samplerDesc.BorderColor[3] = a; + samplerDesc.Anonymous.FloatBorderColor[0] = r; + samplerDesc.Anonymous.FloatBorderColor[1] = g; + samplerDesc.Anonymous.FloatBorderColor[2] = b; + samplerDesc.Anonymous.FloatBorderColor[3] = a; - context.Device14.CreateSampler(&samplerDesc, Token.CpuHandle); + context.Device14.CreateSampler2(&samplerDesc, Token.CpuHandle); } public override ResourceHandle Handle => Token.ResourceHandle; diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 073f41d9..89b08cf3 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -6,5 +6,4 @@ global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxQueryHeapDesc = Silk.NET.Direct3D12.QueryHeapDesc; global using DxRange = Silk.NET.Direct3D12.Range; -global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; From db3c468f07569f54ccf263f4686f8771e2a11628 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 19:08:19 +0800 Subject: [PATCH 139/466] Refactor to use DX12 pipeline state stream API Modernize pipeline state creation by replacing legacy *PipelineStateDesc usage with direct construction of PipelineStateStream2 and passing it to CreatePipelineState. Make PipelineStateStream2 subobject fields private with property accessors, remove the explicit conversion operator, and update all pipeline constructors to use the new structure. Improves encapsulation and streamlines pipeline setup for compute, graphics, and mesh shading pipelines. --- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 12 +- .../DXGraphicsPipeline.cs | 34 +-- .../DXMeshShadingPipeline.cs | 37 ++-- .../PipelineStateStream2.cs | 197 +++++++++--------- 4 files changed, 143 insertions(+), 137 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index dc7dcf5b..27458d75 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -11,13 +11,19 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : { using ZenithMarshal.Scope scope = new(); - ComputePipelineStateDesc computePipelineStateDesc = new() + PipelineStateStream2 pipelineStateStream = new() { - PRootSignature = context.RootSignature, + RootSignature = (nint)context.RootSignature.Handle, CS = desc.ComputeShader.DirectX12().GetShaderBytecode(scope) }; - context.Device14.CreateComputePipelineState(&computePipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + PipelineStateStreamDesc pipelineStateStreamDesc = new() + { + SizeInBytes = (uint)sizeof(PipelineStateStream2), + PPipelineStateSubobjectStream = &pipelineStateStream + }; + + context.Device14.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index ec4f7ee1..1751c910 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -12,13 +12,13 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) { using ZenithMarshal.Scope scope = new(); - GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() + PipelineStateStream2 pipelineStateStream = new() { - PRootSignature = context.RootSignature, + RootSignature = (nint)context.RootSignature.Handle, + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology), VS = desc.VertexShader.DirectX12().GetShaderBytecode(scope), PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), - SampleMask = uint.MaxValue, - PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology) + SampleMask = uint.MaxValue }; // InputLayouts @@ -41,7 +41,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) } } - graphicsPipelineStateDesc.InputLayout = new() + pipelineStateStream.InputLayout = new() { PInputElementDescs = (InputElementDesc*)ZenithMarshal.AllocateAndFill(scope, [.. inputElementDescs]), NumElements = (uint)inputElementDescs.Count @@ -50,16 +50,16 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) // AttachmentFormats { - graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; + pipelineStateStream.RTVFormats.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) { - graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); + pipelineStateStream.RTVFormats.RTFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); } - graphicsPipelineStateDesc.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; + pipelineStateStream.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; - graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); + pipelineStateStream.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); } // RenderState @@ -76,7 +76,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) desc.RenderState.BlendState.ColorAttachment7 ]; - graphicsPipelineStateDesc.RasterizerState = new() + pipelineStateStream.RasterizerState = new() { FillMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.FillMode), CullMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.CullMode), @@ -88,7 +88,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) MultisampleEnable = desc.AttachmentFormats.SampleCount is not SampleCount.Count1 }; - graphicsPipelineStateDesc.DepthStencilState = new() + pipelineStateStream.DepthStencilState = new() { DepthEnable = desc.RenderState.DepthStencilState.IsDepthEnabled, DepthWriteMask = desc.RenderState.DepthStencilState.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, @@ -112,7 +112,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) } }; - graphicsPipelineStateDesc.BlendState = new() + pipelineStateStream.BlendState = new() { AlphaToCoverageEnable = desc.RenderState.BlendState.IsAlphaToCoverageEnabled, IndependentBlendEnable = desc.RenderState.BlendState.IsIndependentBlendEnabled @@ -122,7 +122,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) { ColorAttachmentBlendState blend = colorAttachmentBlendStates[i]; - graphicsPipelineStateDesc.BlendState.RenderTarget[i] = new() + pipelineStateStream.BlendState.RenderTarget[i] = new() { BlendEnable = blend.IsBlendingEnabled, SrcBlend = DXFormats.DirectX12(blend.SrcRgbFactor), @@ -137,7 +137,13 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) } } - context.Device14.CreateGraphicsPipelineState(&graphicsPipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + PipelineStateStreamDesc pipelineStateStreamDesc = new() + { + SizeInBytes = (uint)sizeof(PipelineStateStream2), + PPipelineStateSubobjectStream = &pipelineStateStream + }; + + context.Device14.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 912ee012..0c201d3e 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -12,26 +12,28 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc { using ZenithMarshal.Scope scope = new(); - GraphicsPipelineStateDesc graphicsPipelineStateDesc = new() + PipelineStateStream2 pipelineStateStream = new() { - PRootSignature = context.RootSignature, + RootSignature = (nint)context.RootSignature.Handle, + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology), PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), - SampleMask = uint.MaxValue, - PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology) + AS = (desc.TaskShader?.DirectX12().GetShaderBytecode(scope)) ?? default, + MS = desc.MeshShader.DirectX12().GetShaderBytecode(scope), + SampleMask = uint.MaxValue }; // AttachmentFormats { - graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; + pipelineStateStream.RTVFormats.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) { - graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); + pipelineStateStream.RTVFormats.RTFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); } - graphicsPipelineStateDesc.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; + pipelineStateStream.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; - graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); + pipelineStateStream.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); } // RenderState @@ -48,7 +50,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc desc.RenderState.BlendState.ColorAttachment7 ]; - graphicsPipelineStateDesc.RasterizerState = new() + pipelineStateStream.RasterizerState = new() { FillMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.FillMode), CullMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.CullMode), @@ -60,7 +62,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc MultisampleEnable = desc.AttachmentFormats.SampleCount is not SampleCount.Count1 }; - graphicsPipelineStateDesc.DepthStencilState = new() + pipelineStateStream.DepthStencilState = new() { DepthEnable = desc.RenderState.DepthStencilState.IsDepthEnabled, DepthWriteMask = desc.RenderState.DepthStencilState.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, @@ -84,7 +86,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc } }; - graphicsPipelineStateDesc.BlendState = new() + pipelineStateStream.BlendState = new() { AlphaToCoverageEnable = desc.RenderState.BlendState.IsAlphaToCoverageEnabled, IndependentBlendEnable = desc.RenderState.BlendState.IsIndependentBlendEnabled @@ -94,7 +96,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc { ColorAttachmentBlendState blend = colorAttachmentBlendStates[i]; - graphicsPipelineStateDesc.BlendState.RenderTarget[i] = new() + pipelineStateStream.BlendState.RenderTarget[i] = new() { BlendEnable = blend.IsBlendingEnabled, SrcBlend = DXFormats.DirectX12(blend.SrcRgbFactor), @@ -109,19 +111,10 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc } } - PipelineStateStream2 pipelineStateStream2 = (PipelineStateStream2)graphicsPipelineStateDesc; - - if (desc.TaskShader is not null) - { - pipelineStateStream2.AS.Data = desc.TaskShader.DirectX12().GetShaderBytecode(scope); - } - - pipelineStateStream2.MS.Data = desc.MeshShader.DirectX12().GetShaderBytecode(scope); - PipelineStateStreamDesc pipelineStateStreamDesc = new() { SizeInBytes = (uint)sizeof(PipelineStateStream2), - PPipelineStateSubobjectStream = &pipelineStateStream2 + PPipelineStateSubobjectStream = &pipelineStateStream }; context.Device14.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); diff --git a/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs b/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs index 3689bc95..3f8a3aa2 100644 --- a/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs +++ b/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs @@ -2,108 +2,109 @@ using System.Runtime.InteropServices; using Silk.NET.Direct3D12; using Silk.NET.DXGI; -using static Silk.NET.Direct3D12.RTFormatArray; namespace Zenith.NET.DirectX12; -internal unsafe struct PipelineStateStream2() +internal struct PipelineStateStream2() { - public StreamFlags Flags = new(); + private StreamFlags flags = new(); + private StreamNodeMask nodeMask = new(); + private StreamRootSignature rootSignature = new(); + private StreamInputLayout inputLayout = new(); + private StreamIBStripCutValue ibStripCutValue = new(); + private StreamPrimitiveTopology primitiveTopologyType = new(); + private StreamVS vs = new(); + private StreamGS gs = new(); + private StreamStreamOutput streamOutput = new(); + private StreamHS hs = new(); + private StreamDS ds = new(); + private StreamPS ps = new(); + private StreamAS @as = new(); + private StreamMS ms = new(); + private StreamCS cs = new(); + private StreamBlend blendState = new(); + private StreamDepthStencil1 depthStencilState = new(); + private StreamDepthStencilFormat dsvFormat = new(); + private StreamRasterizer rasterizerState = new(); + private StreamRenderTargetFormats rtvFormats = new(); + private StreamSampleDesc sampleDesc = new(); + private StreamSampleMask sampleMask = new(); + private StreamCachedPso cachedPSO = new(); + private StreamViewInstancing viewInstancingDesc = new(); - public StreamNodeMask NodeMask = new(); + [UnscopedRef] + public ref PipelineStateFlags Flags => ref flags.Data; - public StreamRootSignature RootSignature = new(); + [UnscopedRef] + public ref uint NodeMask => ref nodeMask.Data; - public StreamInputLayout InputLayout = new(); + [UnscopedRef] + public ref nint RootSignature => ref rootSignature.Data; - public StreamIBStripCutValue IBStripCutValue = new(); + [UnscopedRef] + public ref InputLayoutDesc InputLayout => ref inputLayout.Data; - public StreamPrimitiveTopology PrimitiveTopologyType = new(); + [UnscopedRef] + public ref IndexBufferStripCutValue IBStripCutValue => ref ibStripCutValue.Data; - public StreamVS VS = new(); + [UnscopedRef] + public ref PrimitiveTopologyType PrimitiveTopologyType => ref primitiveTopologyType.Data; - public StreamGS GS = new(); + [UnscopedRef] + public ref ShaderBytecode VS => ref vs.Data; - public StreamStreamOutput StreamOutput = new(); + [UnscopedRef] + public ref ShaderBytecode GS => ref gs.Data; - public StreamHS HS = new(); + [UnscopedRef] + public ref StreamOutputDesc StreamOutput => ref streamOutput.Data; - public StreamDS DS = new(); + [UnscopedRef] + public ref ShaderBytecode HS => ref hs.Data; - public StreamPS PS = new(); + [UnscopedRef] + public ref ShaderBytecode DS => ref ds.Data; - public StreamAS AS = new(); + [UnscopedRef] + public ref ShaderBytecode PS => ref ps.Data; - public StreamMS MS = new(); + [UnscopedRef] + public ref ShaderBytecode AS => ref @as.Data; - public StreamCS CS = new(); + [UnscopedRef] + public ref ShaderBytecode MS => ref ms.Data; - public StreamBlend BlendState = new(); + [UnscopedRef] + public ref ShaderBytecode CS => ref cs.Data; - public StreamDepthStencil1 DepthStencilState = new(); + [UnscopedRef] + public ref BlendDesc BlendState => ref blendState.Data; - public StreamDepthStencilFormat DSVFormat = new(); + [UnscopedRef] + public ref DepthStencilDesc1 DepthStencilState => ref depthStencilState.Data; - public StreamRasterizer RasterizerState = new(); + [UnscopedRef] + public ref Format DSVFormat => ref dsvFormat.Data; - public StreamRenderTargetFormats RTVFormats = new(); + [UnscopedRef] + public ref RasterizerDesc RasterizerState => ref rasterizerState.Data; - public StreamSampleDesc SampleDesc = new(); + [UnscopedRef] + public ref RTFormatArray RTVFormats => ref rtvFormats.Data; - public StreamSampleMask SampleMask = new(); + [UnscopedRef] + public ref SampleDesc SampleDesc => ref sampleDesc.Data; - public StreamCachedPso CachedPSO = new(); + [UnscopedRef] + public ref uint SampleMask => ref sampleMask.Data; - public StreamViewInstancing ViewInstancingDesc = new(); + [UnscopedRef] + public ref CachedPipelineState CachedPSO => ref cachedPSO.Data; - public static explicit operator PipelineStateStream2(GraphicsPipelineStateDesc desc) - { - return new() - { - Flags = { Data = desc.Flags }, - NodeMask = { Data = desc.NodeMask }, - RootSignature = { Data = (nint)desc.PRootSignature }, - InputLayout = { Data = desc.InputLayout }, - IBStripCutValue = { Data = desc.IBStripCutValue }, - PrimitiveTopologyType = { Data = desc.PrimitiveTopologyType }, - VS = { Data = desc.VS }, - GS = { Data = desc.GS }, - StreamOutput = { Data = desc.StreamOutput }, - HS = { Data = desc.HS }, - DS = { Data = desc.DS }, - PS = { Data = desc.PS }, - BlendState = { Data = desc.BlendState }, - DepthStencilState = - { - Data = new() - { - DepthEnable = desc.DepthStencilState.DepthEnable, - DepthWriteMask = desc.DepthStencilState.DepthWriteMask, - DepthFunc = desc.DepthStencilState.DepthFunc, - StencilEnable = desc.DepthStencilState.StencilEnable, - StencilReadMask = desc.DepthStencilState.StencilReadMask, - StencilWriteMask = desc.DepthStencilState.StencilWriteMask, - FrontFace = desc.DepthStencilState.FrontFace, - BackFace = desc.DepthStencilState.BackFace - } - }, - DSVFormat = { Data = desc.DSVFormat }, - RasterizerState = { Data = desc.RasterizerState }, - RTVFormats = - { - Data = new() - { - NumRenderTargets = desc.NumRenderTargets, - RTFormats = *(RTFormatsBuffer*)&desc.RTVFormats - } - }, - SampleDesc = { Data = desc.SampleDesc }, - SampleMask = { Data = desc.SampleMask }, - CachedPSO = { Data = desc.CachedPSO } - }; - } + [UnscopedRef] + public ref ViewInstancingDesc ViewInstancingDesc => ref viewInstancingDesc.Data; - internal struct SubObject(PipelineStateSubobjectType type) where T : unmanaged + private struct SubObject(PipelineStateSubobjectType type) where T : unmanaged { public readonly PipelineStateSubobjectType Type = type; @@ -111,7 +112,7 @@ internal struct SubObject(PipelineStateSubobjectType type) where T : unmanage } [StructLayout(LayoutKind.Explicit)] - internal struct StreamFlags() + private struct StreamFlags() { [FieldOffset(0)] private readonly nint _padding; @@ -124,7 +125,7 @@ internal struct StreamFlags() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamNodeMask() + private struct StreamNodeMask() { [FieldOffset(0)] private readonly nint _padding; @@ -137,7 +138,7 @@ internal struct StreamNodeMask() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamRootSignature() + private struct StreamRootSignature() { [FieldOffset(0)] private readonly nint _padding; @@ -150,7 +151,7 @@ internal struct StreamRootSignature() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamInputLayout() + private struct StreamInputLayout() { [FieldOffset(0)] private readonly nint _padding; @@ -163,7 +164,7 @@ internal struct StreamInputLayout() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamIBStripCutValue() + private struct StreamIBStripCutValue() { [FieldOffset(0)] private readonly nint _padding; @@ -176,7 +177,7 @@ internal struct StreamIBStripCutValue() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamPrimitiveTopology() + private struct StreamPrimitiveTopology() { [FieldOffset(0)] private readonly nint _padding; @@ -189,7 +190,7 @@ internal struct StreamPrimitiveTopology() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamVS() + private struct StreamVS() { [FieldOffset(0)] private readonly nint _padding; @@ -202,7 +203,7 @@ internal struct StreamVS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamGS() + private struct StreamGS() { [FieldOffset(0)] private readonly nint _padding; @@ -215,7 +216,7 @@ internal struct StreamGS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamStreamOutput() + private struct StreamStreamOutput() { [FieldOffset(0)] private readonly nint _padding; @@ -228,7 +229,7 @@ internal struct StreamStreamOutput() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamHS() + private struct StreamHS() { [FieldOffset(0)] private readonly nint _padding; @@ -241,7 +242,7 @@ internal struct StreamHS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamDS() + private struct StreamDS() { [FieldOffset(0)] private readonly nint _padding; @@ -254,7 +255,7 @@ internal struct StreamDS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamPS() + private struct StreamPS() { [FieldOffset(0)] private readonly nint _padding; @@ -267,7 +268,7 @@ internal struct StreamPS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamAS() + private struct StreamAS() { [FieldOffset(0)] private readonly nint _padding; @@ -280,7 +281,7 @@ internal struct StreamAS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamMS() + private struct StreamMS() { [FieldOffset(0)] private readonly nint _padding; @@ -293,7 +294,7 @@ internal struct StreamMS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamCS() + private struct StreamCS() { [FieldOffset(0)] private readonly nint _padding; @@ -306,7 +307,7 @@ internal struct StreamCS() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamBlend() + private struct StreamBlend() { [FieldOffset(0)] private readonly nint _padding; @@ -319,7 +320,7 @@ internal struct StreamBlend() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamDepthStencil1() + private struct StreamDepthStencil1() { [FieldOffset(0)] private readonly nint _padding; @@ -332,7 +333,7 @@ internal struct StreamDepthStencil1() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamDepthStencilFormat() + private struct StreamDepthStencilFormat() { [FieldOffset(0)] private readonly nint _padding; @@ -345,7 +346,7 @@ internal struct StreamDepthStencilFormat() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamRasterizer() + private struct StreamRasterizer() { [FieldOffset(0)] private readonly nint _padding; @@ -358,7 +359,7 @@ internal struct StreamRasterizer() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamRenderTargetFormats() + private struct StreamRenderTargetFormats() { [FieldOffset(0)] private readonly nint _padding; @@ -371,7 +372,7 @@ internal struct StreamRenderTargetFormats() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamSampleDesc() + private struct StreamSampleDesc() { [FieldOffset(0)] private readonly nint _padding; @@ -384,7 +385,7 @@ internal struct StreamSampleDesc() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamSampleMask() + private struct StreamSampleMask() { [FieldOffset(0)] private readonly nint _padding; @@ -397,7 +398,7 @@ internal struct StreamSampleMask() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamCachedPso() + private struct StreamCachedPso() { [FieldOffset(0)] private readonly nint _padding; @@ -410,7 +411,7 @@ internal struct StreamCachedPso() } [StructLayout(LayoutKind.Explicit)] - internal struct StreamViewInstancing() + private struct StreamViewInstancing() { [FieldOffset(0)] private readonly nint _padding; @@ -421,4 +422,4 @@ internal struct StreamViewInstancing() [UnscopedRef] public ref ViewInstancingDesc Data => ref Object.Data; } -} +} \ No newline at end of file From 1b6e19d70b0a09878063ce1600558ceccfe01797 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 26 May 2026 19:16:45 +0800 Subject: [PATCH 140/466] Rename PipelineStateStream2 fields with underscore prefix Renamed private fields in PipelineStateStream2 to use an underscore prefix for consistency. Updated property accessors and changed property assignments in DXComputePipeline, DXGraphicsPipeline, and DXMeshShadingPipeline from RootSignature to PRootSignature to match the new naming convention. --- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 2 +- .../DXGraphicsPipeline.cs | 2 +- .../DXMeshShadingPipeline.cs | 2 +- .../PipelineStateStream2.cs | 96 +++++++++---------- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index 27458d75..de18c613 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -13,7 +13,7 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : PipelineStateStream2 pipelineStateStream = new() { - RootSignature = (nint)context.RootSignature.Handle, + PRootSignature = (nint)context.RootSignature.Handle, CS = desc.ComputeShader.DirectX12().GetShaderBytecode(scope) }; diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 1751c910..bdcbf2a5 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -14,7 +14,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) PipelineStateStream2 pipelineStateStream = new() { - RootSignature = (nint)context.RootSignature.Handle, + PRootSignature = (nint)context.RootSignature.Handle, PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology), VS = desc.VertexShader.DirectX12().GetShaderBytecode(scope), PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 0c201d3e..36ad39cf 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -14,7 +14,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc PipelineStateStream2 pipelineStateStream = new() { - RootSignature = (nint)context.RootSignature.Handle, + PRootSignature = (nint)context.RootSignature.Handle, PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology), PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), AS = (desc.TaskShader?.DirectX12().GetShaderBytecode(scope)) ?? default, diff --git a/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs b/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs index 3f8a3aa2..fcd570d6 100644 --- a/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs +++ b/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs @@ -7,102 +7,102 @@ namespace Zenith.NET.DirectX12; internal struct PipelineStateStream2() { - private StreamFlags flags = new(); - private StreamNodeMask nodeMask = new(); - private StreamRootSignature rootSignature = new(); - private StreamInputLayout inputLayout = new(); - private StreamIBStripCutValue ibStripCutValue = new(); - private StreamPrimitiveTopology primitiveTopologyType = new(); - private StreamVS vs = new(); - private StreamGS gs = new(); - private StreamStreamOutput streamOutput = new(); - private StreamHS hs = new(); - private StreamDS ds = new(); - private StreamPS ps = new(); - private StreamAS @as = new(); - private StreamMS ms = new(); - private StreamCS cs = new(); - private StreamBlend blendState = new(); - private StreamDepthStencil1 depthStencilState = new(); - private StreamDepthStencilFormat dsvFormat = new(); - private StreamRasterizer rasterizerState = new(); - private StreamRenderTargetFormats rtvFormats = new(); - private StreamSampleDesc sampleDesc = new(); - private StreamSampleMask sampleMask = new(); - private StreamCachedPso cachedPSO = new(); - private StreamViewInstancing viewInstancingDesc = new(); + private StreamFlags _flags = new(); + private StreamNodeMask _nodeMask = new(); + private StreamRootSignature _pRootSignature = new(); + private StreamInputLayout _inputLayout = new(); + private StreamIBStripCutValue _ibStripCutValue = new(); + private StreamPrimitiveTopology _primitiveTopologyType = new(); + private StreamVS _vs = new(); + private StreamGS _gs = new(); + private StreamStreamOutput _streamOutput = new(); + private StreamHS _hs = new(); + private StreamDS _ds = new(); + private StreamPS _ps = new(); + private StreamAS _as = new(); + private StreamMS _ms = new(); + private StreamCS _cs = new(); + private StreamBlend _blendState = new(); + private StreamDepthStencil1 _depthStencilState = new(); + private StreamDepthStencilFormat _dsvFormat = new(); + private StreamRasterizer _rasterizerState = new(); + private StreamRenderTargetFormats _rtvFormats = new(); + private StreamSampleDesc _sampleDesc = new(); + private StreamSampleMask _sampleMask = new(); + private StreamCachedPso _cachedPSO = new(); + private StreamViewInstancing _viewInstancingDesc = new(); [UnscopedRef] - public ref PipelineStateFlags Flags => ref flags.Data; + public ref PipelineStateFlags Flags => ref _flags.Data; [UnscopedRef] - public ref uint NodeMask => ref nodeMask.Data; + public ref uint NodeMask => ref _nodeMask.Data; [UnscopedRef] - public ref nint RootSignature => ref rootSignature.Data; + public ref nint PRootSignature => ref _pRootSignature.Data; [UnscopedRef] - public ref InputLayoutDesc InputLayout => ref inputLayout.Data; + public ref InputLayoutDesc InputLayout => ref _inputLayout.Data; [UnscopedRef] - public ref IndexBufferStripCutValue IBStripCutValue => ref ibStripCutValue.Data; + public ref IndexBufferStripCutValue IBStripCutValue => ref _ibStripCutValue.Data; [UnscopedRef] - public ref PrimitiveTopologyType PrimitiveTopologyType => ref primitiveTopologyType.Data; + public ref PrimitiveTopologyType PrimitiveTopologyType => ref _primitiveTopologyType.Data; [UnscopedRef] - public ref ShaderBytecode VS => ref vs.Data; + public ref ShaderBytecode VS => ref _vs.Data; [UnscopedRef] - public ref ShaderBytecode GS => ref gs.Data; + public ref ShaderBytecode GS => ref _gs.Data; [UnscopedRef] - public ref StreamOutputDesc StreamOutput => ref streamOutput.Data; + public ref StreamOutputDesc StreamOutput => ref _streamOutput.Data; [UnscopedRef] - public ref ShaderBytecode HS => ref hs.Data; + public ref ShaderBytecode HS => ref _hs.Data; [UnscopedRef] - public ref ShaderBytecode DS => ref ds.Data; + public ref ShaderBytecode DS => ref _ds.Data; [UnscopedRef] - public ref ShaderBytecode PS => ref ps.Data; + public ref ShaderBytecode PS => ref _ps.Data; [UnscopedRef] - public ref ShaderBytecode AS => ref @as.Data; + public ref ShaderBytecode AS => ref _as.Data; [UnscopedRef] - public ref ShaderBytecode MS => ref ms.Data; + public ref ShaderBytecode MS => ref _ms.Data; [UnscopedRef] - public ref ShaderBytecode CS => ref cs.Data; + public ref ShaderBytecode CS => ref _cs.Data; [UnscopedRef] - public ref BlendDesc BlendState => ref blendState.Data; + public ref BlendDesc BlendState => ref _blendState.Data; [UnscopedRef] - public ref DepthStencilDesc1 DepthStencilState => ref depthStencilState.Data; + public ref DepthStencilDesc1 DepthStencilState => ref _depthStencilState.Data; [UnscopedRef] - public ref Format DSVFormat => ref dsvFormat.Data; + public ref Format DSVFormat => ref _dsvFormat.Data; [UnscopedRef] - public ref RasterizerDesc RasterizerState => ref rasterizerState.Data; + public ref RasterizerDesc RasterizerState => ref _rasterizerState.Data; [UnscopedRef] - public ref RTFormatArray RTVFormats => ref rtvFormats.Data; + public ref RTFormatArray RTVFormats => ref _rtvFormats.Data; [UnscopedRef] - public ref SampleDesc SampleDesc => ref sampleDesc.Data; + public ref SampleDesc SampleDesc => ref _sampleDesc.Data; [UnscopedRef] - public ref uint SampleMask => ref sampleMask.Data; + public ref uint SampleMask => ref _sampleMask.Data; [UnscopedRef] - public ref CachedPipelineState CachedPSO => ref cachedPSO.Data; + public ref CachedPipelineState CachedPSO => ref _cachedPSO.Data; [UnscopedRef] - public ref ViewInstancingDesc ViewInstancingDesc => ref viewInstancingDesc.Data; + public ref ViewInstancingDesc ViewInstancingDesc => ref _viewInstancingDesc.Data; private struct SubObject(PipelineStateSubobjectType type) where T : unmanaged { From e4659bab0cae57bd68e40ab89ef3b5e238c304c0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 27 May 2026 07:14:06 +0800 Subject: [PATCH 141/466] Remove explicit Layout property from DXTexture init The Layout property is no longer set to TextureLayout.LayoutUnknown when initializing the texture description in DXTexture. This change relies on the default or implicit handling of the Layout property. --- sources/Zenith.NET.DirectX12/DXTexture.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 5002d379..7f8180db 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -79,7 +79,6 @@ public static ResourceDesc1 ResourceDesc(TextureDesc desc) MipLevels = (ushort)desc.MipLevels, Format = DXFormats.DirectX12(desc.Format), SampleDesc = DXFormats.DirectX12(desc.SampleCount), - Layout = TextureLayout.LayoutUnknown, Flags = DXFormats.DirectX12(desc.Usages) }; } From b0fdc5d486561523048d685e9f46bfdc7b6fc1c9 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 27 May 2026 07:20:15 +0800 Subject: [PATCH 142/466] Update to use ID3D12Device14 for D3D12 device creation Switched device creation from ID3D12Device10 to ID3D12Device14 to leverage newer Direct3D 12 features and ensure compatibility with the latest interface. --- sources/Zenith.NET.DirectX12/DXGraphicsContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 924a563a..e62b7022 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -69,7 +69,7 @@ protected override void Initialize(bool useValidationLayer, Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, SilkMarshal.GuidPtrOf(), (void**)Adapter4.GetAddressOf()).Success(); - if (!D3D12.CreateDevice((IUnknown*)Adapter4.Handle, D3DFeatureLevel.Level122, SilkMarshal.GuidPtrOf(), (void**)Device14.GetAddressOf()).IsSuccess()) + if (!D3D12.CreateDevice((IUnknown*)Adapter4.Handle, D3DFeatureLevel.Level122, SilkMarshal.GuidPtrOf(), (void**)Device14.GetAddressOf()).IsSuccess()) { throw new NotSupportedException("Direct3D 12 Feature Level 12_2 is not supported on the selected adapter."); } From 2daa2e23456f33710d9f77ff9fdd4e007e824086 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 27 May 2026 07:23:30 +0800 Subject: [PATCH 143/466] Remove legacy barrier validation flag in DXGraphicsContext Removed call to SetForceLegacyBarrierValidation(false) from the DXGraphicsContext constructor. Now only EnableDebugLayer() is called when validation layers are enabled. --- sources/Zenith.NET.DirectX12/DXGraphicsContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index e62b7022..478dc5ad 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -61,7 +61,6 @@ protected override void Initialize(bool useValidationLayer, using ComPtr debug6 = new(); if (useValidationLayer && D3D12.GetDebugInterface(SilkMarshal.GuidPtrOf(), (void**)debug6.GetAddressOf()).IsSuccess()) { - debug6.SetForceLegacyBarrierValidation(false); debug6.EnableDebugLayer(); } From d220911acf7623275ea943c9957228326446f111 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 10:18:15 +0800 Subject: [PATCH 144/466] Refactor barrier API and add flexible sync structures Refactored CommandBuffer barrier and synchronization API, replacing MemoryBarrier and AliasingBarrier methods with a unified Barrier method using new MemoryBarrier, BufferBarrier, and TextureBarrier structs. Introduced PipelineStages and ResourceAccess enums for granular control. Renamed TextureState to TextureLayout and updated its members for clarity and consistency. --- sources/Zenith.NET/CommandBuffer.cs | 52 ++++--------------- sources/Zenith.NET/Enums/PipelineStages.cs | 37 +++++++++++++ sources/Zenith.NET/Enums/ResourceAccess.cs | 41 +++++++++++++++ .../{TextureState.cs => TextureLayout.cs} | 12 +++-- sources/Zenith.NET/Structs/BufferBarrier.cs | 18 +++++++ sources/Zenith.NET/Structs/MemoryBarrier.cs | 12 +++++ sources/Zenith.NET/Structs/TextureBarrier.cs | 20 +++++++ 7 files changed, 145 insertions(+), 47 deletions(-) create mode 100644 sources/Zenith.NET/Enums/PipelineStages.cs create mode 100644 sources/Zenith.NET/Enums/ResourceAccess.cs rename sources/Zenith.NET/Enums/{TextureState.cs => TextureLayout.cs} (68%) create mode 100644 sources/Zenith.NET/Structs/BufferBarrier.cs create mode 100644 sources/Zenith.NET/Structs/MemoryBarrier.cs create mode 100644 sources/Zenith.NET/Structs/TextureBarrier.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 36954987..c1e467d5 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -13,44 +13,24 @@ public CommandSubmission Submit(params ReadOnlySpan waits) return queue.Submit(this, waits); } - public void MemoryBarrier() + public void Barrier(MemoryBarrier barrier) { - MemoryBarrierImpl(); + BarrierImpl(new(in barrier), [], []); } - public void MemoryBarrier(Buffer buffer) + public void Barrier(BufferBarrier barrier) { - MemoryBarrierImpl(buffer); + BarrierImpl([], new(in barrier), []); } - public void MemoryBarrier(Texture texture) + public void Barrier(TextureBarrier barrier) { - MemoryBarrierImpl(texture); + BarrierImpl([], [], new(in barrier)); } - public void MemoryBarrier(ReadOnlySpan resources) + public void Barrier(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) { - MemoryBarrierImpl(resources); - } - - public void AliasingBarrier(Buffer buffer) - { - AliasingBarrierImpl(buffer); - } - - public void AliasingBarrier(Texture texture) - { - AliasingBarrierImpl(texture); - } - - public void AliasingBarrier(ReadOnlySpan resources) - { - AliasingBarrierImpl(resources); - } - - public void Transition(Texture texture, TextureSubresource subresource, TextureState state) - { - TransitionImpl(texture, subresource, state); + BarrierImpl(memoryBarriers, bufferBarriers, textureBarriers); } public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) @@ -409,21 +389,7 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip return false; } - protected abstract void MemoryBarrierImpl(); - - protected abstract void MemoryBarrierImpl(Buffer buffer); - - protected abstract void MemoryBarrierImpl(Texture texture); - - protected abstract void MemoryBarrierImpl(ReadOnlySpan resources); - - protected abstract void AliasingBarrierImpl(Buffer buffer); - - protected abstract void AliasingBarrierImpl(Texture texture); - - protected abstract void AliasingBarrierImpl(ReadOnlySpan resources); - - protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureState state); + protected abstract void BarrierImpl(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers); protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes); diff --git a/sources/Zenith.NET/Enums/PipelineStages.cs b/sources/Zenith.NET/Enums/PipelineStages.cs new file mode 100644 index 00000000..3516bac2 --- /dev/null +++ b/sources/Zenith.NET/Enums/PipelineStages.cs @@ -0,0 +1,37 @@ +namespace Zenith.NET; + +[Flags] +public enum PipelineStages +{ + AllCommands = ~0, + + None = 0, + + Indirect = 1 << 0, + + VertexInput = 1 << 1, + + VertexShader = 1 << 2, + + TaskShader = 1 << 3, + + MeshShader = 1 << 4, + + EarlyFragmentTests = 1 << 5, + + FragmentShader = 1 << 6, + + LateFragmentTests = 1 << 7, + + ColorAttachmentOutput = 1 << 8, + + AllGraphics = Indirect | VertexInput | VertexShader | TaskShader | MeshShader | EarlyFragmentTests | FragmentShader | LateFragmentTests | ColorAttachmentOutput, + + ComputeShader = 1 << 9, + + AccelerationStructureBuild = 1 << 10, + + Copy = 1 << 11, + + Resolve = 1 << 12 +} diff --git a/sources/Zenith.NET/Enums/ResourceAccess.cs b/sources/Zenith.NET/Enums/ResourceAccess.cs new file mode 100644 index 00000000..cd63aadc --- /dev/null +++ b/sources/Zenith.NET/Enums/ResourceAccess.cs @@ -0,0 +1,41 @@ +namespace Zenith.NET; + +[Flags] +public enum ResourceAccess +{ + None = 0, + + Vertex = 1 << 0, + + Index = 1 << 1, + + Constant = 1 << 2, + + Indirect = 1 << 3, + + ShaderRead = 1 << 4, + + ShaderWrite = 1 << 5, + + ColorAttachmentRead = 1 << 6, + + ColorAttachmentWrite = 1 << 7, + + DepthStencilAttachmentRead = 1 << 8, + + DepthStencilAttachmentWrite = 1 << 9, + + CopyRead = 1 << 10, + + CopyWrite = 1 << 11, + + ResolveRead = 1 << 12, + + ResolveWrite = 1 << 13, + + AccelerationStructureRead = 1 << 14, + + AccelerationStructureWrite = 1 << 15, + + Present = 1 << 16 +} diff --git a/sources/Zenith.NET/Enums/TextureState.cs b/sources/Zenith.NET/Enums/TextureLayout.cs similarity index 68% rename from sources/Zenith.NET/Enums/TextureState.cs rename to sources/Zenith.NET/Enums/TextureLayout.cs index 10675cd2..c146546c 100644 --- a/sources/Zenith.NET/Enums/TextureState.cs +++ b/sources/Zenith.NET/Enums/TextureLayout.cs @@ -1,10 +1,10 @@ namespace Zenith.NET; -public enum TextureState +public enum TextureLayout { - CopySrc, + Undefined, - CopyDst, + General, Sampled, @@ -12,10 +12,14 @@ public enum TextureState ColorAttachment, - DepthStencil, + DepthStencilAttachment, DepthStencilReadOnly, + CopySrc, + + CopyDst, + ResolveSrc, ResolveDst, diff --git a/sources/Zenith.NET/Structs/BufferBarrier.cs b/sources/Zenith.NET/Structs/BufferBarrier.cs new file mode 100644 index 00000000..384a9d6c --- /dev/null +++ b/sources/Zenith.NET/Structs/BufferBarrier.cs @@ -0,0 +1,18 @@ +namespace Zenith.NET; + +public struct BufferBarrier +{ + public Buffer Buffer; + + public ulong OffsetInBytes; + + public ulong SizeInBytes; + + public PipelineStages SrcStages; + + public PipelineStages DstStages; + + public ResourceAccess SrcAccess; + + public ResourceAccess DstAccess; +} diff --git a/sources/Zenith.NET/Structs/MemoryBarrier.cs b/sources/Zenith.NET/Structs/MemoryBarrier.cs new file mode 100644 index 00000000..6651e5a0 --- /dev/null +++ b/sources/Zenith.NET/Structs/MemoryBarrier.cs @@ -0,0 +1,12 @@ +namespace Zenith.NET; + +public struct MemoryBarrier +{ + public PipelineStages SrcStages; + + public PipelineStages DstStages; + + public ResourceAccess SrcAccess; + + public ResourceAccess DstAccess; +} diff --git a/sources/Zenith.NET/Structs/TextureBarrier.cs b/sources/Zenith.NET/Structs/TextureBarrier.cs new file mode 100644 index 00000000..77e0cb8b --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureBarrier.cs @@ -0,0 +1,20 @@ +namespace Zenith.NET; + +public struct TextureBarrier +{ + public Texture Texture; + + public TextureSubresourceRange Range; + + public PipelineStages SrcStages; + + public PipelineStages DstStages; + + public ResourceAccess SrcAccess; + + public ResourceAccess DstAccess; + + public TextureLayout SrcLayout; + + public TextureLayout DstLayout; +} From 8a05c6ca5a939e87fb00fd9a08fa21632d2398be Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 10:21:19 +0800 Subject: [PATCH 145/466] Swap bit flag values for Constant and Indirect in ResourceAccess Swapped the bit flag assignments for the Constant and Indirect enum members in ResourceAccess. Indirect is now 1 << 2 and Constant is 1 << 3, correcting their previous values. --- sources/Zenith.NET/Enums/ResourceAccess.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET/Enums/ResourceAccess.cs b/sources/Zenith.NET/Enums/ResourceAccess.cs index cd63aadc..5956a97c 100644 --- a/sources/Zenith.NET/Enums/ResourceAccess.cs +++ b/sources/Zenith.NET/Enums/ResourceAccess.cs @@ -9,9 +9,9 @@ public enum ResourceAccess Index = 1 << 1, - Constant = 1 << 2, + Indirect = 1 << 2, - Indirect = 1 << 3, + Constant = 1 << 3, ShaderRead = 1 << 4, From 0e141fd07a17638840db0232496bbbf1858eeb4c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 10:26:51 +0800 Subject: [PATCH 146/466] Refactor ResourceAccess enum bit positions and order Reordered and reassigned bit positions for ResourceAccess enum members. Moved AccelerationStructureRead/Write to lower bits and shifted other access types to higher positions to improve clarity and maintainability. --- sources/Zenith.NET/Enums/ResourceAccess.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sources/Zenith.NET/Enums/ResourceAccess.cs b/sources/Zenith.NET/Enums/ResourceAccess.cs index 5956a97c..0fb794e9 100644 --- a/sources/Zenith.NET/Enums/ResourceAccess.cs +++ b/sources/Zenith.NET/Enums/ResourceAccess.cs @@ -17,25 +17,25 @@ public enum ResourceAccess ShaderWrite = 1 << 5, - ColorAttachmentRead = 1 << 6, + AccelerationStructureRead = 1 << 6, - ColorAttachmentWrite = 1 << 7, + AccelerationStructureWrite = 1 << 7, - DepthStencilAttachmentRead = 1 << 8, + ColorAttachmentRead = 1 << 8, - DepthStencilAttachmentWrite = 1 << 9, + ColorAttachmentWrite = 1 << 9, - CopyRead = 1 << 10, + DepthStencilAttachmentRead = 1 << 10, - CopyWrite = 1 << 11, + DepthStencilAttachmentWrite = 1 << 11, - ResolveRead = 1 << 12, + CopyRead = 1 << 12, - ResolveWrite = 1 << 13, + CopyWrite = 1 << 13, - AccelerationStructureRead = 1 << 14, + ResolveRead = 1 << 14, - AccelerationStructureWrite = 1 << 15, + ResolveWrite = 1 << 15, Present = 1 << 16 } From 0febe62e5f79208f3a3e23197a50ddfe4a102916 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 10:29:15 +0800 Subject: [PATCH 147/466] Update TextureLayout usage with DxTextureLayout alias Replaced TextureLayout.LayoutRowMajor with DxTextureLayout.LayoutRowMajor in DXBuffer. Added a global using alias for Silk.NET.Direct3D12.TextureLayout to Usings.cs for consistency and clarity. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 2 +- sources/Zenith.NET.DirectX12/Usings.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index d38f220f..537ed82b 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -92,7 +92,7 @@ public static ResourceDesc1 ResourceDesc(BufferDesc desc) DepthOrArraySize = 1, MipLevels = 1, SampleDesc = new(1), - Layout = TextureLayout.LayoutRowMajor, + Layout = DxTextureLayout.LayoutRowMajor, Flags = DXFormats.DirectX12(desc.Usages) }; } diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 89b08cf3..e97ab9cc 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -7,3 +7,4 @@ global using DxQueryHeapDesc = Silk.NET.Direct3D12.QueryHeapDesc; global using DxRange = Silk.NET.Direct3D12.Range; global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; +global using DxTextureLayout = Silk.NET.Direct3D12.TextureLayout; From fea6bfa51f162d21e7c4281ba582ca035875c001 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 17:10:36 +0800 Subject: [PATCH 148/466] Refactor buffer and texture usage flags for clarity Renamed and reordered BufferUsages and TextureUsages enums for improved clarity and consistency. Updated all descriptor factory methods and usages to match new flag names. Renamed RenderTarget2D/DepthStencil2D to ColorAttachment/DepthStencilAttachment. Updated DXFormats to use new DepthStencilAttachment flag. These changes align terminology with modern graphics APIs and enhance maintainability. --- .../Renderers/PathTracingRenderer.cs | 4 ++-- .../CornellBox/Renderers/Renderer.cs | 2 +- sources/Zenith.NET.DirectX12/DXFormats.cs | 2 +- sources/Zenith.NET/Enums/BufferUsages.cs | 18 ++++++++--------- sources/Zenith.NET/Enums/TextureUsages.cs | 12 +++++------ sources/Zenith.NET/Structs/BufferDesc.cs | 12 +++++------ sources/Zenith.NET/Structs/TextureDesc.cs | 20 +++++++++---------- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index c99b7ced..8a22c5cf 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -33,7 +33,7 @@ public PathTracingRenderer() SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), StrideInBytes = (uint)sizeof(Vertex), Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure + Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst }); fixed (Vertex* pointer = vertices) @@ -46,7 +46,7 @@ public PathTracingRenderer() SizeInBytes = (uint)(sizeof(uint) * indices.Length), StrideInBytes = sizeof(uint), Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure + Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst }); fixed (uint* pointer = indices) diff --git a/sources/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Experiments/CornellBox/Renderers/Renderer.cs index add22c90..d85af918 100644 --- a/sources/Experiments/CornellBox/Renderers/Renderer.cs +++ b/sources/Experiments/CornellBox/Renderers/Renderer.cs @@ -53,7 +53,7 @@ public virtual void Resize(uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.DepthStencil + Usages = TextureUsages.DepthStencilAttachment }); } diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 44fe365c..5c9c002b 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -173,7 +173,7 @@ public static ResourceFlags DirectX12(TextureUsages textureUsages) result |= ResourceFlags.AllowRenderTarget; } - if (textureUsages.HasFlag(TextureUsages.DepthStencil)) + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { result |= ResourceFlags.AllowDepthStencil; } diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs index 3517ea4f..bfba8e3d 100644 --- a/sources/Zenith.NET/Enums/BufferUsages.cs +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -5,21 +5,21 @@ public enum BufferUsages { None = 0, - CopySrc = 1 << 0, + Vertex = 1 << 0, - CopyDst = 1 << 1, + Index = 1 << 1, - Constant = 1 << 2, + Indirect = 1 << 2, - Vertex = 1 << 3, + Constant = 1 << 3, - Index = 1 << 4, + StorageReadOnly = 1 << 4, - StorageReadOnly = 1 << 5, + StorageReadWrite = 1 << 5, - StorageReadWrite = 1 << 6, + AccelerationStructure = 1 << 6, - Indirect = 1 << 7, + CopySrc = 1 << 7, - AccelerationStructure = 1 << 8 + CopyDst = 1 << 8 } diff --git a/sources/Zenith.NET/Enums/TextureUsages.cs b/sources/Zenith.NET/Enums/TextureUsages.cs index 2da4da15..c098c78f 100644 --- a/sources/Zenith.NET/Enums/TextureUsages.cs +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -5,15 +5,15 @@ public enum TextureUsages { None = 0, - CopySrc = 1 << 0, + Sampled = 1 << 0, - CopyDst = 1 << 1, + Storage = 1 << 1, - Sampled = 1 << 2, + ColorAttachment = 1 << 2, - Storage = 1 << 3, + DepthStencilAttachment = 1 << 3, - ColorAttachment = 1 << 4, + CopySrc = 1 << 4, - DepthStencil = 1 << 5 + CopyDst = 1 << 5 } diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index 81bff8de..b7763424 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -17,7 +17,7 @@ public static BufferDesc Vertex(uint sizeInBytes) SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.Vertex + Usages = BufferUsages.Vertex | BufferUsages.CopyDst }; } @@ -28,7 +28,7 @@ public static BufferDesc Index(uint sizeInBytes) SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.Index + Usages = BufferUsages.Index | BufferUsages.CopyDst }; } @@ -39,7 +39,7 @@ public static BufferDesc Constant(uint sizeInBytes) SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.Constant + Usages = BufferUsages.Constant | BufferUsages.CopyDst }; } @@ -50,7 +50,7 @@ public static BufferDesc StorageReadOnly(uint sizeInBytes, uint strideInBytes) SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.StorageReadOnly + Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst }; } @@ -61,7 +61,7 @@ public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.StorageReadWrite + Usages = BufferUsages.StorageReadWrite | BufferUsages.CopyDst }; } @@ -72,7 +72,7 @@ public static BufferDesc Indirect(uint sizeInBytes) SizeInBytes = sizeInBytes, StrideInBytes = 0, Access = BufferAccess.GpuOnly, - Usages = BufferUsages.CopyDst | BufferUsages.Indirect + Usages = BufferUsages.Indirect | BufferUsages.CopyDst }; } diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index add3d42a..b7bd69b5 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -32,7 +32,7 @@ public static TextureDesc Texture1D(PixelFormat format, uint width, uint mipLeve MipLevels = mipLevels, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } @@ -48,7 +48,7 @@ public static TextureDesc Texture1DArray(PixelFormat format, uint width, uint ar MipLevels = mipLevels, ArrayLayers = arrayLayers, SampleCount = SampleCount.Count1, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } @@ -64,7 +64,7 @@ public static TextureDesc Texture2D(PixelFormat format, uint width, uint height, MipLevels = mipLevels, ArrayLayers = 1, SampleCount = sampleCount, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } @@ -80,7 +80,7 @@ public static TextureDesc Texture2DArray(PixelFormat format, uint width, uint he MipLevels = mipLevels, ArrayLayers = arrayLayers, SampleCount = sampleCount, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } @@ -96,7 +96,7 @@ public static TextureDesc Texture3D(PixelFormat format, uint width, uint height, MipLevels = mipLevels, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } @@ -112,7 +112,7 @@ public static TextureDesc TextureCube(PixelFormat format, uint size, uint mipLev MipLevels = mipLevels, ArrayLayers = 6, SampleCount = SampleCount.Count1, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } @@ -128,11 +128,11 @@ public static TextureDesc TextureCubeArray(PixelFormat format, uint size, uint c MipLevels = mipLevels, ArrayLayers = 6 * cubeCount, SampleCount = SampleCount.Count1, - Usages = TextureUsages.CopyDst | TextureUsages.Sampled + Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; } - public static TextureDesc RenderTarget2D(PixelFormat format, uint width, uint height, uint mipLevels, SampleCount sampleCount) + public static TextureDesc ColorAttachment(PixelFormat format, uint width, uint height, uint mipLevels, SampleCount sampleCount) { return new() { @@ -148,7 +148,7 @@ public static TextureDesc RenderTarget2D(PixelFormat format, uint width, uint he }; } - public static TextureDesc DepthStencil2D(PixelFormat format, uint width, uint height, SampleCount sampleCount) + public static TextureDesc DepthStencilAttachment(PixelFormat format, uint width, uint height, SampleCount sampleCount) { return new() { @@ -160,7 +160,7 @@ public static TextureDesc DepthStencil2D(PixelFormat format, uint width, uint he MipLevels = 1, ArrayLayers = 1, SampleCount = sampleCount, - Usages = TextureUsages.Sampled | TextureUsages.DepthStencil + Usages = TextureUsages.Sampled | TextureUsages.DepthStencilAttachment }; } } From b4057c104ab49d8062afbd58490e3d1b992899da Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 17:17:35 +0800 Subject: [PATCH 149/466] Update barrier handling and clean up render pass setup Refactor CommandBuffer barrier overloads to use array syntax for single barriers instead of 'new(in ...)' ReadOnlySpan construction. Remove redundant render target and render pass setup lines from ImGuiRenderer.cs. --- .../Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 2 -- sources/Zenith.NET/CommandBuffer.cs | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 8d146f57..fe009f53 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -357,8 +357,6 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, commandBuffer.BeginDebugEvent("ImGui"); - ColorAttachment renderTarget = colorAttachment; - commandBuffer.BeginRenderPass([colorAttachment], null); commandBuffer.SetPipeline(graphicsPipeline); diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index c1e467d5..d25f599a 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -15,17 +15,17 @@ public CommandSubmission Submit(params ReadOnlySpan waits) public void Barrier(MemoryBarrier barrier) { - BarrierImpl(new(in barrier), [], []); + BarrierImpl([barrier], [], []); } public void Barrier(BufferBarrier barrier) { - BarrierImpl([], new(in barrier), []); + BarrierImpl([], [barrier], []); } public void Barrier(TextureBarrier barrier) { - BarrierImpl([], [], new(in barrier)); + BarrierImpl([], [], [barrier]); } public void Barrier(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) From b1813ca9c5db09b5e467f389bfa1b90f281aed7e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 17:27:14 +0800 Subject: [PATCH 150/466] Remove single-barrier overloads; fix QueryHeapDesc factories Removed single-argument Barrier overloads from CommandBuffer, keeping only the span-based version. Reordered and corrected QueryHeapDesc static factory methods to match their intended QueryType values. --- sources/Zenith.NET/CommandBuffer.cs | 15 --------------- sources/Zenith.NET/Structs/QueryHeapDesc.cs | 12 ++++++------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index d25f599a..be6114cb 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -13,21 +13,6 @@ public CommandSubmission Submit(params ReadOnlySpan waits) return queue.Submit(this, waits); } - public void Barrier(MemoryBarrier barrier) - { - BarrierImpl([barrier], [], []); - } - - public void Barrier(BufferBarrier barrier) - { - BarrierImpl([], [barrier], []); - } - - public void Barrier(TextureBarrier barrier) - { - BarrierImpl([], [], [barrier]); - } - public void Barrier(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) { BarrierImpl(memoryBarriers, bufferBarriers, textureBarriers); diff --git a/sources/Zenith.NET/Structs/QueryHeapDesc.cs b/sources/Zenith.NET/Structs/QueryHeapDesc.cs index b1299377..23c6b4c6 100644 --- a/sources/Zenith.NET/Structs/QueryHeapDesc.cs +++ b/sources/Zenith.NET/Structs/QueryHeapDesc.cs @@ -6,29 +6,29 @@ public struct QueryHeapDesc public uint Count; - public static QueryHeapDesc Timestamp(uint count) + public static QueryHeapDesc Occlusion(uint count) { return new() { - Type = QueryType.Timestamp, + Type = QueryType.Occlusion, Count = count }; } - public static QueryHeapDesc Occlusion(uint count) + public static QueryHeapDesc BinaryOcclusion(uint count) { return new() { - Type = QueryType.Occlusion, + Type = QueryType.BinaryOcclusion, Count = count }; } - public static QueryHeapDesc BinaryOcclusion(uint count) + public static QueryHeapDesc Timestamp(uint count) { return new() { - Type = QueryType.BinaryOcclusion, + Type = QueryType.Timestamp, Count = count }; } From 954f9f77efb83687ca2305b5a4d0b3f700c0f102 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 28 May 2026 18:07:10 +0800 Subject: [PATCH 151/466] Refactor DXCommandQueue to use explicit fields and fence Refactored DXCommandQueue to replace the primary constructor with explicit fields and a standard constructor. Added public fields for CommandQueue and Fence1, initializing both in the constructor. Updated SetResourceName to use the new CommandQueue field. --- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 19c78631..58d0467c 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -3,8 +3,19 @@ namespace Zenith.NET.DirectX12; -internal class DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr commandQueue) : CommandQueue(context, type) +internal unsafe class DXCommandQueue : CommandQueue { + public ComPtr CommandQueue; + + public ComPtr Fence1; + + public DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr commandQueue) : base(context, type) + { + CommandQueue = commandQueue; + + context.Device14.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence1.GetAddressOf()).Success(); + } + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -32,6 +43,13 @@ protected override void WaitImpl(ulong waitValue) protected override void SetResourceName(string name) { - commandQueue.SetName(name).Success(); + CommandQueue.SetName(name).Success(); + } + + protected override void Destroy() + { + base.Destroy(); + + Fence1.Dispose(); } } From 43e0463e71946484162a3a393da942c3ca310389 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 29 May 2026 14:43:43 +0800 Subject: [PATCH 152/466] Refactor enum/property names for clarity and consistency Renamed properties in Capabilities (RayTracing/MeshShading) to RayTracingSupported/MeshShadingSupported and updated all usages. Shortened BlendFactor enum values and updated references. Renamed RayTracingInstanceFlags values for clarity. Removed redundant closing braces from several files. No functional changes; focused on naming and code style improvements. --- sources/Experiments/CornellBox/App.cs | 4 ++-- .../Experiments/PlatformDetection/Program.cs | 4 ++-- sources/Zenith.NET.DirectX12/DXCapabilities.cs | 8 ++++---- sources/Zenith.NET.DirectX12/DXFormats.cs | 16 ++++++++-------- sources/Zenith.NET/Capabilities.cs | 4 ++-- sources/Zenith.NET/Enums/BlendFactor.cs | 16 ++++++++-------- sources/Zenith.NET/Enums/BlendOp.cs | 2 +- sources/Zenith.NET/Enums/CompareOp.cs | 2 +- sources/Zenith.NET/Enums/ElementFormat.cs | 2 +- sources/Zenith.NET/Enums/LoadOp.cs | 2 +- .../Enums/RayTracingInstanceFlags.cs | 4 ++-- sources/Zenith.NET/Enums/ShaderStages.cs | 2 +- sources/Zenith.NET/Enums/StencilOp.cs | 2 +- sources/Zenith.NET/Enums/StoreOp.cs | 2 +- sources/Zenith.NET/Structs/BufferData.cs | 2 +- .../Structs/ColorAttachmentBlendState.cs | 18 +++++++++--------- sources/Zenith.NET/Structs/Offset3D.cs | 2 +- .../Structs/RayTracingAabbGeometry.cs | 2 +- .../Structs/RayTracingTriangleGeometry.cs | 2 +- sources/Zenith.NET/Structs/RenderState.cs | 2 +- sources/Zenith.NET/Structs/TextureData.cs | 2 +- .../Zenith.NET/Structs/TextureDataLayout.cs | 2 +- .../Zenith.NET/ValidationMessageEventArgs.cs | 2 +- sources/Zenith.NET/ZenithMarshal.cs | 2 +- 24 files changed, 53 insertions(+), 53 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 74872698..d47cc9ab 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -82,7 +82,7 @@ static App() rasterizer = new(); - if (Context.Capabilities.RayTracing) + if (Context.Capabilities.RayTracingSupported) { activeRenderer = pathTracer = new(); currentMode = 0; @@ -131,7 +131,7 @@ public static void Run() ImGui.Text("Render Mode:"); - if (Context.Capabilities.RayTracing) + if (Context.Capabilities.RayTracingSupported) { if (ImGui.RadioButton("Path Tracing", currentMode is 0) && currentMode is not 0) { diff --git a/sources/Experiments/PlatformDetection/Program.cs b/sources/Experiments/PlatformDetection/Program.cs index b578e3e4..43018cd8 100644 --- a/sources/Experiments/PlatformDetection/Program.cs +++ b/sources/Experiments/PlatformDetection/Program.cs @@ -17,8 +17,8 @@ Console.WriteLine($"GraphicsApi {graphicsApi} is supported."); Console.WriteLine($" Device Name: {context.Capabilities.DeviceName}"); - Console.WriteLine($" Ray Tracing Supported: {context.Capabilities.RayTracing}"); - Console.WriteLine($" Mesh Shading Supported: {context.Capabilities.MeshShading}"); + Console.WriteLine($" Ray Tracing Supported: {context.Capabilities.RayTracingSupported}"); + Console.WriteLine($" Mesh Shading Supported: {context.Capabilities.MeshShadingSupported}"); } catch (Exception) { diff --git a/sources/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Zenith.NET.DirectX12/DXCapabilities.cs index adb2f265..dd9d8962 100644 --- a/sources/Zenith.NET.DirectX12/DXCapabilities.cs +++ b/sources/Zenith.NET.DirectX12/DXCapabilities.cs @@ -18,13 +18,13 @@ public DXCapabilities(DXGraphicsContext context) context.Device14.CheckFeatureSupport(Feature.D3D12Options7, &options7, (uint)sizeof(FeatureDataD3D12Options7)).Success(); DeviceName = ZenithMarshal.StringFromPointer((nint)desc.Description, StringEncoding.UTF16); - RayTracing = options5.RaytracingTier >= RaytracingTier.Tier11; - MeshShading = options7.MeshShaderTier >= MeshShaderTier.Tier1; + RayTracingSupported = options5.RaytracingTier >= RaytracingTier.Tier11; + MeshShadingSupported = options7.MeshShaderTier >= MeshShaderTier.Tier1; } public override string DeviceName { get; } - public override bool RayTracing { get; } + public override bool RayTracingSupported { get; } - public override bool MeshShading { get; } + public override bool MeshShadingSupported { get; } } diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 5c9c002b..305669f1 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -369,14 +369,14 @@ public static Blend DirectX12(BlendFactor blendFactor) { BlendFactor.Zero => Blend.Zero, BlendFactor.One => Blend.One, - BlendFactor.SourceColor => Blend.SrcColor, - BlendFactor.OneMinusSourceColor => Blend.InvSrcColor, - BlendFactor.DestinationColor => Blend.DestColor, - BlendFactor.OneMinusDestinationColor => Blend.InvDestColor, - BlendFactor.SourceAlpha => Blend.SrcAlpha, - BlendFactor.OneMinusSourceAlpha => Blend.InvSrcAlpha, - BlendFactor.DestinationAlpha => Blend.DestAlpha, - BlendFactor.OneMinusDestinationAlpha => Blend.InvDestAlpha, + BlendFactor.SrcColor => Blend.SrcColor, + BlendFactor.OneMinusSrcColor => Blend.InvSrcColor, + BlendFactor.DstColor => Blend.DestColor, + BlendFactor.OneMinusDstColor => Blend.InvDestColor, + BlendFactor.SrcAlpha => Blend.SrcAlpha, + BlendFactor.OneMinusSrcAlpha => Blend.InvSrcAlpha, + BlendFactor.DstAlpha => Blend.DestAlpha, + BlendFactor.OneMinusDstAlpha => Blend.InvDestAlpha, BlendFactor.Constant => Blend.BlendFactor, BlendFactor.OneMinusConstant => Blend.InvBlendFactor, _ => default diff --git a/sources/Zenith.NET/Capabilities.cs b/sources/Zenith.NET/Capabilities.cs index 4c5f396f..49070ed8 100644 --- a/sources/Zenith.NET/Capabilities.cs +++ b/sources/Zenith.NET/Capabilities.cs @@ -4,7 +4,7 @@ public abstract class Capabilities { public abstract string DeviceName { get; } - public abstract bool RayTracing { get; } + public abstract bool RayTracingSupported { get; } - public abstract bool MeshShading { get; } + public abstract bool MeshShadingSupported { get; } } diff --git a/sources/Zenith.NET/Enums/BlendFactor.cs b/sources/Zenith.NET/Enums/BlendFactor.cs index dc293852..60bf9244 100644 --- a/sources/Zenith.NET/Enums/BlendFactor.cs +++ b/sources/Zenith.NET/Enums/BlendFactor.cs @@ -6,21 +6,21 @@ public enum BlendFactor One, - SourceColor, + SrcColor, - OneMinusSourceColor, + OneMinusSrcColor, - DestinationColor, + DstColor, - OneMinusDestinationColor, + OneMinusDstColor, - SourceAlpha, + SrcAlpha, - OneMinusSourceAlpha, + OneMinusSrcAlpha, - DestinationAlpha, + DstAlpha, - OneMinusDestinationAlpha, + OneMinusDstAlpha, Constant, diff --git a/sources/Zenith.NET/Enums/BlendOp.cs b/sources/Zenith.NET/Enums/BlendOp.cs index 133edfbb..e0f30065 100644 --- a/sources/Zenith.NET/Enums/BlendOp.cs +++ b/sources/Zenith.NET/Enums/BlendOp.cs @@ -11,4 +11,4 @@ public enum BlendOp Min, Max -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/CompareOp.cs b/sources/Zenith.NET/Enums/CompareOp.cs index f24edc29..f5d0426a 100644 --- a/sources/Zenith.NET/Enums/CompareOp.cs +++ b/sources/Zenith.NET/Enums/CompareOp.cs @@ -17,4 +17,4 @@ public enum CompareOp GreaterEqual, Always -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/ElementFormat.cs b/sources/Zenith.NET/Enums/ElementFormat.cs index eff6c589..9587dc36 100644 --- a/sources/Zenith.NET/Enums/ElementFormat.cs +++ b/sources/Zenith.NET/Enums/ElementFormat.cs @@ -79,4 +79,4 @@ public enum ElementFormat Int3, Int4 -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/LoadOp.cs b/sources/Zenith.NET/Enums/LoadOp.cs index ccf14d19..63d41e53 100644 --- a/sources/Zenith.NET/Enums/LoadOp.cs +++ b/sources/Zenith.NET/Enums/LoadOp.cs @@ -7,4 +7,4 @@ public enum LoadOp Clear, DontCare -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs index c4af153a..01c609bc 100644 --- a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs +++ b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs @@ -7,9 +7,9 @@ public enum RayTracingInstanceFlags FrontCounterClockwise = 1 << 0, - CullDisable = 1 << 1, + DisableCull = 1 << 1, ForceOpaque = 1 << 2, - ForceNoOpaque = 1 << 3 + ForceNonOpaque = 1 << 3 } diff --git a/sources/Zenith.NET/Enums/ShaderStages.cs b/sources/Zenith.NET/Enums/ShaderStages.cs index 38f94a19..2b736051 100644 --- a/sources/Zenith.NET/Enums/ShaderStages.cs +++ b/sources/Zenith.NET/Enums/ShaderStages.cs @@ -14,4 +14,4 @@ public enum ShaderStages Task = 1 << 3, Mesh = 1 << 4 -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/StencilOp.cs b/sources/Zenith.NET/Enums/StencilOp.cs index 1b9ff58d..f5490e38 100644 --- a/sources/Zenith.NET/Enums/StencilOp.cs +++ b/sources/Zenith.NET/Enums/StencilOp.cs @@ -17,4 +17,4 @@ public enum StencilOp IncrementAndWrap, DecrementAndWrap -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/StoreOp.cs b/sources/Zenith.NET/Enums/StoreOp.cs index 55a8eb45..4d7b4033 100644 --- a/sources/Zenith.NET/Enums/StoreOp.cs +++ b/sources/Zenith.NET/Enums/StoreOp.cs @@ -5,4 +5,4 @@ public enum StoreOp Store, DontCare -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/BufferData.cs b/sources/Zenith.NET/Structs/BufferData.cs index cf8a128f..c5987655 100644 --- a/sources/Zenith.NET/Structs/BufferData.cs +++ b/sources/Zenith.NET/Structs/BufferData.cs @@ -5,4 +5,4 @@ public struct BufferData public nint Pointer; public uint SizeInBytes; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs index 6eff8d3b..d951590c 100644 --- a/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -39,10 +39,10 @@ public static ColorAttachmentBlendState AlphaBlend() { IsBlendingEnabled = true, SrcRgbFactor = BlendFactor.One, - DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + DstRgbFactor = BlendFactor.OneMinusSrcAlpha, RgbOp = BlendOp.Add, SrcAlphaFactor = BlendFactor.One, - DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, + DstAlphaFactor = BlendFactor.OneMinusSrcAlpha, AlphaOp = BlendOp.Add, ColorWrites = ColorWrites.All }; @@ -53,10 +53,10 @@ public static ColorAttachmentBlendState Additive() return new() { IsBlendingEnabled = true, - SrcRgbFactor = BlendFactor.SourceAlpha, + SrcRgbFactor = BlendFactor.SrcAlpha, DstRgbFactor = BlendFactor.One, RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.SourceAlpha, + SrcAlphaFactor = BlendFactor.SrcAlpha, DstAlphaFactor = BlendFactor.One, AlphaOp = BlendOp.Add, ColorWrites = ColorWrites.All @@ -68,11 +68,11 @@ public static ColorAttachmentBlendState NonPremultiplied() return new() { IsBlendingEnabled = true, - SrcRgbFactor = BlendFactor.SourceAlpha, - DstRgbFactor = BlendFactor.OneMinusSourceAlpha, + SrcRgbFactor = BlendFactor.SrcAlpha, + DstRgbFactor = BlendFactor.OneMinusSrcAlpha, RgbOp = BlendOp.Add, - SrcAlphaFactor = BlendFactor.SourceAlpha, - DstAlphaFactor = BlendFactor.OneMinusSourceAlpha, + SrcAlphaFactor = BlendFactor.SrcAlpha, + DstAlphaFactor = BlendFactor.OneMinusSrcAlpha, AlphaOp = BlendOp.Add, ColorWrites = ColorWrites.All }; @@ -92,4 +92,4 @@ public static ColorAttachmentBlendState ColorDisabled() ColorWrites = ColorWrites.None }; } -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/Offset3D.cs b/sources/Zenith.NET/Structs/Offset3D.cs index 8134c7bf..f4a0adb0 100644 --- a/sources/Zenith.NET/Structs/Offset3D.cs +++ b/sources/Zenith.NET/Structs/Offset3D.cs @@ -7,4 +7,4 @@ public struct Offset3D public uint Y; public uint Z; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs index db7966aa..2da0bd7b 100644 --- a/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs @@ -9,4 +9,4 @@ public struct RayTracingAabbGeometry public uint StrideInBytes; public uint OffsetInBytes; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index bc2f35bb..7499cd72 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -23,4 +23,4 @@ public struct RayTracingTriangleGeometry public uint IndexOffsetInBytes; public Matrix4x4 Transform; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/RenderState.cs b/sources/Zenith.NET/Structs/RenderState.cs index 89b68199..abc71741 100644 --- a/sources/Zenith.NET/Structs/RenderState.cs +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -7,4 +7,4 @@ public struct RenderState public DepthStencilState DepthStencilState; public BlendState BlendState; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/TextureData.cs b/sources/Zenith.NET/Structs/TextureData.cs index 47b6c8aa..9f4a7c5d 100644 --- a/sources/Zenith.NET/Structs/TextureData.cs +++ b/sources/Zenith.NET/Structs/TextureData.cs @@ -5,4 +5,4 @@ public struct TextureData public nint Pointer; public TextureDataLayout Layout; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Structs/TextureDataLayout.cs b/sources/Zenith.NET/Structs/TextureDataLayout.cs index cabb3834..3121f013 100644 --- a/sources/Zenith.NET/Structs/TextureDataLayout.cs +++ b/sources/Zenith.NET/Structs/TextureDataLayout.cs @@ -7,4 +7,4 @@ public struct TextureDataLayout public uint RowStrideInBytes; public uint SliceStrideInBytes; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/ValidationMessageEventArgs.cs b/sources/Zenith.NET/ValidationMessageEventArgs.cs index 13f78db3..53b0f1f5 100644 --- a/sources/Zenith.NET/ValidationMessageEventArgs.cs +++ b/sources/Zenith.NET/ValidationMessageEventArgs.cs @@ -7,4 +7,4 @@ public class ValidationMessageEventArgs(MessageSeverity severity, string message public string Message { get; } = message; public DateTimeOffset Timestamp { get; } = DateTimeOffset.UtcNow; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/ZenithMarshal.cs b/sources/Zenith.NET/ZenithMarshal.cs index a601e85c..a773221f 100644 --- a/sources/Zenith.NET/ZenithMarshal.cs +++ b/sources/Zenith.NET/ZenithMarshal.cs @@ -87,4 +87,4 @@ public static string[] StringArrayFromPointer(nint pointer, uint length, StringE return strings; } -} \ No newline at end of file +} From a1661210db8571c3ee128d5b45eaf71cc39ab5a4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 29 May 2026 17:15:14 +0800 Subject: [PATCH 153/466] Refactor buffer/heap access to MemoryResidency model Replace BufferAccess and HeapType with new MemoryResidency enum (GpuOnly, CpuReadOnly, CpuWriteOnly) across buffer and heap creation. Update DirectX mapping logic and add Residency fields to BufferDesc and HeapDesc. Remove obsolete enums. Add static helpers for common barrier/range creation. Rename Binormal to Bitangent in ElementSemantic and expand NativeObjectType. --- .../Renderers/PathTracingRenderer.cs | 16 +- .../Renderers/RasterizationRenderer.cs | 4 +- .../ImGuiRenderer.cs | 12 +- sources/Zenith.NET.DirectX12/DXBuffer.cs | 2 +- sources/Zenith.NET.DirectX12/DXFormats.cs | 21 +-- sources/Zenith.NET.DirectX12/DXHeap.cs | 2 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 2 +- sources/Zenith.NET/Buffer.cs | 4 +- sources/Zenith.NET/Downloader.cs | 4 +- sources/Zenith.NET/Enums/ElementSemantic.cs | 2 +- sources/Zenith.NET/Enums/HeapType.cs | 10 -- .../{BufferAccess.cs => MemoryResidency.cs} | 2 +- sources/Zenith.NET/Enums/NativeObjectType.cs | 15 ++ sources/Zenith.NET/Structs/BufferBarrier.cs | 112 ++++++++++++- sources/Zenith.NET/Structs/BufferDesc.cs | 45 ++++-- sources/Zenith.NET/Structs/HeapDesc.cs | 31 +++- sources/Zenith.NET/Structs/MemoryBarrier.cs | 11 ++ sources/Zenith.NET/Structs/TextureBarrier.cs | 150 ++++++++++++++++++ .../Structs/TextureSubresourceRange.cs | 11 ++ sources/Zenith.NET/Uploader.cs | 4 +- 20 files changed, 384 insertions(+), 76 deletions(-) delete mode 100644 sources/Zenith.NET/Enums/HeapType.cs rename sources/Zenith.NET/Enums/{BufferAccess.cs => MemoryResidency.cs} (73%) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 8a22c5cf..ca303177 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -32,8 +32,8 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), StrideInBytes = (uint)sizeof(Vertex), - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst + Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }); fixed (Vertex* pointer = vertices) @@ -45,8 +45,8 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(uint) * indices.Length), StrideInBytes = sizeof(uint), - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst + Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }); fixed (uint* pointer = indices) @@ -57,8 +57,8 @@ public PathTracingRenderer() constantBuffer = App.Context.CreateBuffer(new() { SizeInBytes = (uint)sizeof(PathTracingConstants), - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Constant + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly }); using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); @@ -113,8 +113,8 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Material) * materials.Length), StrideInBytes = (uint)sizeof(Material), - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly + Usages = BufferUsages.StorageReadOnly, + Residency = MemoryResidency.GpuOnly }); fixed (Material* pointer = materials) diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 561e6658..e3fb27f3 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -41,8 +41,8 @@ public RasterizationRenderer() constantBuffer = App.Context.CreateBuffer(new() { SizeInBytes = (uint)sizeof(RasterizationConstants), - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Constant + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly }); InputLayout inputLayout = new(); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index fe009f53..600c5f63 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -290,8 +290,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, vertexBuffer = Context.CreateBuffer(new() { SizeInBytes = totalVertexSizeInBytes, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Vertex + Usages = BufferUsages.Vertex, + Residency = MemoryResidency.CpuWriteOnly }); } @@ -302,8 +302,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, indexBuffer = Context.CreateBuffer(new() { SizeInBytes = totalIndexSizeInBytes, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Index + Usages = BufferUsages.Index, + Residency = MemoryResidency.CpuWriteOnly }); } @@ -314,8 +314,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, constantBuffer = Context.CreateBuffer(new() { SizeInBytes = totalConstantSizeInBytes, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.Constant + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly }); } diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 537ed82b..565f23a4 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -16,7 +16,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr DxHeapType.Default, - HeapType.CpuReadOnly => DxHeapType.Readback, - HeapType.CpuWriteOnly => DxHeapType.Upload, + MemoryResidency.GpuOnly => DxHeapType.Default, + MemoryResidency.CpuReadOnly => DxHeapType.Readback, + MemoryResidency.CpuWriteOnly => DxHeapType.Upload, _ => default }; } @@ -33,17 +33,6 @@ public static ResourceFlags DirectX12(BufferUsages bufferUsages) return result; } - public static DxHeapType DirectX12(BufferAccess bufferAccess) - { - return bufferAccess switch - { - BufferAccess.GpuOnly => DxHeapType.Default, - BufferAccess.CpuReadOnly => DxHeapType.Readback, - BufferAccess.CpuWriteOnly => DxHeapType.Upload, - _ => default - }; - } - public static ResourceDimension DirectX12(TextureType textureType) { return textureType switch diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index d8287b2c..6a217ea0 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -13,7 +13,7 @@ public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) DxHeapDesc heapDesc = new() { SizeInBytes = ZenithHelper.Align(desc.SizeInBytes, DXGraphicsContext.DefaultHeapAlignment), - Properties = new(DXFormats.DirectX12(desc.Type)), + Properties = new(DXFormats.DirectX12(desc.Residency)), Alignment = DXGraphicsContext.DefaultHeapAlignment }; diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index 2d790e78..f1a43d1f 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -17,7 +17,7 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context context.Device14.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); - Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Access = BufferAccess.CpuReadOnly }, null); + Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Residency = MemoryResidency.CpuReadOnly }, null); } public DXBuffer Buffer { get; } diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index cb7d1e5d..1929dae2 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -18,7 +18,7 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public void Upload(uint offsetInBytes, BufferData data) { - if (desc.Access is BufferAccess.CpuWriteOnly) + if (desc.Residency is MemoryResidency.CpuWriteOnly) { MappedMemory mappedMemory = Map(); @@ -40,7 +40,7 @@ public void Upload(uint offsetInBytes, BufferData data) public void Download(uint offsetInBytes, BufferData data) { - if (desc.Access is BufferAccess.CpuReadOnly) + if (desc.Residency is MemoryResidency.CpuReadOnly) { MappedMemory mappedMemory = Map(); diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index df6205ab..3e3c2e1e 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -22,8 +22,8 @@ public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) lease = new(context.CreateBuffer(new() { SizeInBytes = data.SizeInBytes, - Access = BufferAccess.CpuReadOnly, - Usages = BufferUsages.CopyDst + Usages = BufferUsages.CopyDst, + Residency = MemoryResidency.CpuReadOnly })); } diff --git a/sources/Zenith.NET/Enums/ElementSemantic.cs b/sources/Zenith.NET/Enums/ElementSemantic.cs index d5745f86..d3fbe849 100644 --- a/sources/Zenith.NET/Enums/ElementSemantic.cs +++ b/sources/Zenith.NET/Enums/ElementSemantic.cs @@ -10,7 +10,7 @@ public enum ElementSemantic Tangent, - Binormal, + Bitangent, Color, diff --git a/sources/Zenith.NET/Enums/HeapType.cs b/sources/Zenith.NET/Enums/HeapType.cs deleted file mode 100644 index 04ce3464..00000000 --- a/sources/Zenith.NET/Enums/HeapType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Zenith.NET; - -public enum HeapType -{ - GpuOnly, - - CpuReadOnly, - - CpuWriteOnly -} diff --git a/sources/Zenith.NET/Enums/BufferAccess.cs b/sources/Zenith.NET/Enums/MemoryResidency.cs similarity index 73% rename from sources/Zenith.NET/Enums/BufferAccess.cs rename to sources/Zenith.NET/Enums/MemoryResidency.cs index 58925a00..fa464c52 100644 --- a/sources/Zenith.NET/Enums/BufferAccess.cs +++ b/sources/Zenith.NET/Enums/MemoryResidency.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum BufferAccess +public enum MemoryResidency { GpuOnly, diff --git a/sources/Zenith.NET/Enums/NativeObjectType.cs b/sources/Zenith.NET/Enums/NativeObjectType.cs index 1872e9d6..e308a849 100644 --- a/sources/Zenith.NET/Enums/NativeObjectType.cs +++ b/sources/Zenith.NET/Enums/NativeObjectType.cs @@ -2,4 +2,19 @@ public enum NativeObjectType { + Instance, + + PhysicalDevice, + + Device, + + CommandQueue, + + CommandQueueFamilyIndex, + + CommandBuffer, + + Resource, + + ResourceView } diff --git a/sources/Zenith.NET/Structs/BufferBarrier.cs b/sources/Zenith.NET/Structs/BufferBarrier.cs index 384a9d6c..3be0c06d 100644 --- a/sources/Zenith.NET/Structs/BufferBarrier.cs +++ b/sources/Zenith.NET/Structs/BufferBarrier.cs @@ -4,10 +4,6 @@ public struct BufferBarrier { public Buffer Buffer; - public ulong OffsetInBytes; - - public ulong SizeInBytes; - public PipelineStages SrcStages; public PipelineStages DstStages; @@ -15,4 +11,112 @@ public struct BufferBarrier public ResourceAccess SrcAccess; public ResourceAccess DstAccess; + + public static BufferBarrier Vertex(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexInput, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.Vertex + }; + } + + public static BufferBarrier Index(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexInput, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.Index + }; + } + + public static BufferBarrier Constant(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.Constant + }; + } + + public static BufferBarrier Indirect(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Indirect, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.Indirect + }; + } + + public static BufferBarrier ShaderRead(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ShaderRead + }; + } + + public static BufferBarrier Storage(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite + }; + } + + public static BufferBarrier CopySrc(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Copy, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.CopyRead + }; + } + + public static BufferBarrier CopyDst(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Copy, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.CopyWrite + }; + } + + public static BufferBarrier AccelerationStructureRead(Buffer buffer, BufferBarrier? previous) + { + return new() + { + Buffer = buffer, + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.AccelerationStructureBuild, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.AccelerationStructureRead + }; + } } diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index b7763424..ffd433f5 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -6,18 +6,18 @@ public struct BufferDesc public uint StrideInBytes; - public BufferAccess Access; - public BufferUsages Usages; + public MemoryResidency Residency; + public static BufferDesc Vertex(uint sizeInBytes) { return new() { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Vertex | BufferUsages.CopyDst + Usages = BufferUsages.Vertex | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }; } @@ -27,8 +27,19 @@ public static BufferDesc Index(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Index | BufferUsages.CopyDst + Usages = BufferUsages.Index | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc Indirect(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Usages = BufferUsages.Indirect | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }; } @@ -38,8 +49,8 @@ public static BufferDesc Constant(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Constant | BufferUsages.CopyDst + Usages = BufferUsages.Constant | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }; } @@ -49,8 +60,8 @@ public static BufferDesc StorageReadOnly(uint sizeInBytes, uint strideInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst + Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }; } @@ -60,19 +71,19 @@ public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.StorageReadWrite | BufferUsages.CopyDst + Usages = BufferUsages.StorageReadWrite | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }; } - public static BufferDesc Indirect(uint sizeInBytes) + public static BufferDesc AccelerationStructure(uint sizeInBytes) { return new() { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Access = BufferAccess.GpuOnly, - Usages = BufferUsages.Indirect | BufferUsages.CopyDst + Usages = BufferUsages.AccelerationStructure | BufferUsages.CopyDst, + Residency = MemoryResidency.GpuOnly }; } @@ -82,8 +93,8 @@ public static BufferDesc Staging(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.CopySrc + Usages = BufferUsages.CopySrc, + Residency = MemoryResidency.CpuWriteOnly }; } } diff --git a/sources/Zenith.NET/Structs/HeapDesc.cs b/sources/Zenith.NET/Structs/HeapDesc.cs index a15eae1e..8087b7cf 100644 --- a/sources/Zenith.NET/Structs/HeapDesc.cs +++ b/sources/Zenith.NET/Structs/HeapDesc.cs @@ -2,7 +2,34 @@ public struct HeapDesc { - public HeapType Type; - public ulong SizeInBytes; + + public MemoryResidency Residency; + + public static HeapDesc GpuOnly(ulong sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + Residency = MemoryResidency.GpuOnly + }; + } + + public static HeapDesc CpuReadOnly(ulong sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + Residency = MemoryResidency.CpuReadOnly + }; + } + + public static HeapDesc CpuWriteOnly(ulong sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + Residency = MemoryResidency.CpuWriteOnly + }; + } } diff --git a/sources/Zenith.NET/Structs/MemoryBarrier.cs b/sources/Zenith.NET/Structs/MemoryBarrier.cs index 6651e5a0..86f74627 100644 --- a/sources/Zenith.NET/Structs/MemoryBarrier.cs +++ b/sources/Zenith.NET/Structs/MemoryBarrier.cs @@ -9,4 +9,15 @@ public struct MemoryBarrier public ResourceAccess SrcAccess; public ResourceAccess DstAccess; + + public static MemoryBarrier Storage(MemoryBarrier? previous) + { + return new() + { + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, + SrcAccess = previous?.DstAccess ?? ResourceAccess.ShaderWrite, + DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite + }; + } } diff --git a/sources/Zenith.NET/Structs/TextureBarrier.cs b/sources/Zenith.NET/Structs/TextureBarrier.cs index 77e0cb8b..d18d015c 100644 --- a/sources/Zenith.NET/Structs/TextureBarrier.cs +++ b/sources/Zenith.NET/Structs/TextureBarrier.cs @@ -17,4 +17,154 @@ public struct TextureBarrier public TextureLayout SrcLayout; public TextureLayout DstLayout; + + public static TextureBarrier ColorAttachment(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.ColorAttachmentOutput, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ColorAttachmentWrite, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.ColorAttachment + }; + } + + public static TextureBarrier DepthStencilAttachment(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.EarlyFragmentTests | PipelineStages.LateFragmentTests, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.DepthStencilAttachmentWrite, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.DepthStencilAttachment + }; + } + + public static TextureBarrier DepthStencilRead(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.EarlyFragmentTests | PipelineStages.FragmentShader | PipelineStages.LateFragmentTests, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.DepthStencilAttachmentRead, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.DepthStencilReadOnly + }; + } + + public static TextureBarrier ShaderRead(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ShaderRead, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.Sampled + }; + } + + public static TextureBarrier Storage(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.Storage + }; + } + + public static TextureBarrier CopySrc(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Copy, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.CopyRead, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.CopySrc + }; + } + + public static TextureBarrier CopyDst(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Copy, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.CopyWrite, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.CopyDst + }; + } + + public static TextureBarrier ResolveSrc(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Resolve, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ResolveRead, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.ResolveSrc + }; + } + + public static TextureBarrier ResolveDst(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.Resolve, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.ResolveWrite, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.ResolveDst + }; + } + + public static TextureBarrier Present(Texture texture, TextureBarrier? previous) + { + return new() + { + Texture = texture, + Range = TextureSubresourceRange.All(texture), + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.None, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.Present, + SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, + DstLayout = TextureLayout.Present + }; + } } diff --git a/sources/Zenith.NET/Structs/TextureSubresourceRange.cs b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs index 5da89975..c8e5d108 100644 --- a/sources/Zenith.NET/Structs/TextureSubresourceRange.cs +++ b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs @@ -9,4 +9,15 @@ public struct TextureSubresourceRange public uint BaseArrayLayer; public uint LayerCount; + + public static TextureSubresourceRange All(Texture texture) + { + return new() + { + BaseMipLevel = 0, + LevelCount = texture.Desc.MipLevels, + BaseArrayLayer = 0, + LayerCount = texture.Desc.ArrayLayers + }; + } } diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index b97c620a..02d6bb5c 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -22,8 +22,8 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) lease = new(context.CreateBuffer(new() { SizeInBytes = sizeInBytes, - Access = BufferAccess.CpuWriteOnly, - Usages = BufferUsages.CopySrc + Usages = BufferUsages.CopySrc, + Residency = MemoryResidency.CpuWriteOnly })); } From 8e4d172b8c5fe7b477ec8e93e99227e8e08d578d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 29 May 2026 17:35:50 +0800 Subject: [PATCH 154/466] Simplify DXTextureView range initialization Replaced manual subresource range construction with TextureSubresourceRange.All(this) in DXTextureView initialization, improving code clarity and maintainability. --- sources/Zenith.NET.DirectX12/DXTexture.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 7f8180db..a99bc538 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -37,11 +37,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr Date: Fri, 29 May 2026 17:53:37 +0800 Subject: [PATCH 155/466] Update ImGuiRenderer shader and DXCommandQueue methods - Add missing UV and Color fields to ImGuiRenderer shader structs - Implement GetCompletedValue and WaitImpl in DXCommandQueue - Remove all members from NativeObjectType enum --- .../Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 14 +++++++------- sources/Zenith.NET.DirectX12/DXCommandQueue.cs | 7 +++++-- sources/Zenith.NET/Enums/NativeObjectType.cs | 15 --------------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 600c5f63..43371ac4 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -11,18 +11,18 @@ internal unsafe class ImGuiRenderer : DisposableObject struct VSInput { float2 Position : POSITION0; - + float2 UV : TEXCOORD0; - + float4 Color : COLOR0; }; struct VSOutput { float4 Position : SV_POSITION; - + float2 UV : TEXCOORD0; - + float4 Color : COLOR0; }; @@ -45,15 +45,15 @@ float3 SrgbToLinear(float3 srgb) VSOutput VSMain(VSInput input) { VSOutput output; - + output.Position = mul(float4(input.Position, 0.0, 1.0), constants.Projection); output.UV = input.UV; output.Color = input.Color; - + #if 0 output.Color.rgb = SrgbToLinear(output.Color.rgb); #endif - + return output; } diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 58d0467c..65d3bf50 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -23,7 +23,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override ulong GetCompletedValue() { - throw new NotImplementedException(); + return Fence1.GetCompletedValue(); } protected override CommandBuffer CreateCommandBuffer() @@ -38,7 +38,10 @@ protected override void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan Date: Fri, 29 May 2026 18:03:42 +0800 Subject: [PATCH 156/466] Add DX12 CommandBuffer and related extensions Implemented DXCommandBuffer with method stubs and resource management using Silk.NET.Direct3D12. Added extension methods for casting CommandQueue and CommandBuffer to DX12 types. Introduced DXFormats.DirectX12 for mapping CommandQueueType to CommandListType. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 191 ++++++++++++++++++ sources/Zenith.NET.DirectX12/DXFormats.cs | 11 + sources/Zenith.NET.DirectX12/Extensions.cs | 16 ++ 3 files changed, 218 insertions(+) create mode 100644 sources/Zenith.NET.DirectX12/DXCommandBuffer.cs diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs new file mode 100644 index 00000000..98aeadca --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -0,0 +1,191 @@ +using System.Numerics; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXCommandBuffer : CommandBuffer +{ + public ComPtr CommandAllocator; + + public ComPtr CommandList; + + public DXCommandBuffer(DXGraphicsContext context, CommandQueue queue) : base(context, queue) + { + context.Device14.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), SilkMarshal.GuidPtrOf(), (void**)CommandAllocator.GetAddressOf()).Success(); + + context.Device14.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void BarrierImpl(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) + { + } + + protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) + { + } + + protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + } + + protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) + { + } + + protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) + { + } + + protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + } + + protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); + } + + protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); + } + + protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) + { + } + + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + } + + protected override void EndRenderPassImpl() + { + } + + protected override void SetScissorsImpl(ReadOnlySpan scissors) + { + } + + protected override void SetViewportsImpl(ReadOnlySpan viewports) + { + } + + protected override void SetPipelineImpl(GraphicsPipeline pipeline) + { + } + + protected override void SetPipelineImpl(ComputePipeline pipeline) + { + } + + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + { + } + + protected override void SetStencilReferenceImpl(uint stencilReference) + { + } + + protected override void SetBlendConstantImpl(Vector4 blendConstant) + { + } + + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) + { + } + + protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) + { + } + + protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) + { + } + + protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) + { + } + + protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + } + + protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) + { + } + + protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + } + + protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) + { + } + + protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) + { + } + + protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) + { + } + + protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) + { + } + + protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) + { + } + + protected override void EndQueryImpl(QueryHeap queryHeap, uint index) + { + } + + protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) + { + } + + protected override void BeginDebugEventImpl(string label) + { + } + + protected override void EndDebugEventImpl() + { + } + + protected override void InsertDebugMarkerImpl(string label) + { + } + + protected override void BeginImpl() + { + } + + protected override void EndImpl() + { + } + + protected override void ResetImpl() + { + } + + protected override void SetResourceName(string name) + { + CommandList.SetName(name).Success(); + } + + protected override void Destroy() + { + base.Destroy(); + + CommandList.Dispose(); + CommandAllocator.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index a0d0aaa6..06999012 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -5,6 +5,17 @@ namespace Zenith.NET.DirectX12; internal static class DXFormats { + public static CommandListType DirectX12(CommandQueueType commandQueueType) + { + return commandQueueType switch + { + CommandQueueType.Graphics => CommandListType.Direct, + CommandQueueType.Compute => CommandListType.Compute, + CommandQueueType.Copy => CommandListType.Copy, + _ => default + }; + } + public static DxHeapType DirectX12(MemoryResidency memoryResidency) { return memoryResidency switch diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 8f3147d4..234ecc2c 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -28,6 +28,22 @@ internal bool IsSuccess() } } + extension(CommandQueue commandQueue) + { + internal DXCommandQueue DirectX12() + { + return (DXCommandQueue)commandQueue; + } + } + + extension(CommandBuffer commandBuffer) + { + internal DXCommandBuffer DirectX12() + { + return (DXCommandBuffer)commandBuffer; + } + } + extension(Heap heap) { internal DXHeap DirectX12() From 1559db5550a2dd491374afcda7e434e194a4b8f8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 29 May 2026 18:04:55 +0800 Subject: [PATCH 157/466] Add ManualResetEvent for DXCommandQueue synchronization Introduced a ManualResetEvent to DXCommandQueue to handle synchronization in WaitImpl by passing its handle to SetEventOnCompletion and waiting/resetting the event. Ensured proper disposal of the event in Destroy for resource cleanup. --- sources/Zenith.NET.DirectX12/DXCommandQueue.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 65d3bf50..389c6e29 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -5,6 +5,8 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXCommandQueue : CommandQueue { + private readonly ManualResetEvent @event = new(false); + public ComPtr CommandQueue; public ComPtr Fence1; @@ -40,7 +42,10 @@ protected override void WaitImpl(ulong waitValue) { if (Fence1.GetCompletedValue() < waitValue) { - Fence1.SetEventOnCompletion(waitValue, default).Success(); + Fence1.SetEventOnCompletion(waitValue, (void*)@event.SafeWaitHandle.DangerousGetHandle()).Success(); + + @event.WaitOne(); + @event.Reset(); } } @@ -54,5 +59,7 @@ protected override void Destroy() base.Destroy(); Fence1.Dispose(); + + @event.Dispose(); } } From b49eff282c54390f03910d7884e50d606bba4e2c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 29 May 2026 18:16:04 +0800 Subject: [PATCH 158/466] Implement DXCommandQueue methods and improve fence handling Refactored DXCommandQueue to provide a strongly-typed Context, implemented CreateCommandBuffer and SubmitImpl, and simplified WaitImpl logic. Updated CommandQueue.Wait to avoid unnecessary waits by checking fence completion before waiting. --- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 21 ++++++++++++------- sources/Zenith.NET/CommandQueue.cs | 9 ++++++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 389c6e29..5cb7287d 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -18,6 +18,8 @@ public DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr(), (void**)Fence1.GetAddressOf()).Success(); } + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -30,23 +32,26 @@ protected override ulong GetCompletedValue() protected override CommandBuffer CreateCommandBuffer() { - throw new NotImplementedException(); + return new DXCommandBuffer(Context, this); } protected override void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue) { - throw new NotImplementedException(); + foreach (CommandSubmission wait in waits) + { + CommandQueue.Wait(wait.Queue.DirectX12().Fence1, wait.Value).Success(); + } + + CommandQueue.ExecuteCommandLists(1, commandBuffer.DirectX12().CommandList.GetAddressOf()); + CommandQueue.Signal(Fence1, signalValue).Success(); } protected override void WaitImpl(ulong waitValue) { - if (Fence1.GetCompletedValue() < waitValue) - { - Fence1.SetEventOnCompletion(waitValue, (void*)@event.SafeWaitHandle.DangerousGetHandle()).Success(); + Fence1.SetEventOnCompletion(waitValue, (void*)@event.SafeWaitHandle.DangerousGetHandle()).Success(); - @event.WaitOne(); - @event.Reset(); - } + @event.WaitOne(); + @event.Reset(); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 41ef0f4b..356b4e84 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -38,10 +38,15 @@ internal void Wait(ulong value) { using Lock.Scope _ = @lock.EnterScope(); - WaitImpl(value); - ulong completed = GetCompletedValue(); + if (completed < value) + { + WaitImpl(value); + + completed = GetCompletedValue(); + } + while (submitteds.TryPeek(out Submitted submitted) && submitted.Value <= completed) { submitteds.Dequeue(); From 70340bd253b2decb29cc255cf9e7cda101aaa0dd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 13:07:16 +0800 Subject: [PATCH 159/466] Refactor: target lower D3D12/DXGI interfaces for compat Refactored the codebase to use ID3D12Device10, IDXGIAdapter, and IDXGIFactory6 for broader Direct3D 12 compatibility. Updated all related method calls and interface usages. Validation layer now queries ID3D12InfoQueue1 directly from the device. Switched to ID3D12Fence and DxSamplerDesc, and updated sampler creation logic. Revised feature checks and device creation to ensure required features are present. Replaced GetResourceAllocationInfo3 with GetResourceAllocationInfo2. Updated usings and type aliases. These changes improve compatibility with a wider range of D3D12 drivers and hardware. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 20 +++--- sources/Zenith.NET.DirectX12/DXBufferView.cs | 6 +- .../Zenith.NET.DirectX12/DXCapabilities.cs | 6 +- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 4 +- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 14 ++-- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 2 +- .../Zenith.NET.DirectX12/DXDescriptorHeap.cs | 4 +- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 72 +++++++++++-------- .../DXGraphicsPipeline.cs | 2 +- sources/Zenith.NET.DirectX12/DXHeap.cs | 38 +++++----- .../DXMeshShadingPipeline.cs | 2 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 2 +- sources/Zenith.NET.DirectX12/DXSampler.cs | 12 ++-- sources/Zenith.NET.DirectX12/DXTexture.cs | 20 +++--- sources/Zenith.NET.DirectX12/DXTextureView.cs | 4 +- .../Zenith.NET.DirectX12/DXValidationLayer.cs | 11 +-- sources/Zenith.NET.DirectX12/Usings.cs | 1 + 17 files changed, 118 insertions(+), 102 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 565f23a4..9333c2e7 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -18,16 +18,16 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr(), - (void**)Resource.GetAddressOf()).Success(); + context.Device.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); } else { diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs index cf9d9b79..a9f4ae7e 100644 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -40,7 +40,7 @@ private DXDescriptorToken CreateCbvToken() SizeInBytes = ZenithHelper.Align(Desc.SizeInBytes, 256u) }; - context.Device14.CreateConstantBufferView(&viewDesc, token.CpuHandle); + context.Device.CreateConstantBufferView(&viewDesc, token.CpuHandle); return token; } @@ -61,7 +61,7 @@ private DXDescriptorToken CreateSrvToken() } }; - context.Device14.CreateShaderResourceView(Desc.Buffer.DirectX12().Resource, &viewDesc, token.CpuHandle); + context.Device.CreateShaderResourceView(Desc.Buffer.DirectX12().Resource, &viewDesc, token.CpuHandle); return token; } @@ -81,7 +81,7 @@ private DXDescriptorToken CreateUavToken() } }; - context.Device14.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); + context.Device.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); return token; } diff --git a/sources/Zenith.NET.DirectX12/DXCapabilities.cs b/sources/Zenith.NET.DirectX12/DXCapabilities.cs index dd9d8962..3d99a2fb 100644 --- a/sources/Zenith.NET.DirectX12/DXCapabilities.cs +++ b/sources/Zenith.NET.DirectX12/DXCapabilities.cs @@ -9,13 +9,13 @@ internal unsafe class DXCapabilities : Capabilities public DXCapabilities(DXGraphicsContext context) { AdapterDesc desc; - context.Adapter4.GetDesc(&desc).Success(); + context.Adapter.GetDesc(&desc).Success(); FeatureDataD3D12Options5 options5 = new(); - context.Device14.CheckFeatureSupport(Feature.D3D12Options5, &options5, (uint)sizeof(FeatureDataD3D12Options5)).Success(); + context.Device.CheckFeatureSupport(Feature.D3D12Options5, &options5, (uint)sizeof(FeatureDataD3D12Options5)).Success(); FeatureDataD3D12Options7 options7 = new(); - context.Device14.CheckFeatureSupport(Feature.D3D12Options7, &options7, (uint)sizeof(FeatureDataD3D12Options7)).Success(); + context.Device.CheckFeatureSupport(Feature.D3D12Options7, &options7, (uint)sizeof(FeatureDataD3D12Options7)).Success(); DeviceName = ZenithMarshal.StringFromPointer((nint)desc.Description, StringEncoding.UTF16); RayTracingSupported = options5.RaytracingTier >= RaytracingTier.Tier11; diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 98aeadca..283ef981 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -12,9 +12,9 @@ internal unsafe class DXCommandBuffer : CommandBuffer public DXCommandBuffer(DXGraphicsContext context, CommandQueue queue) : base(context, queue) { - context.Device14.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), SilkMarshal.GuidPtrOf(), (void**)CommandAllocator.GetAddressOf()).Success(); + context.Device.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), SilkMarshal.GuidPtrOf(), (void**)CommandAllocator.GetAddressOf()).Success(); - context.Device14.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); + context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 5cb7287d..b578b80d 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -9,13 +9,13 @@ internal unsafe class DXCommandQueue : CommandQueue public ComPtr CommandQueue; - public ComPtr Fence1; + public ComPtr Fence; public DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr commandQueue) : base(context, type) { CommandQueue = commandQueue; - context.Device14.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence1.GetAddressOf()).Success(); + context.Device.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence.GetAddressOf()).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; @@ -27,7 +27,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override ulong GetCompletedValue() { - return Fence1.GetCompletedValue(); + return Fence.GetCompletedValue(); } protected override CommandBuffer CreateCommandBuffer() @@ -39,16 +39,16 @@ protected override void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index 2840c3e9..c5b0cb7f 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -26,11 +26,11 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None }; - context.Device14.CreateDescriptorHeap(&desc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); + context.Device.CreateDescriptorHeap(&desc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); CpuStartHandle = Heap.GetCPUDescriptorHandleForHeapStart(); GpuStartHandle = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; - IncrementSize = context.Device14.GetDescriptorHandleIncrementSize(type); + IncrementSize = context.Device.GetDescriptorHandleIncrementSize(type); } public DXDescriptorToken Allocate() diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 478dc5ad..dfa991a8 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -1,6 +1,7 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; using Silk.NET.DXGI; +using Feature = Silk.NET.Direct3D12.Feature; namespace Zenith.NET.DirectX12; @@ -10,13 +11,11 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public const uint Shader4ComponentMapping = 0x1688; - public ComPtr Factory7; + public ComPtr Factory; - public ComPtr Adapter4; + public ComPtr Adapter; - public ComPtr Device14; - - public ComPtr? InfoQueue1; + public ComPtr Device; public ComPtr RootSignature; @@ -58,25 +57,40 @@ protected override void Initialize(bool useValidationLayer, out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - using ComPtr debug6 = new(); - if (useValidationLayer && D3D12.GetDebugInterface(SilkMarshal.GuidPtrOf(), (void**)debug6.GetAddressOf()).IsSuccess()) + using ComPtr debug = new(); + if (useValidationLayer && D3D12.GetDebugInterface(SilkMarshal.GuidPtrOf(), (void**)debug.GetAddressOf()).IsSuccess()) { - debug6.EnableDebugLayer(); + debug.EnableDebugLayer(); } - DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), SilkMarshal.GuidPtrOf(), (void**)Factory7.GetAddressOf()).Success(); + DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), SilkMarshal.GuidPtrOf(), (void**)Factory.GetAddressOf()).Success(); + + Factory.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, SilkMarshal.GuidPtrOf(), (void**)Adapter.GetAddressOf()).Success(); + + if (!D3D12.CreateDevice((IUnknown*)Adapter.Handle, D3DFeatureLevel.Level120, SilkMarshal.GuidPtrOf(), (void**)Device.GetAddressOf()).IsSuccess()) + { + throw new NotSupportedException("Direct3D 12 Feature Level 12_0 is not supported on the selected adapter."); + } - Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, SilkMarshal.GuidPtrOf(), (void**)Adapter4.GetAddressOf()).Success(); + FeatureDataShaderModel shaderModel = new() { HighestShaderModel = D3DShaderModel.ShaderModel66 }; + Device.CheckFeatureSupport(Feature.ShaderModel, &shaderModel, (uint)sizeof(FeatureDataShaderModel)).Success(); + if (shaderModel.HighestShaderModel < D3DShaderModel.ShaderModel66) + { + throw new NotSupportedException("Shader Model 6.6 is required but not supported on the selected adapter."); + } - if (!D3D12.CreateDevice((IUnknown*)Adapter4.Handle, D3DFeatureLevel.Level122, SilkMarshal.GuidPtrOf(), (void**)Device14.GetAddressOf()).IsSuccess()) + FeatureDataD3D12Options options = new(); + Device.CheckFeatureSupport(Feature.D3D12Options, &options, (uint)sizeof(FeatureDataD3D12Options)).Success(); + if (options.ResourceBindingTier < ResourceBindingTier.Tier3) { - throw new NotSupportedException("Direct3D 12 Feature Level 12_2 is not supported on the selected adapter."); + throw new NotSupportedException("Resource Binding Tier 3 (bindless) is required but not supported on the selected adapter."); } - ComPtr infoQueue1 = new(); - if (Device14.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)infoQueue1.GetAddressOf()).IsSuccess()) + FeatureDataD3D12Options12 options12 = new(); + Device.CheckFeatureSupport(Feature.D3D12Options12, &options12, (uint)sizeof(FeatureDataD3D12Options12)).Success(); + if (!options12.EnhancedBarriersSupported) { - InfoQueue1 = infoQueue1; + throw new NotSupportedException("Enhanced Barriers are required but not supported on the selected adapter."); } RootParameter1 rootParameter = new() @@ -102,32 +116,32 @@ protected override void Initialize(bool useValidationLayer, using ComPtr rootSignatureBlob = new(); using ComPtr rootSignatureError = new(); D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, rootSignatureBlob.GetAddressOf(), rootSignatureError.GetAddressOf()).Success(); - Device14.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); + Device.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; - Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device14.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; - Device14.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); + Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Compute; - Device14.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)ComputeCommandQueue.GetAddressOf()).Success(); + Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)ComputeCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Copy; - Device14.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CopyCommandQueue.GetAddressOf()).Success(); + Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CopyCommandQueue.GetAddressOf()).Success(); capabilities = new DXCapabilities(this); graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); @@ -150,7 +164,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); - ResourceAllocationInfo info = Device14.GetResourceAllocationInfo3(0, 1, &resourceDesc, default(uint*), default, default(ResourceAllocationInfo1*)); + ResourceAllocationInfo info = Device.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } @@ -159,7 +173,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); - ResourceAllocationInfo info = Device14.GetResourceAllocationInfo3(0, 1, &resourceDesc, default(uint*), default, default(ResourceAllocationInfo1*)); + ResourceAllocationInfo info = Device.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); return new(info.SizeInBytes, info.Alignment); } @@ -234,11 +248,9 @@ protected override void Destroy() RootSignature.Dispose(); - InfoQueue1?.Dispose(); - - Device14.Dispose(); - Adapter4.Dispose(); - Factory7.Dispose(); + Device.Dispose(); + Adapter.Dispose(); + Factory.Dispose(); D3D12.Dispose(); DXGI.Dispose(); diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index bdcbf2a5..8c944b80 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -143,7 +143,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) PPipelineStateSubobjectStream = &pipelineStateStream }; - context.Device14.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index 6a217ea0..a889ea99 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -17,7 +17,7 @@ public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) Alignment = DXGraphicsContext.DefaultHeapAlignment }; - context.Device14.CreateHeap(&heapDesc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); + context.Device.CreateHeap(&heapDesc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; @@ -32,15 +32,15 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); ComPtr resource = new(); - Context.Device14.CreatePlacedResource2(Heap, - offsetInBytes, - &resourceDesc, - BarrierLayout.Undefined, - default(ClearValue*), - 0, - default(Format*), - SilkMarshal.GuidPtrOf(), - (void**)resource.GetAddressOf()).Success(); + Context.Device.CreatePlacedResource2(Heap, + offsetInBytes, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)resource.GetAddressOf()).Success(); return new DXBuffer(Context, desc, resource); } @@ -50,15 +50,15 @@ protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc de ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); ComPtr resource = new(); - Context.Device14.CreatePlacedResource2(Heap, - offsetInBytes, - &resourceDesc, - BarrierLayout.Undefined, - default(ClearValue*), - 0, - default(Format*), - SilkMarshal.GuidPtrOf(), - (void**)resource.GetAddressOf()).Success(); + Context.Device.CreatePlacedResource2(Heap, + offsetInBytes, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)resource.GetAddressOf()).Success(); return new DXTexture(Context, desc, resource); } diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 36ad39cf..f77868bb 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -117,7 +117,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc PPipelineStateSubobjectStream = &pipelineStateStream }; - context.Device14.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index f1a43d1f..42cfcc5b 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -15,7 +15,7 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context Count = desc.Count }; - context.Device14.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); + context.Device.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Residency = MemoryResidency.CpuReadOnly }, null); } diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs index b2c84e59..70455ab0 100644 --- a/sources/Zenith.NET.DirectX12/DXSampler.cs +++ b/sources/Zenith.NET.DirectX12/DXSampler.cs @@ -12,7 +12,7 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de (float r, float g, float b, float a) = DXFormats.DirectX12(desc.BorderColor); - SamplerDesc2 samplerDesc = new() + DxSamplerDesc samplerDesc = new() { Filter = DXFormats.DirectX12(desc.MinFilter, desc.MagFilter, desc.MipFilter, desc.MaxAnisotropy, desc.CompareOp), AddressU = DXFormats.DirectX12(desc.AddressU), @@ -25,12 +25,12 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de MaxLOD = desc.MaxLod }; - samplerDesc.Anonymous.FloatBorderColor[0] = r; - samplerDesc.Anonymous.FloatBorderColor[1] = g; - samplerDesc.Anonymous.FloatBorderColor[2] = b; - samplerDesc.Anonymous.FloatBorderColor[3] = a; + samplerDesc.BorderColor[0] = r; + samplerDesc.BorderColor[1] = g; + samplerDesc.BorderColor[2] = b; + samplerDesc.BorderColor[3] = a; - context.Device14.CreateSampler2(&samplerDesc, Token.CpuHandle); + context.Device.CreateSampler(&samplerDesc, Token.CpuHandle); } public override ResourceHandle Handle => Token.ResourceHandle; diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index a99bc538..18222262 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -16,16 +16,16 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr(), - (void**)Resource.GetAddressOf()).Success(); + context.Device.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); } else { diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs index fdc6163e..41c9dbd5 100644 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -127,7 +127,7 @@ private DXDescriptorToken CreateSrvToken() break; } - context.Device14.CreateShaderResourceView(Desc.Texture.DirectX12().Resource, &viewDesc, token.CpuHandle); + context.Device.CreateShaderResourceView(Desc.Texture.DirectX12().Resource, &viewDesc, token.CpuHandle); return token; } @@ -201,7 +201,7 @@ private DXDescriptorToken CreateUavToken() break; } - context.Device14.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); + context.Device.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); return token; } diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index 2d425b21..76e8f9ad 100644 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -1,4 +1,5 @@ -using Silk.NET.Direct3D12; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; namespace Zenith.NET.DirectX12; @@ -7,11 +8,13 @@ internal unsafe class DXValidationLayer : ValidationLayer private readonly PfnMessageFunc callback; private readonly uint callbackCookie; + public ComPtr InfoQueue; + public DXValidationLayer(DXGraphicsContext context) : base(context) { - callback = new(Callback); + context.Device.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)InfoQueue.GetAddressOf()).Success(); - context.InfoQueue1?.RegisterMessageCallback(callback, MessageCallbackFlags.FlagNone, null, ref callbackCookie).Success(); + InfoQueue.RegisterMessageCallback(callback = new(Callback), MessageCallbackFlags.FlagNone, default, ref callbackCookie).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; @@ -27,7 +30,7 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.InfoQueue1?.UnregisterMessageCallback(callbackCookie).Success(); + InfoQueue.UnregisterMessageCallback(callbackCookie).Success(); callback.Dispose(); } diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index e97ab9cc..ce008afb 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -6,5 +6,6 @@ global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxQueryHeapDesc = Silk.NET.Direct3D12.QueryHeapDesc; global using DxRange = Silk.NET.Direct3D12.Range; +global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; global using DxTextureLayout = Silk.NET.Direct3D12.TextureLayout; From 904477cd6c9b022042966af8fb79b2c4b75d6380 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 13:08:35 +0800 Subject: [PATCH 160/466] Dispose InfoQueue in DXValidationLayer.Destroy() Added explicit call to InfoQueue.Dispose() in the Destroy() method to ensure proper resource cleanup when DXValidationLayer is destroyed. --- sources/Zenith.NET.DirectX12/DXValidationLayer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index 76e8f9ad..fcaf0dd9 100644 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -31,6 +31,7 @@ protected override void SetResourceName(string name) protected override void Destroy() { InfoQueue.UnregisterMessageCallback(callbackCookie).Success(); + InfoQueue.Dispose(); callback.Dispose(); } From a8940e9ca00cd1fae8eee91a2a197f77a5cc4399 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 13:42:42 +0800 Subject: [PATCH 161/466] Refactor DX12 command buffer and extend functionality Refactored DXCommandBuffer to use ID3D12GraphicsCommandList7 and implemented previously missing methods for resource resolve, render pass, pipeline state, draw/dispatch, queries, and debug markers. Updated command queue execution, improved descriptor heap/root signature setup, and enhanced DXFormats and DXQueryHeap for better type handling. Added new global using aliases for DirectX types. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 106 +++++++++++++++++- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 2 +- sources/Zenith.NET.DirectX12/DXFormats.cs | 28 ++++- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 2 +- sources/Zenith.NET.DirectX12/Usings.cs | 2 + 5 files changed, 132 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 283ef981..bf6f089f 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -1,6 +1,7 @@ using System.Numerics; using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.Maths; namespace Zenith.NET.DirectX12; @@ -8,15 +9,17 @@ internal unsafe class DXCommandBuffer : CommandBuffer { public ComPtr CommandAllocator; - public ComPtr CommandList; + public ComPtr CommandList; public DXCommandBuffer(DXGraphicsContext context, CommandQueue queue) : base(context, queue) { context.Device.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), SilkMarshal.GuidPtrOf(), (void**)CommandAllocator.GetAddressOf()).Success(); - context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); + context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); } + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -44,6 +47,11 @@ protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { + DXTexture dxSrc = src.DirectX12(); + DXTexture dxDst = dst.DirectX12(); + + // TODO: 计算subresource的index。 + CommandList.ResolveSubresource(dxDst.Resource, 0, dxSrc.Resource, 0, DXFormats.DirectX12(dxDst.Desc.Format)); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) @@ -66,42 +74,78 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA protected override void EndRenderPassImpl() { + CommandList.EndRenderPass(); } protected override void SetScissorsImpl(ReadOnlySpan scissors) { + Box2D* pRects = stackalloc Box2D[scissors.Length]; + for (int i = 0; i < scissors.Length; i++) + { + pRects[i] = new(scissors[i].X, scissors[i].Y, scissors[i].X + (int)scissors[i].Width, scissors[i].Y + (int)scissors[i].Height); + } + + CommandList.RSSetScissorRects((uint)scissors.Length, pRects); } protected override void SetViewportsImpl(ReadOnlySpan viewports) { + DxViewport* pViewports = stackalloc DxViewport[viewports.Length]; + for (int i = 0; i < viewports.Length; i++) + { + pViewports[i] = new(viewports[i].X, viewports[i].Y, viewports[i].Width, viewports[i].Height, viewports[i].MinDepth, viewports[i].MaxDepth); + } + + CommandList.RSSetViewports((uint)viewports.Length, pViewports); } protected override void SetPipelineImpl(GraphicsPipeline pipeline) { + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); } protected override void SetPipelineImpl(ComputePipeline pipeline) { + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); } protected override void SetPipelineImpl(MeshShadingPipeline pipeline) { + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); } protected override void SetStencilReferenceImpl(uint stencilReference) { + CommandList.OMSetStencilRef(stencilReference); } protected override void SetBlendConstantImpl(Vector4 blendConstant) { + CommandList.OMSetBlendFactor(&blendConstant.X); } protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { + VertexBufferView view = new() + { + BufferLocation = buffer.DirectX12().GPUVirtualAddress + offsetInBytes, + SizeInBytes = buffer.Desc.SizeInBytes - offsetInBytes, + StrideInBytes = pipeline.Desc.InputLayouts[slot].StrideInBytes + }; + + CommandList.IASetVertexBuffers(slot, 1, &view); } protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { + IndexBufferView view = new() + { + BufferLocation = buffer.DirectX12().GPUVirtualAddress + offsetInBytes, + SizeInBytes = buffer.Desc.SizeInBytes - offsetInBytes, + Format = DXFormats.DirectX12(indexFormat) + }; + + CommandList.IASetIndexBuffer(&view); } protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) @@ -110,70 +154,128 @@ protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { + CommandList.DrawInstanced(vertexCount, instanceCount, firstVertex, firstInstance); } protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { + CommandList.ExecuteIndirect(Context.DrawSignature, drawCount, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) { + CommandList.DrawIndexedInstanced(indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); } protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { + CommandList.ExecuteIndirect(Context.DrawIndexedSignature, drawCount, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { + CommandList.Dispatch(groupCountX, groupCountY, groupCountZ); } protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) { + CommandList.ExecuteIndirect(Context.DispatchSignature, 1, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { + CommandList.DispatchMesh(groupCountX, groupCountY, groupCountZ); } protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) { + CommandList.ExecuteIndirect(Context.DispatchMeshSignature, dispatchCount, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { + CommandList.BeginQuery(queryHeap.DirectX12().QueryHeap, DXFormats.DirectX12(queryHeap.Desc.Type).Type, index); } protected override void EndQueryImpl(QueryHeap queryHeap, uint index) { + DXQueryHeap dxQueryHeap = queryHeap.DirectX12(); + + CommandList.EndQuery(dxQueryHeap.QueryHeap, DXFormats.DirectX12(dxQueryHeap.Desc.Type).Type, index); + CommandList.ResolveQueryData(dxQueryHeap.QueryHeap, DXFormats.DirectX12(dxQueryHeap.Desc.Type).Type, index, 1, dxQueryHeap.Buffer.Resource, sizeof(ulong) * index); } protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) { + DXQueryHeap dxQueryHeap = queryHeap.DirectX12(); + + CommandList.EndQuery(dxQueryHeap.QueryHeap, DxQueryType.Timestamp, index); + CommandList.ResolveQueryData(dxQueryHeap.QueryHeap, DxQueryType.Timestamp, index, 1, dxQueryHeap.Buffer.Resource, sizeof(ulong) * index); } protected override void BeginDebugEventImpl(string label) { + using ZenithMarshal.Scope scope = new(); + + uint size = PixHelpers.CalculateEventSize(label); + + ulong* buffer = (ulong*)ZenithMarshal.Allocate(scope, size); + + PixHelpers.FormatEventToBuffer(buffer, PixHelpers.Event, 0, label); + + CommandList.BeginEvent(PixHelpers.Version, buffer, size); } protected override void EndDebugEventImpl() { + CommandList.EndEvent(); } protected override void InsertDebugMarkerImpl(string label) { + using ZenithMarshal.Scope scope = new(); + + uint size = PixHelpers.CalculateEventSize(label); + + ulong* buffer = (ulong*)ZenithMarshal.Allocate(scope, size); + + PixHelpers.FormatEventToBuffer(buffer, PixHelpers.Marker, 0, label); + + CommandList.SetMarker(PixHelpers.Version, buffer, size); } protected override void BeginImpl() { + if (Queue.Type is CommandQueueType.Copy) + { + return; + } + + ID3D12DescriptorHeap** ppDescriptorHeaps = stackalloc ID3D12DescriptorHeap*[] { Context.CbvSrvUavHeap.Heap, Context.SamplerHeap.Heap }; + + CommandList.SetDescriptorHeaps(2, ppDescriptorHeaps); + + switch (Queue.Type) + { + case CommandQueueType.Graphics: + CommandList.SetGraphicsRootSignature(Context.RootSignature); + break; + + case CommandQueueType.Compute: + CommandList.SetComputeRootSignature(Context.RootSignature); + break; + } } protected override void EndImpl() { + CommandList.Close().Success(); } protected override void ResetImpl() { + CommandAllocator.Reset().Success(); + CommandList.Reset(CommandAllocator, default(ID3D12PipelineState*)).Success(); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index b578b80d..1ff7806e 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -42,7 +42,7 @@ protected override void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan QueryHeapType.Occlusion, + QueryType.Timestamp => QueryHeapType.Timestamp, + _ => default + }, + queryType switch + { + QueryType.Occlusion => DxQueryType.Occlusion, + QueryType.BinaryOcclusion => DxQueryType.BinaryOcclusion, + QueryType.Timestamp => DxQueryType.Timestamp, + _ => default + } + ); + } + + public static Format DirectX12(IndexFormat indexFormat) + { + return indexFormat switch { - QueryType.Occlusion or QueryType.BinaryOcclusion => QueryHeapType.Occlusion, - QueryType.Timestamp => QueryHeapType.Timestamp, + IndexFormat.UInt16 => Format.FormatR16Uint, + IndexFormat.UInt32 => Format.FormatR32Uint, _ => default }; } diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index 42cfcc5b..a4abfe7f 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -11,7 +11,7 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context { DxQueryHeapDesc queryHeapDesc = new() { - Type = DXFormats.DirectX12(desc.Type), + Type = DXFormats.DirectX12(desc.Type).HeapType, Count = desc.Count }; diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index ce008afb..914fe643 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -5,7 +5,9 @@ global using DxHeapType = Silk.NET.Direct3D12.HeapType; global using DxMessageSeverity = Silk.NET.Direct3D12.MessageSeverity; global using DxQueryHeapDesc = Silk.NET.Direct3D12.QueryHeapDesc; +global using DxQueryType = Silk.NET.Direct3D12.QueryType; global using DxRange = Silk.NET.Direct3D12.Range; global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; global using DxTextureLayout = Silk.NET.Direct3D12.TextureLayout; +global using DxViewport = Silk.NET.Direct3D12.Viewport; From 034aa7ba82fcdbb99c2b5dcde21f84d6162dd96b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 13:47:50 +0800 Subject: [PATCH 162/466] Update ResolveTextureImpl to use correct subresource indices Refactored ResolveTextureImpl in DXCommandBuffer to compute and use accurate subresource indices for source and destination textures. Added Subresource method to DXTexture to calculate subresource index based on mip level and array layer. Replaced hardcoded indices with calculated values in ResolveSubresource call. --- sources/Zenith.NET.DirectX12/DXCommandBuffer.cs | 3 +-- sources/Zenith.NET.DirectX12/DXTexture.cs | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index bf6f089f..09356a69 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -50,8 +50,7 @@ protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSu DXTexture dxSrc = src.DirectX12(); DXTexture dxDst = dst.DirectX12(); - // TODO: 计算subresource的index。 - CommandList.ResolveSubresource(dxDst.Resource, 0, dxSrc.Resource, 0, DXFormats.DirectX12(dxDst.Desc.Format)); + CommandList.ResolveSubresource(dxDst.Resource, dxDst.Subresource(dstSubresource), dxSrc.Resource, dxSrc.Subresource(srcSubresource), DXFormats.DirectX12(dxDst.Desc.Format)); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 18222262..ac745fdf 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -47,6 +47,11 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr View.StorageHandle; + public uint Subresource(TextureSubresource textureSubresource) + { + return (textureSubresource.MipLevel * Desc.ArrayLayers) + textureSubresource.ArrayLayer; + } + public override nint GetNativeObject(NativeObjectType type) { return 0; From 7f82c2e6235ba335ef94caf60ea02f3004d7d618 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 13:51:30 +0800 Subject: [PATCH 163/466] Refactor DX12 copy/resolve ops and subresource indexing Refactored DXCommandBuffer to implement buffer and texture copy operations using DirectX 12 API calls. Replaced Subresource with SubresourceIndex in DXTexture for clearer subresource handling and updated all usages, including ResolveTextureImpl, for consistency and correctness. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 26 ++++++++++++++++++- sources/Zenith.NET.DirectX12/DXTexture.cs | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 09356a69..590287ad 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -31,6 +31,10 @@ protected override void BarrierImpl(ReadOnlySpan memoryBarriers, protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { + DXBuffer dxSrc = src.DirectX12(); + DXBuffer dxDst = dst.DirectX12(); + + CommandList.CopyBufferRegion(dxDst.Resource, dstOffsetInBytes, dxSrc.Resource, srcOffsetInBytes, sizeInBytes); } protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) @@ -39,6 +43,26 @@ protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInByte protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { + DXTexture dxSrc = src.DirectX12(); + DXTexture dxDst = dst.DirectX12(); + + TextureCopyLocation srcLocation = new() + { + PResource = dxSrc.Resource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = dxSrc.SubresourceIndex(srcSubresource) + }; + + Box srcBox = new(srcOffset.X, srcOffset.Y, srcOffset.Z, srcOffset.X + extent.Width, srcOffset.Y + extent.Height, srcOffset.Z + extent.Depth); + + TextureCopyLocation dstLocation = new() + { + PResource = dxDst.Resource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = dxDst.SubresourceIndex(dstSubresource) + }; + + CommandList.CopyTextureRegion(&dstLocation, dstOffset.X, dstOffset.Y, dstOffset.Z, &srcLocation, &srcBox); } protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) @@ -50,7 +74,7 @@ protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSu DXTexture dxSrc = src.DirectX12(); DXTexture dxDst = dst.DirectX12(); - CommandList.ResolveSubresource(dxDst.Resource, dxDst.Subresource(dstSubresource), dxSrc.Resource, dxSrc.Subresource(srcSubresource), DXFormats.DirectX12(dxDst.Desc.Format)); + CommandList.ResolveSubresource(dxDst.Resource, dxDst.SubresourceIndex(dstSubresource), dxSrc.Resource, dxSrc.SubresourceIndex(srcSubresource), DXFormats.DirectX12(dxDst.Desc.Format)); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index ac745fdf..9d39767f 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -47,7 +47,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr View.StorageHandle; - public uint Subresource(TextureSubresource textureSubresource) + public uint SubresourceIndex(TextureSubresource textureSubresource) { return (textureSubresource.MipLevel * Desc.ArrayLayers) + textureSubresource.ArrayLayer; } From 9e08c61d6fcdc7c6ed221c5fc57b64a5adb9e5cc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 14:55:05 +0800 Subject: [PATCH 164/466] Update PipelineStages enum bitmask assignments Reassigned bitmask values for AllGraphics, AllCommands, Copy, and Resolve in the PipelineStages enum. AllGraphics is now a single bit value instead of a composite flag. Adjusted positions and values for AllCommands and Copy to maintain unique bit assignments. --- sources/Zenith.NET/Enums/PipelineStages.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET/Enums/PipelineStages.cs b/sources/Zenith.NET/Enums/PipelineStages.cs index 3516bac2..4f404775 100644 --- a/sources/Zenith.NET/Enums/PipelineStages.cs +++ b/sources/Zenith.NET/Enums/PipelineStages.cs @@ -3,8 +3,6 @@ [Flags] public enum PipelineStages { - AllCommands = ~0, - None = 0, Indirect = 1 << 0, @@ -25,13 +23,15 @@ public enum PipelineStages ColorAttachmentOutput = 1 << 8, - AllGraphics = Indirect | VertexInput | VertexShader | TaskShader | MeshShader | EarlyFragmentTests | FragmentShader | LateFragmentTests | ColorAttachmentOutput, - ComputeShader = 1 << 9, AccelerationStructureBuild = 1 << 10, - Copy = 1 << 11, + AllGraphics = 1 << 11, + + AllCommands = 1 << 12, + + Copy = 1 << 13, - Resolve = 1 << 12 + Resolve = 1 << 14 } From 5bf4691a4fbc08eb3d8fe3c407c58dcd058f3695 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 30 May 2026 15:06:26 +0800 Subject: [PATCH 165/466] Implement DirectX 12 barrier translation and mappings Added BarrierImpl to DXCommandBuffer to convert engine barriers to DirectX 12 structures. Introduced DXFormats mapping methods for pipeline stages, resource access, and texture layouts. Updated global usings for buffer and texture barriers. Reordered PipelineStages enum and added Copy/Resolve stages. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 78 +++++++ sources/Zenith.NET.DirectX12/DXFormats.cs | 194 ++++++++++++++++++ sources/Zenith.NET.DirectX12/Usings.cs | 2 + sources/Zenith.NET/Enums/PipelineStages.cs | 8 +- 4 files changed, 278 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 590287ad..7ecc5953 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -27,6 +27,84 @@ public override nint GetNativeObject(NativeObjectType type) protected override void BarrierImpl(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) { + GlobalBarrier* pGlobalBarriers = stackalloc GlobalBarrier[memoryBarriers.Length]; + for (int i = 0; i < memoryBarriers.Length; i++) + { + MemoryBarrier barrier = memoryBarriers[i]; + + pGlobalBarriers[i] = new() + { + SyncBefore = DXFormats.DirectX12(barrier.SrcStages), + SyncAfter = DXFormats.DirectX12(barrier.DstStages), + AccessBefore = DXFormats.DirectX12(barrier.SrcAccess), + AccessAfter = DXFormats.DirectX12(barrier.DstAccess) + }; + } + + DxBufferBarrier* pBufferBarriers = stackalloc DxBufferBarrier[bufferBarriers.Length]; + for (int i = 0; i < bufferBarriers.Length; i++) + { + BufferBarrier barrier = bufferBarriers[i]; + + pBufferBarriers[i] = new() + { + SyncBefore = DXFormats.DirectX12(barrier.SrcStages), + SyncAfter = DXFormats.DirectX12(barrier.DstStages), + AccessBefore = DXFormats.DirectX12(barrier.SrcAccess), + AccessAfter = DXFormats.DirectX12(barrier.DstAccess), + PResource = barrier.Buffer.DirectX12().Resource, + Size = ulong.MaxValue + }; + } + + DxTextureBarrier* pTextureBarriers = stackalloc DxTextureBarrier[textureBarriers.Length]; + for (int i = 0; i < textureBarriers.Length; i++) + { + TextureBarrier barrier = textureBarriers[i]; + + pTextureBarriers[i] = new() + { + SyncBefore = DXFormats.DirectX12(barrier.SrcStages), + SyncAfter = DXFormats.DirectX12(barrier.DstStages), + AccessBefore = DXFormats.DirectX12(barrier.SrcAccess), + AccessAfter = DXFormats.DirectX12(barrier.DstAccess), + LayoutBefore = DXFormats.DirectX12(barrier.SrcLayout), + LayoutAfter = DXFormats.DirectX12(barrier.DstLayout), + PResource = barrier.Texture.DirectX12().Resource, + Subresources = new() + { + IndexOrFirstMipLevel = barrier.Range.BaseMipLevel, + NumMipLevels = barrier.Range.LevelCount, + FirstArraySlice = barrier.Range.BaseArrayLayer, + NumArraySlices = barrier.Range.LayerCount, + NumPlanes = 1 + } + }; + } + + BarrierGroup* pBarrierGroups = stackalloc BarrierGroup[] + { + new() + { + Type = BarrierType.Global, + NumBarriers = (uint)memoryBarriers.Length, + PGlobalBarriers = pGlobalBarriers + }, + new() + { + Type = BarrierType.Buffer, + NumBarriers = (uint)bufferBarriers.Length, + PBufferBarriers = pBufferBarriers + }, + new() + { + Type = BarrierType.Texture, + NumBarriers = (uint)textureBarriers.Length, + PTextureBarriers = pTextureBarriers + } + }; + + CommandList.Barrier(3, pBarrierGroups); } protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 686efb2d..7fdda46b 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -452,4 +452,198 @@ public static Format DirectX12(IndexFormat indexFormat) _ => default }; } + + public static BarrierSync DirectX12(PipelineStages pipelineStages) + { + BarrierSync result = BarrierSync.None; + + if (pipelineStages.HasFlag(PipelineStages.Indirect)) + { + result |= BarrierSync.ExecuteIndirect; + } + + if (pipelineStages.HasFlag(PipelineStages.VertexInput)) + { + result |= BarrierSync.IndexInput | BarrierSync.VertexShading; + } + + if (pipelineStages.HasFlag(PipelineStages.VertexShader)) + { + result |= BarrierSync.VertexShading; + } + + if (pipelineStages.HasFlag(PipelineStages.TaskShader)) + { + result |= BarrierSync.VertexShading; + } + + if (pipelineStages.HasFlag(PipelineStages.MeshShader)) + { + result |= BarrierSync.VertexShading; + } + + if (pipelineStages.HasFlag(PipelineStages.EarlyFragmentTests)) + { + result |= BarrierSync.DepthStencil; + } + + if (pipelineStages.HasFlag(PipelineStages.FragmentShader)) + { + result |= BarrierSync.PixelShading; + } + + if (pipelineStages.HasFlag(PipelineStages.LateFragmentTests)) + { + result |= BarrierSync.DepthStencil; + } + + if (pipelineStages.HasFlag(PipelineStages.ColorAttachmentOutput)) + { + result |= BarrierSync.RenderTarget; + } + + if (pipelineStages.HasFlag(PipelineStages.ComputeShader)) + { + result |= BarrierSync.ComputeShading; + } + + if (pipelineStages.HasFlag(PipelineStages.AccelerationStructureBuild)) + { + result |= BarrierSync.BuildRaytracingAccelerationStructure; + } + + if (pipelineStages.HasFlag(PipelineStages.Copy)) + { + result |= BarrierSync.Copy; + } + + if (pipelineStages.HasFlag(PipelineStages.Resolve)) + { + result |= BarrierSync.Resolve; + } + + if (pipelineStages.HasFlag(PipelineStages.AllGraphics)) + { + result |= BarrierSync.AllShading; + } + + if (pipelineStages.HasFlag(PipelineStages.AllCommands)) + { + result |= BarrierSync.All; + } + + return result; + } + + public static BarrierAccess DirectX12(ResourceAccess resourceAccess) + { + if (resourceAccess is ResourceAccess.None) + { + return BarrierAccess.NoAccess; + } + + BarrierAccess result = BarrierAccess.Common; + + if (resourceAccess.HasFlag(ResourceAccess.Vertex)) + { + result |= BarrierAccess.VertexBuffer; + } + + if (resourceAccess.HasFlag(ResourceAccess.Index)) + { + result |= BarrierAccess.IndexBuffer; + } + + if (resourceAccess.HasFlag(ResourceAccess.Indirect)) + { + result |= BarrierAccess.IndirectArgument; + } + + if (resourceAccess.HasFlag(ResourceAccess.Constant)) + { + result |= BarrierAccess.ConstantBuffer; + } + + if (resourceAccess.HasFlag(ResourceAccess.ShaderRead)) + { + result |= BarrierAccess.ShaderResource; + } + + if (resourceAccess.HasFlag(ResourceAccess.ShaderWrite)) + { + result |= BarrierAccess.UnorderedAccess; + } + + if (resourceAccess.HasFlag(ResourceAccess.AccelerationStructureRead)) + { + result |= BarrierAccess.RaytracingAccelerationStructureRead; + } + + if (resourceAccess.HasFlag(ResourceAccess.AccelerationStructureWrite)) + { + result |= BarrierAccess.RaytracingAccelerationStructureWrite; + } + + if (resourceAccess.HasFlag(ResourceAccess.ColorAttachmentRead)) + { + result |= BarrierAccess.RenderTarget; + } + + if (resourceAccess.HasFlag(ResourceAccess.ColorAttachmentWrite)) + { + result |= BarrierAccess.RenderTarget; + } + + if (resourceAccess.HasFlag(ResourceAccess.DepthStencilAttachmentRead)) + { + result |= BarrierAccess.DepthStencilRead; + } + + if (resourceAccess.HasFlag(ResourceAccess.DepthStencilAttachmentWrite)) + { + result |= BarrierAccess.DepthStencilWrite; + } + + if (resourceAccess.HasFlag(ResourceAccess.CopyRead)) + { + result |= BarrierAccess.CopySource; + } + + if (resourceAccess.HasFlag(ResourceAccess.CopyWrite)) + { + result |= BarrierAccess.CopyDest; + } + + if (resourceAccess.HasFlag(ResourceAccess.ResolveRead)) + { + result |= BarrierAccess.ResolveSource; + } + + if (resourceAccess.HasFlag(ResourceAccess.ResolveWrite)) + { + result |= BarrierAccess.ResolveDest; + } + + return result; + } + + public static BarrierLayout DirectX12(TextureLayout textureLayout) + { + return textureLayout switch + { + TextureLayout.Undefined => BarrierLayout.Undefined, + TextureLayout.General => BarrierLayout.Common, + TextureLayout.Sampled => BarrierLayout.ShaderResource, + TextureLayout.Storage => BarrierLayout.UnorderedAccess, + TextureLayout.ColorAttachment => BarrierLayout.RenderTarget, + TextureLayout.DepthStencilAttachment => BarrierLayout.DepthStencilWrite, + TextureLayout.DepthStencilReadOnly => BarrierLayout.DepthStencilRead, + TextureLayout.CopySrc => BarrierLayout.CopySource, + TextureLayout.CopyDst => BarrierLayout.CopyDest, + TextureLayout.ResolveSrc => BarrierLayout.ResolveSource, + TextureLayout.ResolveDst => BarrierLayout.ResolveDest, + TextureLayout.Present => BarrierLayout.Present, + _ => default + }; + } } diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 914fe643..25539438 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,4 +1,5 @@ global using DxBlendOp = Silk.NET.Direct3D12.BlendOp; +global using DxBufferBarrier = Silk.NET.Direct3D12.BufferBarrier; global using DxCullMode = Silk.NET.Direct3D12.CullMode; global using DxFillMode = Silk.NET.Direct3D12.FillMode; global using DxHeapDesc = Silk.NET.Direct3D12.HeapDesc; @@ -9,5 +10,6 @@ global using DxRange = Silk.NET.Direct3D12.Range; global using DxSamplerDesc = Silk.NET.Direct3D12.SamplerDesc; global using DxStencilOp = Silk.NET.Direct3D12.StencilOp; +global using DxTextureBarrier = Silk.NET.Direct3D12.TextureBarrier; global using DxTextureLayout = Silk.NET.Direct3D12.TextureLayout; global using DxViewport = Silk.NET.Direct3D12.Viewport; diff --git a/sources/Zenith.NET/Enums/PipelineStages.cs b/sources/Zenith.NET/Enums/PipelineStages.cs index 4f404775..47472315 100644 --- a/sources/Zenith.NET/Enums/PipelineStages.cs +++ b/sources/Zenith.NET/Enums/PipelineStages.cs @@ -27,11 +27,11 @@ public enum PipelineStages AccelerationStructureBuild = 1 << 10, - AllGraphics = 1 << 11, + Copy = 1 << 11, - AllCommands = 1 << 12, + Resolve = 1 << 12, - Copy = 1 << 13, + AllGraphics = 1 << 13, - Resolve = 1 << 14 + AllCommands = 1 << 14 } From ef8550ee16cec23af21696e2d8601a2ef7c05432 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 31 May 2026 10:22:01 +0800 Subject: [PATCH 166/466] Refactor texture data layout handling and API usage Flatten TextureData by inlining size and stride fields, removing TextureDataLayout. Update all usages and method signatures to pass layout info directly. Simplifies code and improves clarity. --- .../ImGuiRenderer.cs | 36 +++++++------------ .../Extensions.cs | 18 ++++------ .../Zenith.NET.Views.Avalonia/Surface.cs | 9 ++--- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 9 ++--- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 4 +-- sources/Zenith.NET/CommandBuffer.cs | 22 ++++++------ sources/Zenith.NET/Structs/TextureData.cs | 6 +++- .../Zenith.NET/Structs/TextureDataLayout.cs | 10 ------ 8 files changed, 42 insertions(+), 72 deletions(-) delete mode 100644 sources/Zenith.NET/Structs/TextureDataLayout.cs diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 43371ac4..cb1ef1cf 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -170,12 +170,9 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, TextureData data = new() { Pointer = (nint)textureData.Pixels, - Layout = new() - { - SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), - RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), - SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) - } + SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) }; commandBuffer.Upload(texture, default, default, extent, data); @@ -185,12 +182,9 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, TextureData data = new() { Pointer = (nint)textureData.Pixels, - Layout = new() - { - SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), - RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), - SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) - } + SizeInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format, extent.Width, extent.Height), + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) }; commandBuffer.Upload(texture, default, default, extent, data); @@ -228,12 +222,9 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, TextureData data = new() { Pointer = (nint)pointer, - Layout = new() - { - SizeInBytes = (uint)(sizeof(int) * rect.W * rect.H), - RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), - SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) - } + SizeInBytes = (uint)(sizeof(int) * rect.W * rect.H), + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) }; commandBuffer.Upload(texture, default, offset, extent, data); @@ -250,12 +241,9 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, TextureData data = new() { Pointer = (nint)pointer, - Layout = new() - { - SizeInBytes = (uint)(rect.W * rect.H), - RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), - SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) - } + SizeInBytes = (uint)(rect.W * rect.H), + RowStrideInBytes = ZenithHelper.RowStrideInBytes(texture.Desc.Format, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(texture.Desc.Format, extent.Width, extent.Height) }; commandBuffer.Upload(texture, default, offset, extent, data); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index ab4f8fa4..9d5792d5 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -34,12 +34,9 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) TextureData data = new() { Pointer = (nint)pPixels, - Layout = new() - { - SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), - RowStrideInBytes = ZenithHelper.RowStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), - SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) - } + SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), + RowStrideInBytes = ZenithHelper.RowStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) }; commandBuffer.Upload(texture, default, default, extent, data); @@ -61,12 +58,9 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) TextureData data = new() { Pointer = (nint)pPixels, - Layout = new() - { - SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), - RowStrideInBytes = ZenithHelper.RowStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), - SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) - } + SizeInBytes = (uint)(sizeof(Rgba32) * pixels.Length), + RowStrideInBytes = ZenithHelper.RowStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height), + SliceStrideInBytes = ZenithHelper.SliceStrideInBytes(PixelFormat.R8G8B8A8UNorm, extent.Width, extent.Height) }; commandBuffer.Upload(texture, new() { MipLevel = i }, default, extent, data); diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 0b11795a..0d89c97c 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -21,12 +21,9 @@ public void Present() Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() { Pointer = lockedFramebuffer.Address, - Layout = new() - { - SizeInBytes = (uint)(lockedFramebuffer.RowBytes * Height), - RowStrideInBytes = (uint)lockedFramebuffer.RowBytes, - SliceStrideInBytes = (uint)(lockedFramebuffer.RowBytes * Height) - } + SizeInBytes = (uint)(lockedFramebuffer.RowBytes * Height), + RowStrideInBytes = (uint)lockedFramebuffer.RowBytes, + SliceStrideInBytes = (uint)(lockedFramebuffer.RowBytes * Height) }); } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index 338bac84..df226d1e 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -69,12 +69,9 @@ public void Present() Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() { Pointer = (nint)pPixels, - Layout = new() - { - SizeInBytes = (uint)pixels.Length, - RowStrideInBytes = Width * 4, - SliceStrideInBytes = (uint)pixels.Length - } + SizeInBytes = (uint)pixels.Length, + RowStrideInBytes = Width * 4, + SliceStrideInBytes = (uint)pixels.Length }); } diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 7ecc5953..642ca658 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -115,7 +115,7 @@ protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer CommandList.CopyBufferRegion(dxDst.Resource, dstOffsetInBytes, dxSrc.Resource, srcOffsetInBytes, sizeInBytes); } - protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { } @@ -143,7 +143,7 @@ protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubre CommandList.CopyTextureRegion(&dstLocation, dstOffset.X, dstOffset.Y, dstOffset.Z, &srcLocation, &srcBox); } - protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) + protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { } diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index be6114cb..b5cad989 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -35,17 +35,17 @@ public void Download(Buffer buffer, uint offsetInBytes, BufferData data) public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer stagingBuffer = Context.Uploader.Buffer(this, data.Layout.SizeInBytes); - stagingBuffer.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); + Buffer stagingBuffer = Context.Uploader.Buffer(this, data.SizeInBytes); + stagingBuffer.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.SizeInBytes }); - CopyBufferToTexture(stagingBuffer, 0, data.Layout, texture, subresource, offset, extent); + // TODO } public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer stagingBuffer = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.Layout.SizeInBytes }); + Buffer stagingBuffer = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.SizeInBytes }); - CopyTextureToBuffer(texture, subresource, offset, extent, stagingBuffer, 0, data.Layout); + // TODO } public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) @@ -53,9 +53,9 @@ public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOf CopyBufferImpl(src, srcOffsetInBytes, dst, dstOffsetInBytes, sizeInBytes); } - public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - CopyBufferToTextureImpl(src, srcOffsetInBytes, srcLayout, dst, dstSubresource, dstOffset, dstExtent); + CopyBufferToTextureImpl(src, srcOffsetInBytes, srcRowStrideInBytes, srcSliceStrideInBytes, dst, dstSubresource, dstOffset, dstExtent); } public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) @@ -63,9 +63,9 @@ public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D CopyTextureImpl(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent); } - public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout) + public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { - CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstLayout); + CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstRowStrideInBytes, dstSliceStrideInBytes); } public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) @@ -378,11 +378,11 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes); - protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, TextureDataLayout srcLayout, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent); + protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent); protected abstract void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent); - protected abstract void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, TextureDataLayout dstLayout); + protected abstract void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes); protected abstract void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource); diff --git a/sources/Zenith.NET/Structs/TextureData.cs b/sources/Zenith.NET/Structs/TextureData.cs index 9f4a7c5d..fe6e0116 100644 --- a/sources/Zenith.NET/Structs/TextureData.cs +++ b/sources/Zenith.NET/Structs/TextureData.cs @@ -4,5 +4,9 @@ public struct TextureData { public nint Pointer; - public TextureDataLayout Layout; + public uint SizeInBytes; + + public uint RowStrideInBytes; + + public uint SliceStrideInBytes; } diff --git a/sources/Zenith.NET/Structs/TextureDataLayout.cs b/sources/Zenith.NET/Structs/TextureDataLayout.cs deleted file mode 100644 index 3121f013..00000000 --- a/sources/Zenith.NET/Structs/TextureDataLayout.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Zenith.NET; - -public struct TextureDataLayout -{ - public uint SizeInBytes; - - public uint RowStrideInBytes; - - public uint SliceStrideInBytes; -} From ab76888301ed88f40ae77e1e4bbb1775f6b9ae9a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 31 May 2026 15:34:02 +0800 Subject: [PATCH 167/466] Implement buffer-texture copy methods in DXCommandBuffer Add CopyBufferToTextureImpl and CopyTextureToBufferImpl using DirectX 12's CopyTextureRegion. These methods configure TextureCopyLocation for both buffers and textures, supporting placed footprints, subresource indices, and correct offset and extent handling for data transfers. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 642ca658..64957bfd 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -117,6 +117,35 @@ protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { + DXBuffer dxSrc = src.DirectX12(); + DXTexture dxDst = dst.DirectX12(); + + TextureCopyLocation srcLocation = new() + { + PResource = dxSrc.Resource, + Type = TextureCopyType.PlacedFootprint, + PlacedFootprint = new() + { + Offset = srcOffsetInBytes, + Footprint = new() + { + Format = DXFormats.DirectX12(dxDst.Desc.Format), + Width = dstExtent.Width, + Height = dstExtent.Height, + Depth = dstExtent.Depth, + RowPitch = srcRowStrideInBytes + } + } + }; + + TextureCopyLocation dstLocation = new() + { + PResource = dxDst.Resource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = dxDst.SubresourceIndex(dstSubresource) + }; + + CommandList.CopyTextureRegion(&dstLocation, dstOffset.X, dstOffset.Y, dstOffset.Z, &srcLocation, default(Box*)); } protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) @@ -145,6 +174,37 @@ protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubre protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { + DXTexture dxSrc = src.DirectX12(); + DXBuffer dxDst = dst.DirectX12(); + + TextureCopyLocation srcLocation = new() + { + PResource = dxSrc.Resource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = dxSrc.SubresourceIndex(srcSubresource) + }; + + Box srcBox = new(srcOffset.X, srcOffset.Y, srcOffset.Z, srcOffset.X + srcExtent.Width, srcOffset.Y + srcExtent.Height, srcOffset.Z + srcExtent.Depth); + + TextureCopyLocation dstLocation = new() + { + PResource = dxDst.Resource, + Type = TextureCopyType.PlacedFootprint, + PlacedFootprint = new() + { + Offset = dstOffsetInBytes, + Footprint = new() + { + Format = DXFormats.DirectX12(dxSrc.Desc.Format), + Width = srcExtent.Width, + Height = srcExtent.Height, + Depth = srcExtent.Depth, + RowPitch = dstRowStrideInBytes + } + } + }; + + CommandList.CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, &srcBox); } protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) From 4bf6ac64f959492b0730f6546de63f536d742a10 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 31 May 2026 15:57:04 +0800 Subject: [PATCH 168/466] Handle row/depth pitch and block layouts in buffer/texture IO Refactor Download/Upload methods to support row/depth pitch alignment and block-based texture layouts. Replace BufferData with DownloadLayout for flexible memory layout in Downloader and Lease. Add DownloadLayout struct to manage pointer, row size, strides, and row count, improving support for non-linear and block-compressed formats. --- sources/Zenith.NET/CommandBuffer.cs | 71 ++++++++++++++++++++++++++--- sources/Zenith.NET/Downloader.cs | 41 +++++++++++++---- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index b5cad989..eb1acf7d 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -28,24 +28,83 @@ public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) public void Download(Buffer buffer, uint offsetInBytes, BufferData data) { - Buffer stagingBuffer = Context.Downloader.Buffer(this, data); + Buffer stagingBuffer = Context.Downloader.Buffer(this, data.SizeInBytes, new() + { + Pointer = data.Pointer, + RowSizeInBytes = data.SizeInBytes, + SrcRowStrideInBytes = data.SizeInBytes, + DstRowStrideInBytes = data.SizeInBytes, + Rows = 1 + }); CopyBuffer(buffer, offsetInBytes, stagingBuffer, 0, data.SizeInBytes); } public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer stagingBuffer = Context.Uploader.Buffer(this, data.SizeInBytes); - stagingBuffer.Upload(0, new() { Pointer = data.Pointer, SizeInBytes = data.SizeInBytes }); + const uint RowPitchAlignment = 256; + const uint DepthPitchAlignment = 512; + + (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(texture.Desc.Format, extent.Width, extent.Height); + + uint rowPitchInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format) * blocksWide; + + uint alignedRowPitchInBytes = ZenithHelper.Align(rowPitchInBytes, RowPitchAlignment); + uint alignedDepthPitchInBytes = ZenithHelper.Align(alignedRowPitchInBytes * blocksHigh, DepthPitchAlignment); + + Extent3D sliceExtent = extent with { Depth = 1 }; + + for (uint i = 0; i < extent.Depth; i++) + { + Buffer stagingBuffer = Context.Uploader.Buffer(this, alignedDepthPitchInBytes); + + MappedMemory mappedMemory = stagingBuffer.Map(); + + unsafe + { + for (uint j = 0; j < blocksHigh; j++) + { + new ReadOnlySpan((void*)(data.Pointer + (data.SliceStrideInBytes * i) + (data.RowStrideInBytes * j)), (int)rowPitchInBytes).CopyTo(new((void*)(mappedMemory.Pointer + (alignedRowPitchInBytes * j)), (int)rowPitchInBytes)); + } + } - // TODO + stagingBuffer.Unmap(); + + CopyBufferToTexture(stagingBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes, texture, subresource, offset, sliceExtent); + + offset.Z++; + } } public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - Buffer stagingBuffer = Context.Downloader.Buffer(this, new() { Pointer = data.Pointer, SizeInBytes = data.SizeInBytes }); + const uint RowPitchAlignment = 256; + const uint DepthPitchAlignment = 512; + + (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(texture.Desc.Format, extent.Width, extent.Height); + + uint rowPitchInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format) * blocksWide; + + uint alignedRowPitchInBytes = ZenithHelper.Align(rowPitchInBytes, RowPitchAlignment); + uint alignedDepthPitchInBytes = ZenithHelper.Align(alignedRowPitchInBytes * blocksHigh, DepthPitchAlignment); - // TODO + Extent3D sliceExtent = extent with { Depth = 1 }; + + for (uint i = 0; i < extent.Depth; i++) + { + Buffer stagingBuffer = Context.Downloader.Buffer(this, alignedDepthPitchInBytes, new() + { + Pointer = (nint)(data.Pointer + (data.SliceStrideInBytes * i)), + RowSizeInBytes = rowPitchInBytes, + SrcRowStrideInBytes = alignedRowPitchInBytes, + DstRowStrideInBytes = data.RowStrideInBytes, + Rows = blocksHigh + }); + + CopyTextureToBuffer(texture, subresource, offset, sliceExtent, stagingBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes); + + offset.Z++; + } } public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index 3e3c2e1e..e18a40d6 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -8,7 +8,7 @@ internal class Downloader(GraphicsContext context) : DisposableObject private readonly List available = []; private readonly Dictionary> borrowed = []; - public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) + public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, DownloadLayout layout) { using Lock.Scope _ = @lock.EnterScope(); @@ -17,17 +17,17 @@ public Buffer Buffer(CommandBuffer commandBuffer, BufferData data) borrowed[commandBuffer] = leases = []; } - if (!(available.Where(item => item.HasCapacityFor(data.SizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) + if (!(available.Where(item => item.HasCapacityFor(sizeInBytes)).MinBy(static item => item.Buffer.Desc.SizeInBytes) is Lease lease && available.Remove(lease))) { lease = new(context.CreateBuffer(new() { - SizeInBytes = data.SizeInBytes, + SizeInBytes = sizeInBytes, Usages = BufferUsages.CopyDst, Residency = MemoryResidency.CpuReadOnly })); } - leases.Add(lease.Borrow(data)); + leases.Add(lease.Borrow(layout)); return lease.Buffer; } @@ -70,7 +70,7 @@ private class Lease(Buffer buffer) { private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; - private BufferData data; + private DownloadLayout layout; public Buffer Buffer { get; } = buffer; @@ -79,16 +79,26 @@ public bool HasCapacityFor(uint sizeInBytes) return Buffer.Desc.SizeInBytes >= sizeInBytes; } - public Lease Borrow(BufferData newData) + public Lease Borrow(DownloadLayout newLayout) { - data = newData; + layout = newLayout; return this; } public Lease Writeback() { - Buffer.Download(0, data); + MappedMemory mappedMemory = Buffer.Map(); + + unsafe + { + for (uint row = 0; row < layout.Rows; row++) + { + new ReadOnlySpan((void*)(mappedMemory.Pointer + (layout.SrcRowStrideInBytes * row)), (int)layout.RowSizeInBytes).CopyTo(new((void*)(layout.Pointer + (layout.DstRowStrideInBytes * row)), (int)layout.RowSizeInBytes)); + } + } + + Buffer.Unmap(); return this; } @@ -109,7 +119,7 @@ public Lease Renew() { expirationTime = DateTime.UtcNow + LeaseLifetime; - data = default; + layout = default; return this; } @@ -120,3 +130,16 @@ public void Release() } } } + +internal struct DownloadLayout +{ + public nint Pointer; + + public uint RowSizeInBytes; + + public uint SrcRowStrideInBytes; + + public uint DstRowStrideInBytes; + + public uint Rows; +} From 2944f319c244c1e81d414a230bff37ddec47ed7e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 31 May 2026 16:27:50 +0800 Subject: [PATCH 169/466] Refactor buffer transfer logic to use TransferLayout Replace DownloadLayout with new TransferLayout struct, unifying upload and download logic for buffers and textures. Update CommandBuffer, Uploader, and Downloader to use TransferLayout for memory mapping and data transfer, improving maintainability and reducing code duplication. --- sources/Zenith.NET/CommandBuffer.cs | 41 +++++++++--------- sources/Zenith.NET/Downloader.cs | 41 +++--------------- sources/Zenith.NET/Structs/TransferLayout.cs | 44 ++++++++++++++++++++ sources/Zenith.NET/Uploader.cs | 11 ++++- 4 files changed, 80 insertions(+), 57 deletions(-) create mode 100644 sources/Zenith.NET/Structs/TransferLayout.cs diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index eb1acf7d..9db79748 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -20,15 +20,21 @@ public void Barrier(ReadOnlySpan memoryBarriers, ReadOnlySpan((void*)(data.Pointer + (data.SliceStrideInBytes * i) + (data.RowStrideInBytes * j)), (int)rowPitchInBytes).CopyTo(new((void*)(mappedMemory.Pointer + (alignedRowPitchInBytes * j)), (int)rowPitchInBytes)); - } - } - - stagingBuffer.Unmap(); + Pointer = (nint)(data.Pointer + (data.SliceStrideInBytes * i)), + RowSizeInBytes = rowPitchInBytes, + SrcRowStrideInBytes = data.RowStrideInBytes, + DstRowStrideInBytes = alignedRowPitchInBytes, + Rows = blocksHigh + }); - CopyBufferToTexture(stagingBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes, texture, subresource, offset, sliceExtent); + CopyBufferToTexture(transferBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes, texture, subresource, offset, sliceExtent); offset.Z++; } @@ -92,7 +93,7 @@ public void Download(Texture texture, TextureSubresource subresource, Offset3D o for (uint i = 0; i < extent.Depth; i++) { - Buffer stagingBuffer = Context.Downloader.Buffer(this, alignedDepthPitchInBytes, new() + Buffer transferBuffer = Context.Downloader.Buffer(this, alignedDepthPitchInBytes, new() { Pointer = (nint)(data.Pointer + (data.SliceStrideInBytes * i)), RowSizeInBytes = rowPitchInBytes, @@ -101,7 +102,7 @@ public void Download(Texture texture, TextureSubresource subresource, Offset3D o Rows = blocksHigh }); - CopyTextureToBuffer(texture, subresource, offset, sliceExtent, stagingBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes); + CopyTextureToBuffer(texture, subresource, offset, sliceExtent, transferBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes); offset.Z++; } diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index e18a40d6..75cfb04d 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -8,7 +8,7 @@ internal class Downloader(GraphicsContext context) : DisposableObject private readonly List available = []; private readonly Dictionary> borrowed = []; - public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, DownloadLayout layout) + public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, TransferLayout layout) { using Lock.Scope _ = @lock.EnterScope(); @@ -42,7 +42,7 @@ public void Release(CommandBuffer commandBuffer) { foreach (Lease lease in leases) { - available.Add(lease.Writeback().Renew()); + available.Add(lease.Renew()); } } } @@ -70,7 +70,7 @@ private class Lease(Buffer buffer) { private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; - private DownloadLayout layout; + private TransferLayout layout; public Buffer Buffer { get; } = buffer; @@ -79,26 +79,9 @@ public bool HasCapacityFor(uint sizeInBytes) return Buffer.Desc.SizeInBytes >= sizeInBytes; } - public Lease Borrow(DownloadLayout newLayout) + public Lease Borrow(TransferLayout layout) { - layout = newLayout; - - return this; - } - - public Lease Writeback() - { - MappedMemory mappedMemory = Buffer.Map(); - - unsafe - { - for (uint row = 0; row < layout.Rows; row++) - { - new ReadOnlySpan((void*)(mappedMemory.Pointer + (layout.SrcRowStrideInBytes * row)), (int)layout.RowSizeInBytes).CopyTo(new((void*)(layout.Pointer + (layout.DstRowStrideInBytes * row)), (int)layout.RowSizeInBytes)); - } - } - - Buffer.Unmap(); + this.layout = layout; return this; } @@ -119,6 +102,7 @@ public Lease Renew() { expirationTime = DateTime.UtcNow + LeaseLifetime; + layout.Download(Buffer); layout = default; return this; @@ -130,16 +114,3 @@ public void Release() } } } - -internal struct DownloadLayout -{ - public nint Pointer; - - public uint RowSizeInBytes; - - public uint SrcRowStrideInBytes; - - public uint DstRowStrideInBytes; - - public uint Rows; -} diff --git a/sources/Zenith.NET/Structs/TransferLayout.cs b/sources/Zenith.NET/Structs/TransferLayout.cs new file mode 100644 index 00000000..6e12611b --- /dev/null +++ b/sources/Zenith.NET/Structs/TransferLayout.cs @@ -0,0 +1,44 @@ +namespace Zenith.NET; + +internal struct TransferLayout +{ + public nint Pointer; + + public uint RowSizeInBytes; + + public uint SrcRowStrideInBytes; + + public uint DstRowStrideInBytes; + + public uint Rows; + + public readonly void Upload(Buffer buffer) + { + MappedMemory mappedMemory = buffer.Map(); + + unsafe + { + for (uint row = 0; row < Rows; row++) + { + new ReadOnlySpan((void*)(Pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(mappedMemory.Pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); + } + } + + buffer.Unmap(); + } + + public readonly void Download(Buffer buffer) + { + MappedMemory mappedMemory = buffer.Map(); + + unsafe + { + for (uint row = 0; row < Rows; row++) + { + new ReadOnlySpan((void*)(mappedMemory.Pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(Pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); + } + } + + buffer.Unmap(); + } +} diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index 02d6bb5c..e3048d7f 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -8,7 +8,7 @@ internal class Uploader(GraphicsContext context) : DisposableObject private readonly List available = []; private readonly Dictionary> borrowed = []; - public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) + public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, TransferLayout layout) { using Lock.Scope _ = @lock.EnterScope(); @@ -27,7 +27,7 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) })); } - leases.Add(lease); + leases.Add(lease.Borrow(layout)); return lease.Buffer; } @@ -77,6 +77,13 @@ public bool HasCapacityFor(uint sizeInBytes) return Buffer.Desc.SizeInBytes >= sizeInBytes; } + public Lease Borrow(TransferLayout layout) + { + layout.Upload(Buffer); + + return this; + } + public bool TryExpire() { if (DateTime.UtcNow >= expirationTime) From 46b678430ca62321e901044834a777f7f092143e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 16:18:36 +0800 Subject: [PATCH 170/466] Refactor shader compilation and loading workflow Replaces CreateShaderFromFile/Source with ZenithCompiler static methods for shader compilation. Updates ShaderDesc to use CodeBytes and removes EntryPoint/Stage. Removes ShaderStages enum and related logic. Refactors renderer classes to use the new compilation flow. Adds ZenithCompiler.cs to centralize shader compilation logic per graphics API. Updates ImGuiRenderer shader source to use a raw string literal. --- .../Assets/Shaders/PathTracing.slang | 1 + .../Assets/Shaders/Rasterization.slang | 2 + .../Renderers/PathTracingRenderer.cs | 6 +- .../Renderers/RasterizationRenderer.cs | 12 ++- .../ImGuiRenderer.cs | 18 ++++- sources/Zenith.NET.DirectX12/DXShader.cs | 4 +- sources/Zenith.NET/Enums/ShaderStages.cs | 17 ----- sources/Zenith.NET/GraphicsContext.cs | 76 +------------------ sources/Zenith.NET/Structs/ShaderDesc.cs | 56 +------------- sources/Zenith.NET/ZenithCompiler.cs | 66 ++++++++++++++++ 10 files changed, 103 insertions(+), 155 deletions(-) delete mode 100644 sources/Zenith.NET/Enums/ShaderStages.cs create mode 100644 sources/Zenith.NET/ZenithCompiler.cs diff --git a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang index 2e5364a0..5dd2780c 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang @@ -461,6 +461,7 @@ float Halton(uint index, uint base) return result; } +[shader("compute")] [numthreads(16, 16, 1)] void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) { diff --git a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang index 71e98889..35e8d422 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang @@ -151,6 +151,7 @@ float3 FresnelSchlick(float cosTheta, float3 F0) return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0); } +[shader("vertex")] FSInput VSMain(VSInput input) { float4 worldPos = mul(float4(input.Position, 1.0), rasterization.Model); @@ -164,6 +165,7 @@ FSInput VSMain(VSInput input) return output; } +[shader("fragment")] float4 FSMain(FSInput input) : SV_TARGET { Material mat = (*rasterization.Materials)[input.MaterialId]; diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index ca303177..72cba7d4 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -61,7 +61,11 @@ public PathTracingRenderer() Residency = MemoryResidency.CpuWriteOnly }); - using Shader computeShader = App.Context.CreateShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStages.Compute); + using Shader computeShader = App.Context.CreateShader(new() + { + Name = "CSMain", + CodeBytes = ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("PathTracing.slang"), "CSMain") + }); pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index e3fb27f3..3f74ccea 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -49,8 +49,16 @@ public RasterizationRenderer() inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Normal }); - using Shader vertexShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "VSMain", ShaderStages.Vertex); - using Shader fragmentShader = App.Context.CreateShaderFromFile(ShaderPath("Rasterization.slang"), "FSMain", ShaderStages.Fragment); + using Shader vertexShader = App.Context.CreateShader(new() + { + Name = "VSMain", + CodeBytes = ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("Rasterization.slang"), "VSMain") + }); + using Shader fragmentShader = App.Context.CreateShader(new() + { + Name = "FSMain", + CodeBytes = ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("Rasterization.slang"), "FSMain") + }); pipeline = App.Context.CreateGraphicsPipeline(new() { diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index cb1ef1cf..aa3364c5 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -7,7 +7,7 @@ namespace Zenith.NET.Extensions.ImGui; internal unsafe class ImGuiRenderer : DisposableObject { - public const string Source = @" + public const string Source = """ struct VSInput { float2 Position : POSITION0; @@ -42,6 +42,7 @@ float3 SrgbToLinear(float3 srgb) return srgb * (srgb * (srgb * 0.305306011 + 0.682171111) + 0.012522878); } +[shader("vertex")] VSOutput VSMain(VSInput input) { VSOutput output; @@ -57,11 +58,12 @@ VSOutput VSMain(VSInput input) return output; } +[shader("fragment")] float4 FSMain(VSOutput input) : SV_TARGET { return input.Color * (*constants.Texture).Sample(*constants.Sampler, input.UV); } -"; +"""; private readonly Sampler sampler; private readonly GraphicsPipeline graphicsPipeline; @@ -80,8 +82,16 @@ public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormat sampler = context.CreateSampler(SamplerDesc.PointClamp()); - using Shader vertex = context.CreateShaderFromSource(source, "VSMain", ShaderStages.Vertex); - using Shader fragment = context.CreateShaderFromSource(source, "FSMain", ShaderStages.Fragment); + using Shader vertex = context.CreateShader(new() + { + Name = "VSMain", + CodeBytes = ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "VSMain") + }); + using Shader fragment = context.CreateShader(new() + { + Name = "FSMain", + CodeBytes = ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "FSMain") + }); InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.Position }); diff --git a/sources/Zenith.NET.DirectX12/DXShader.cs b/sources/Zenith.NET.DirectX12/DXShader.cs index 21207be3..f36c6cdf 100644 --- a/sources/Zenith.NET.DirectX12/DXShader.cs +++ b/sources/Zenith.NET.DirectX12/DXShader.cs @@ -8,8 +8,8 @@ public ShaderBytecode GetShaderBytecode(ZenithMarshal.Scope scope) { return new() { - PShaderBytecode = (byte*)ZenithMarshal.AllocateAndFill(scope, Desc.Bytecode), - BytecodeLength = (uint)Desc.Bytecode.Length + PShaderBytecode = (byte*)ZenithMarshal.AllocateAndFill(scope, Desc.CodeBytes), + BytecodeLength = (uint)Desc.CodeBytes.Length }; } diff --git a/sources/Zenith.NET/Enums/ShaderStages.cs b/sources/Zenith.NET/Enums/ShaderStages.cs deleted file mode 100644 index 2b736051..00000000 --- a/sources/Zenith.NET/Enums/ShaderStages.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum ShaderStages -{ - None = 0, - - Vertex = 1 << 0, - - Fragment = 1 << 1, - - Compute = 1 << 2, - - Task = 1 << 3, - - Mesh = 1 << 4 -} diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index a8af0063..578c0a34 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -1,6 +1,4 @@ -using Slangc.NET; - -namespace Zenith.NET; +namespace Zenith.NET; public abstract class GraphicsContext : DisposableObject, INativeObject { @@ -113,26 +111,6 @@ public QueryHeap CreateQueryHeap(QueryHeapDesc desc) return CreateQueryHeapImpl(desc); } - public Shader CreateShaderFromFile(string file, string entryPoint, ShaderStages stage, string[]? searchPaths = null) - { - return CreateShader(new() - { - Bytecode = SlangCompiler.Compile(SlangCompilerArguments(file, entryPoint, stage, searchPaths)), - EntryPoint = entryPoint, - Stage = stage - }); - } - - public Shader CreateShaderFromSource(string source, string entryPoint, ShaderStages stage, string[]? searchPaths = null) - { - return CreateShader(new() - { - Bytecode = SlangCompiler.Compile(source, SlangCompilerArguments(null, entryPoint, stage, searchPaths)), - EntryPoint = entryPoint, - Stage = stage - }); - } - public abstract nint GetNativeObject(NativeObjectType type); protected override void Destroy() @@ -185,56 +163,4 @@ internal void OnValidationMessage(ValidationMessageEventArgs args) { ValidationMessage?.Invoke(this, args); } - - private string[] SlangCompilerArguments(string? file, string entryPoint, ShaderStages stage, string[]? searchPaths) - { - List arguments; - - if (file is null) - { - arguments = - [ - "-entry", entryPoint, - "-stage", stage.ToString().ToLowerInvariant(), - "-matrix-layout-row-major" - ]; - } - else - { - arguments = - [ - file, - "-entry", entryPoint, - "-stage", stage.ToString().ToLowerInvariant(), - "-matrix-layout-row-major" - ]; - } - - if (searchPaths is not null) - { - foreach (string path in searchPaths) - { - arguments.AddRange(["-I", path]); - } - } - - arguments.Add("-target"); - - switch (GraphicsApi) - { - case GraphicsApi.DirectX12: - arguments.AddRange(["dxil", "-profile", "sm_6_6"]); - break; - - case GraphicsApi.Metal: - arguments.AddRange(["metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]); - break; - - case GraphicsApi.Vulkan: - arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-fvk-use-entrypoint-name"]); - break; - } - - return [.. arguments]; - } } diff --git a/sources/Zenith.NET/Structs/ShaderDesc.cs b/sources/Zenith.NET/Structs/ShaderDesc.cs index 3956c0d6..d8d0dee6 100644 --- a/sources/Zenith.NET/Structs/ShaderDesc.cs +++ b/sources/Zenith.NET/Structs/ShaderDesc.cs @@ -2,59 +2,7 @@ public struct ShaderDesc { - public byte[] Bytecode; + public string Name; - public string EntryPoint; - - public ShaderStages Stage; - - public static ShaderDesc Vertex(byte[] bytecode, string entryPoint) - { - return new() - { - Bytecode = bytecode, - EntryPoint = entryPoint, - Stage = ShaderStages.Vertex - }; - } - - public static ShaderDesc Fragment(byte[] bytecode, string entryPoint) - { - return new() - { - Bytecode = bytecode, - EntryPoint = entryPoint, - Stage = ShaderStages.Fragment - }; - } - - public static ShaderDesc Compute(byte[] bytecode, string entryPoint) - { - return new() - { - Bytecode = bytecode, - EntryPoint = entryPoint, - Stage = ShaderStages.Compute - }; - } - - public static ShaderDesc Task(byte[] bytecode, string entryPoint) - { - return new() - { - Bytecode = bytecode, - EntryPoint = entryPoint, - Stage = ShaderStages.Task - }; - } - - public static ShaderDesc Mesh(byte[] bytecode, string entryPoint) - { - return new() - { - Bytecode = bytecode, - EntryPoint = entryPoint, - Stage = ShaderStages.Mesh - }; - } + public byte[] CodeBytes; } diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs new file mode 100644 index 00000000..2b1d9374 --- /dev/null +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -0,0 +1,66 @@ +using Slangc.NET; + +namespace Zenith.NET; + +public static class ZenithCompiler +{ + public static byte[] CompileFromFile(GraphicsApi graphicsApi, string file, string entry, string[]? searchPaths = null) + { + return SlangCompiler.Compile(SlangCompilerArguments(graphicsApi, file, entry, searchPaths)); + } + + public static byte[] CompileFromSource(GraphicsApi graphicsApi, string source, string entry, string[]? searchPaths = null) + { + return SlangCompiler.Compile(source, SlangCompilerArguments(graphicsApi, null, entry, searchPaths)); + } + + private static string[] SlangCompilerArguments(GraphicsApi graphicsApi, string? file, string entry, string[]? searchPaths) + { + List arguments; + + if (file is null) + { + arguments = + [ + "-entry", entry, + "-matrix-layout-row-major" + ]; + } + else + { + arguments = + [ + file, + "-entry", entry, + "-matrix-layout-row-major" + ]; + } + + if (searchPaths is not null) + { + foreach (string searchPath in searchPaths) + { + arguments.AddRange(["-I", searchPath]); + } + } + + arguments.Add("-target"); + + switch (graphicsApi) + { + case GraphicsApi.DirectX12: + arguments.AddRange(["dxil", "-profile", "sm_6_6"]); + break; + + case GraphicsApi.Metal: + arguments.AddRange(["metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]); + break; + + case GraphicsApi.Vulkan: + arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-fvk-use-entrypoint-name"]); + break; + } + + return [.. arguments]; + } +} From a7292c361061c488ccb70943723b84b5923174db Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 18:07:46 +0800 Subject: [PATCH 171/466] Remove ResolveTexture fields from ColorAttachment Removed ResolveTexture and ResolveSubresource fields from the ColorAttachment struct, along with related assignments in the Clear, Load, and DontCare static methods. This simplifies the struct and its initialization methods. --- sources/Zenith.NET/Structs/ColorAttachment.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/sources/Zenith.NET/Structs/ColorAttachment.cs b/sources/Zenith.NET/Structs/ColorAttachment.cs index 6587b50c..36669a50 100644 --- a/sources/Zenith.NET/Structs/ColorAttachment.cs +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -14,10 +14,6 @@ public struct ColorAttachment public Vector4 ClearColor; - public Texture? ResolveTexture; - - public TextureSubresource ResolveSubresource; - public static ColorAttachment Clear(Texture texture, Vector4 clearColor) { return new() @@ -26,9 +22,7 @@ public static ColorAttachment Clear(Texture texture, Vector4 clearColor) Subresource = new(), LoadOp = LoadOp.Clear, StoreOp = StoreOp.Store, - ClearColor = clearColor, - ResolveTexture = null, - ResolveSubresource = new() + ClearColor = clearColor }; } @@ -40,9 +34,7 @@ public static ColorAttachment Load(Texture texture) Subresource = new(), LoadOp = LoadOp.Load, StoreOp = StoreOp.Store, - ClearColor = Vector4.Zero, - ResolveTexture = null, - ResolveSubresource = new() + ClearColor = Vector4.Zero }; } @@ -54,9 +46,7 @@ public static ColorAttachment DontCare(Texture texture) Subresource = new(), LoadOp = LoadOp.DontCare, StoreOp = StoreOp.Store, - ClearColor = Vector4.Zero, - ResolveTexture = null, - ResolveSubresource = new() + ClearColor = Vector4.Zero }; } } From b857e7fbf68f9f7e8f41338f5dd1409f7d1c8ee7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 18:43:12 +0800 Subject: [PATCH 172/466] Refactor struct init, add RTV/DSV view caching, render pass Refactor struct initializations to use object initializers for clarity and consistency. Implement render pass setup in DXCommandBuffer, including clear values and access types. Add RTV/DSV descriptor token caching and handle creation in DXTexture, with proper disposal. Add LoadOp/StoreOp to DirectX12 access type mappings. Update DXGraphicsContext to use default for optional resource parameters. Remove FirstWSlice from Texture3D UAV view in DXTextureView. --- sources/Zenith.NET.DirectX12/DXBuffer.cs | 10 +- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 128 +++++++++++++- sources/Zenith.NET.DirectX12/DXFormats.cs | 51 +++++- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 4 +- sources/Zenith.NET.DirectX12/DXHeap.cs | 5 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 2 +- sources/Zenith.NET.DirectX12/DXTexture.cs | 163 ++++++++++++++++++ sources/Zenith.NET.DirectX12/DXTextureView.cs | 1 - 8 files changed, 347 insertions(+), 17 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 9333c2e7..c8714b2a 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -16,7 +16,10 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr colorAttachments, DepthStencilAttachment? depthStencilAttachment) { + RenderPassRenderTargetDesc* pRenderTargets = stackalloc RenderPassRenderTargetDesc[colorAttachments.Length]; + + for (int i = 0; i < colorAttachments.Length; i++) + { + ColorAttachment attachment = colorAttachments[i]; + + DXTexture texture = attachment.Texture.DirectX12(); + + ClearValue clearValue = new() + { + Format = DXFormats.DirectX12(texture.Desc.Format) + }; + + *(Vector4*)clearValue.Anonymous.Color = attachment.ClearColor; + + pRenderTargets[i] = new() + { + CpuDescriptor = texture.RtvHandle(attachment.Subresource), + BeginningAccess = new() + { + Type = DXFormats.DirectX12(attachment.LoadOp), + Clear = new() + { + ClearValue = clearValue + } + }, + EndingAccess = new() + { + Type = DXFormats.DirectX12(attachment.StoreOp) + } + }; + } + + if (depthStencilAttachment is { } depthStencil) + { + DXTexture texture = depthStencil.Texture.DirectX12(); + + bool hasStencil = ZenithHelper.HasStencil(texture.Desc.Format); + + ClearValue clearValue = new() + { + Format = DXFormats.DirectX12(texture.Desc.Format), + DepthStencil = new() + { + Depth = depthStencil.ClearDepth, + Stencil = depthStencil.ClearStencil + } + }; + + RenderPassDepthStencilDesc depthStencilDesc = new() + { + CpuDescriptor = texture.DsvHandle(depthStencil.Subresource), + DepthBeginningAccess = new() + { + Type = DXFormats.DirectX12(depthStencil.DepthLoadOp), + Clear = new() + { + ClearValue = clearValue + } + }, + DepthEndingAccess = new() + { + Type = DXFormats.DirectX12(depthStencil.DepthStoreOp) + }, + StencilBeginningAccess = new() + { + Type = hasStencil ? DXFormats.DirectX12(depthStencil.StencilLoadOp) : RenderPassBeginningAccessType.NoAccess, + Clear = new() + { + ClearValue = clearValue + } + }, + StencilEndingAccess = new() + { + Type = hasStencil ? DXFormats.DirectX12(depthStencil.StencilStoreOp) : RenderPassEndingAccessType.NoAccess + } + }; + + CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, &depthStencilDesc, RenderPassFlags.None); + } + else + { + CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, default(RenderPassDepthStencilDesc*), RenderPassFlags.None); + } } protected override void EndRenderPassImpl() @@ -243,7 +343,19 @@ protected override void SetScissorsImpl(ReadOnlySpan scissors) Box2D* pRects = stackalloc Box2D[scissors.Length]; for (int i = 0; i < scissors.Length; i++) { - pRects[i] = new(scissors[i].X, scissors[i].Y, scissors[i].X + (int)scissors[i].Width, scissors[i].Y + (int)scissors[i].Height); + pRects[i] = new() + { + Min = new() + { + X = scissors[i].X, + Y = scissors[i].Y + }, + Max = new() + { + X = scissors[i].X + (int)scissors[i].Width, + Y = scissors[i].Y + (int)scissors[i].Height + } + }; } CommandList.RSSetScissorRects((uint)scissors.Length, pRects); @@ -254,7 +366,15 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) DxViewport* pViewports = stackalloc DxViewport[viewports.Length]; for (int i = 0; i < viewports.Length; i++) { - pViewports[i] = new(viewports[i].X, viewports[i].Y, viewports[i].Width, viewports[i].Height, viewports[i].MinDepth, viewports[i].MaxDepth); + pViewports[i] = new() + { + TopLeftX = viewports[i].X, + TopLeftY = viewports[i].Y, + Width = viewports[i].Width, + Height = viewports[i].Height, + MinDepth = viewports[i].MinDepth, + MaxDepth = viewports[i].MaxDepth + }; } CommandList.RSSetViewports((uint)viewports.Length, pViewports); diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 7fdda46b..7a313fdc 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -144,12 +144,30 @@ public static SampleDesc DirectX12(SampleCount sampleCount) { return sampleCount switch { - SampleCount.Count1 => new(1, 0), - SampleCount.Count2 => new(2, 0), - SampleCount.Count4 => new(4, 0), - SampleCount.Count8 => new(8, 0), - SampleCount.Count16 => new(16, 0), - SampleCount.Count32 => new(32, 0), + SampleCount.Count1 => new() + { + Count = 1 + }, + SampleCount.Count2 => new() + { + Count = 2 + }, + SampleCount.Count4 => new() + { + Count = 4 + }, + SampleCount.Count8 => new() + { + Count = 8 + }, + SampleCount.Count16 => new() + { + Count = 16 + }, + SampleCount.Count32 => new() + { + Count = 32 + }, _ => default }; } @@ -453,6 +471,27 @@ public static Format DirectX12(IndexFormat indexFormat) }; } + public static RenderPassBeginningAccessType DirectX12(LoadOp loadOp) + { + return loadOp switch + { + LoadOp.Load => RenderPassBeginningAccessType.Preserve, + LoadOp.Clear => RenderPassBeginningAccessType.Clear, + LoadOp.DontCare => RenderPassBeginningAccessType.Discard, + _ => default + }; + } + + public static RenderPassEndingAccessType DirectX12(StoreOp storeOp) + { + return storeOp switch + { + StoreOp.Store => RenderPassEndingAccessType.Preserve, + StoreOp.DontCare => RenderPassEndingAccessType.Discard, + _ => default + }; + } + public static BarrierSync DirectX12(PipelineStages pipelineStages) { BarrierSync result = BarrierSync.None; diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index dfa991a8..991bb53a 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -180,7 +180,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) protected override Buffer CreateBufferImpl(BufferDesc desc) { - return new DXBuffer(this, desc, null); + return new DXBuffer(this, desc, default); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) @@ -190,7 +190,7 @@ protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc) { - return new DXTexture(this, desc, null); + return new DXTexture(this, desc, default); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index a889ea99..adbc7e1d 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -13,7 +13,10 @@ public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) DxHeapDesc heapDesc = new() { SizeInBytes = ZenithHelper.Align(desc.SizeInBytes, DXGraphicsContext.DefaultHeapAlignment), - Properties = new(DXFormats.DirectX12(desc.Residency)), + Properties = new() + { + Type = DXFormats.DirectX12(desc.Residency) + }, Alignment = DXGraphicsContext.DefaultHeapAlignment }; diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index a4abfe7f..a7c65161 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -17,7 +17,7 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context context.Device.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); - Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Residency = MemoryResidency.CpuReadOnly }, null); + Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Residency = MemoryResidency.CpuReadOnly }, default); } public DXBuffer Buffer { get; } diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 9d39767f..93a59242 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -6,6 +6,9 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXTexture : Texture { + private readonly Dictionary rtvTokens = []; + private readonly Dictionary dsvTokens = []; + public ComPtr Resource; public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr? resource) : base(context, desc) @@ -41,6 +44,8 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr (DXGraphicsContext)base.Context; + public DXTextureView View { get; } public override ResourceHandle SampledHandle => View.SampledHandle; @@ -52,6 +57,152 @@ public uint SubresourceIndex(TextureSubresource textureSubresource) return (textureSubresource.MipLevel * Desc.ArrayLayers) + textureSubresource.ArrayLayer; } + public CpuDescriptorHandle RtvHandle(TextureSubresource subresource) + { + if (!rtvTokens.TryGetValue(subresource, out DXDescriptorToken token)) + { + rtvTokens[subresource] = token = Context.RtvHeap.Allocate(); + + RenderTargetViewDesc viewDesc = new(DXFormats.DirectX12(Desc.Format)); + + switch (Desc.Type) + { + case TextureType.Texture1D: + viewDesc.ViewDimension = RtvDimension.Texture1D; + viewDesc.Texture1D = new(subresource.MipLevel); + break; + + case TextureType.Texture1DArray: + viewDesc.ViewDimension = RtvDimension.Texture1Darray; + viewDesc.Texture1DArray = new() + { + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + break; + + case TextureType.Texture2D: + if (Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = RtvDimension.Texture2D; + viewDesc.Texture2D = new(subresource.MipLevel); + } + else + { + viewDesc.ViewDimension = RtvDimension.Texture2Dms; + } + break; + + case TextureType.Texture2DArray: + case TextureType.TextureCube: + case TextureType.TextureCubeArray: + if (Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = RtvDimension.Texture2Darray; + viewDesc.Texture2DArray = new() + { + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + } + else + { + viewDesc.ViewDimension = RtvDimension.Texture2Dmsarray; + viewDesc.Texture2DMSArray = new() + { + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + } + break; + + case TextureType.Texture3D: + viewDesc.ViewDimension = RtvDimension.Texture3D; + viewDesc.Texture3D = new() + { + MipSlice = subresource.MipLevel, + FirstWSlice = subresource.ArrayLayer, + WSize = 1 + }; + break; + } + + Context.Device.CreateRenderTargetView(Resource, &viewDesc, token.CpuHandle); + } + + return token.CpuHandle; + } + + public CpuDescriptorHandle DsvHandle(TextureSubresource subresource) + { + if (!dsvTokens.TryGetValue(subresource, out DXDescriptorToken token)) + { + dsvTokens[subresource] = token = Context.DsvHeap.Allocate(); + + DepthStencilViewDesc viewDesc = new(DXFormats.DirectX12(Desc.Format)); + + switch (Desc.Type) + { + case TextureType.Texture1D: + viewDesc.ViewDimension = DsvDimension.Texture1D; + viewDesc.Texture1D = new(subresource.MipLevel); + break; + + case TextureType.Texture1DArray: + viewDesc.ViewDimension = DsvDimension.Texture1Darray; + viewDesc.Texture1DArray = new() + { + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + break; + + case TextureType.Texture2D: + if (Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = DsvDimension.Texture2D; + viewDesc.Texture2D = new(subresource.MipLevel); + } + else + { + viewDesc.ViewDimension = DsvDimension.Texture2Dms; + } + break; + + case TextureType.Texture2DArray: + case TextureType.TextureCube: + case TextureType.TextureCubeArray: + if (Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = DsvDimension.Texture2Darray; + viewDesc.Texture2DArray = new() + { + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + } + else + { + viewDesc.ViewDimension = DsvDimension.Texture2Dmsarray; + viewDesc.Texture2DMSArray = new() + { + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + } + break; + } + + Context.Device.CreateDepthStencilView(Resource, &viewDesc, token.CpuHandle); + } + + return token.CpuHandle; + } + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -64,6 +215,18 @@ protected override void SetResourceName(string name) protected override void Destroy() { + foreach (DXDescriptorToken token in rtvTokens.Values) + { + token.Dispose(); + } + rtvTokens.Clear(); + + foreach (DXDescriptorToken token in dsvTokens.Values) + { + token.Dispose(); + } + dsvTokens.Clear(); + View.Dispose(); Resource.Dispose(); diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs index 41c9dbd5..93cebe18 100644 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -164,7 +164,6 @@ private DXDescriptorToken CreateUavToken() viewDesc.Texture3D = new() { MipSlice = Desc.Range.BaseMipLevel, - FirstWSlice = 0, WSize = Desc.Texture.Desc.Depth }; break; From e106986b9b1b2cabfd703e9bf64692acd8970595 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 18:51:04 +0800 Subject: [PATCH 173/466] Refactor attachment handling in render pass setup Refactored DXCommandBuffer and CommandBuffer to use local variables for color and depth-stencil attachments, improving clarity and correctness. Replaced pattern matching with HasValue checks for nullable attachments, and updated access logic for attachment properties. Enhanced depth/stencil access type logic using ZenithHelper. Simplified BeginRenderPass invocation and updated MipDimensions usage for consistency. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 33 ++++++++----------- sources/Zenith.NET/CommandBuffer.cs | 12 ++++--- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 437872ea..b9f6f892 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -248,7 +248,6 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { RenderPassRenderTargetDesc* pRenderTargets = stackalloc RenderPassRenderTargetDesc[colorAttachments.Length]; - for (int i = 0; i < colorAttachments.Length; i++) { ColorAttachment attachment = colorAttachments[i]; @@ -259,7 +258,6 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA { Format = DXFormats.DirectX12(texture.Desc.Format) }; - *(Vector4*)clearValue.Anonymous.Color = attachment.ClearColor; pRenderTargets[i] = new() @@ -280,28 +278,29 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA }; } - if (depthStencilAttachment is { } depthStencil) + RenderPassDepthStencilDesc depthStencil = default; + if (depthStencilAttachment.HasValue) { - DXTexture texture = depthStencil.Texture.DirectX12(); + DepthStencilAttachment attachment = depthStencilAttachment.Value; - bool hasStencil = ZenithHelper.HasStencil(texture.Desc.Format); + DXTexture texture = attachment.Texture.DirectX12(); ClearValue clearValue = new() { Format = DXFormats.DirectX12(texture.Desc.Format), DepthStencil = new() { - Depth = depthStencil.ClearDepth, - Stencil = depthStencil.ClearStencil + Depth = attachment.ClearDepth, + Stencil = attachment.ClearStencil } }; - RenderPassDepthStencilDesc depthStencilDesc = new() + depthStencil = new() { - CpuDescriptor = texture.DsvHandle(depthStencil.Subresource), + CpuDescriptor = texture.DsvHandle(attachment.Subresource), DepthBeginningAccess = new() { - Type = DXFormats.DirectX12(depthStencil.DepthLoadOp), + Type = DXFormats.DirectX12(attachment.DepthLoadOp), Clear = new() { ClearValue = clearValue @@ -309,11 +308,11 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA }, DepthEndingAccess = new() { - Type = DXFormats.DirectX12(depthStencil.DepthStoreOp) + Type = ZenithHelper.HasDepth(texture.Desc.Format) ? DXFormats.DirectX12(attachment.DepthStoreOp) : RenderPassEndingAccessType.NoAccess }, StencilBeginningAccess = new() { - Type = hasStencil ? DXFormats.DirectX12(depthStencil.StencilLoadOp) : RenderPassBeginningAccessType.NoAccess, + Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilLoadOp) : RenderPassBeginningAccessType.NoAccess, Clear = new() { ClearValue = clearValue @@ -321,16 +320,12 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA }, StencilEndingAccess = new() { - Type = hasStencil ? DXFormats.DirectX12(depthStencil.StencilStoreOp) : RenderPassEndingAccessType.NoAccess + Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilStoreOp) : RenderPassEndingAccessType.NoAccess } }; - - CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, &depthStencilDesc, RenderPassFlags.None); - } - else - { - CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, default(RenderPassDepthStencilDesc*), RenderPassFlags.None); } + + CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, &depthStencil, RenderPassFlags.None); } protected override void EndRenderPassImpl() diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 9db79748..496d0e6e 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -161,10 +161,12 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept { for (int i = 0; i < colorAttachments.Length; i++) { - ZenithHelper.MipDimensions(colorAttachments[i].Texture.Desc.Width, - colorAttachments[i].Texture.Desc.Height, + ColorAttachment attachment = colorAttachments[i]; + + ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, + attachment.Texture.Desc.Height, 0, - colorAttachments[i].Subresource.MipLevel, + attachment.Subresource.MipLevel, out uint width, out uint height, out _); @@ -173,8 +175,10 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept viewports[i] = new() { Width = width, Height = height, MaxDepth = 1.0f }; } } - else if (depthStencilAttachment is { } attachment) + else if (depthStencilAttachment.HasValue) { + DepthStencilAttachment attachment = depthStencilAttachment.Value; + ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, attachment.Texture.Desc.Height, 0, From 64cd40660bc1bc9f6a9c0d5eb91e178ce6ad69c1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 18:54:38 +0800 Subject: [PATCH 174/466] Refactor DXTexture handle method names for clarity Renamed RtvHandle to GetRtvHandle and DsvHandle to GetDsvHandle in DXTexture for clearer intent. Updated all usages in DXCommandBuffer. Also standardized parameter naming in SubresourceIndex for consistency. --- sources/Zenith.NET.DirectX12/DXCommandBuffer.cs | 4 ++-- sources/Zenith.NET.DirectX12/DXTexture.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index b9f6f892..a14fa3b5 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -262,7 +262,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA pRenderTargets[i] = new() { - CpuDescriptor = texture.RtvHandle(attachment.Subresource), + CpuDescriptor = texture.GetRtvHandle(attachment.Subresource), BeginningAccess = new() { Type = DXFormats.DirectX12(attachment.LoadOp), @@ -297,7 +297,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA depthStencil = new() { - CpuDescriptor = texture.DsvHandle(attachment.Subresource), + CpuDescriptor = texture.GetDsvHandle(attachment.Subresource), DepthBeginningAccess = new() { Type = DXFormats.DirectX12(attachment.DepthLoadOp), diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 93a59242..ced340a4 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -52,12 +52,12 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr View.StorageHandle; - public uint SubresourceIndex(TextureSubresource textureSubresource) + public uint SubresourceIndex(TextureSubresource subresource) { - return (textureSubresource.MipLevel * Desc.ArrayLayers) + textureSubresource.ArrayLayer; + return (subresource.MipLevel * Desc.ArrayLayers) + subresource.ArrayLayer; } - public CpuDescriptorHandle RtvHandle(TextureSubresource subresource) + public CpuDescriptorHandle GetRtvHandle(TextureSubresource subresource) { if (!rtvTokens.TryGetValue(subresource, out DXDescriptorToken token)) { @@ -135,7 +135,7 @@ public CpuDescriptorHandle RtvHandle(TextureSubresource subresource) return token.CpuHandle; } - public CpuDescriptorHandle DsvHandle(TextureSubresource subresource) + public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) { if (!dsvTokens.TryGetValue(subresource, out DXDescriptorToken token)) { From 36cb8163a6a9e29a1e455593952ce8e1778bdfab Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 21:18:29 +0800 Subject: [PATCH 175/466] Add BLAS update support and improve memory barriers Introduce UpdateAccelerationStructure for BLAS in CommandBuffer, add Refresh method to BottomLevelAccelerationStructure, and replace BufferBarrier.AccelerationStructureRead with new MemoryBarrier methods for acceleration structure read/write synchronization. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 4 ++++ .../BottomLevelAccelerationStructure.cs | 5 +++++ sources/Zenith.NET/CommandBuffer.cs | 9 ++++++++ sources/Zenith.NET/Structs/BufferBarrier.cs | 12 ---------- sources/Zenith.NET/Structs/MemoryBarrier.cs | 22 +++++++++++++++++++ 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index a14fa3b5..15b7d1ab 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -241,6 +241,10 @@ protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl( throw new NotImplementedException(); } + protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) + { + } + protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { } diff --git a/sources/Zenith.NET/BottomLevelAccelerationStructure.cs b/sources/Zenith.NET/BottomLevelAccelerationStructure.cs index 445587a8..bdc9e2fb 100644 --- a/sources/Zenith.NET/BottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET/BottomLevelAccelerationStructure.cs @@ -5,4 +5,9 @@ public abstract class BottomLevelAccelerationStructure(GraphicsContext context, private BottomLevelAccelerationStructureDesc desc = desc; public ref readonly BottomLevelAccelerationStructureDesc Desc => ref desc; + + internal void Refresh(BottomLevelAccelerationStructureDesc newDesc) + { + desc = newDesc; + } } diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 496d0e6e..8f5dee0f 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -143,6 +143,13 @@ public TopLevelAccelerationStructure BuildAccelerationStructure(TopLevelAccelera return BuildAccelerationStructureImpl(desc); } + public void UpdateAccelerationStructure(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) + { + UpdateAccelerationStructureImpl(accelerationStructure, newDesc); + + accelerationStructure.Refresh(newDesc); + } + public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { UpdateAccelerationStructureImpl(accelerationStructure, newDesc); @@ -454,6 +461,8 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip protected abstract TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc); + protected abstract void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc); + protected abstract void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc); protected abstract void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); diff --git a/sources/Zenith.NET/Structs/BufferBarrier.cs b/sources/Zenith.NET/Structs/BufferBarrier.cs index 3be0c06d..a05c4b2c 100644 --- a/sources/Zenith.NET/Structs/BufferBarrier.cs +++ b/sources/Zenith.NET/Structs/BufferBarrier.cs @@ -107,16 +107,4 @@ public static BufferBarrier CopyDst(Buffer buffer, BufferBarrier? previous) DstAccess = ResourceAccess.CopyWrite }; } - - public static BufferBarrier AccelerationStructureRead(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.AccelerationStructureBuild, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.AccelerationStructureRead - }; - } } diff --git a/sources/Zenith.NET/Structs/MemoryBarrier.cs b/sources/Zenith.NET/Structs/MemoryBarrier.cs index 86f74627..13ce7ad6 100644 --- a/sources/Zenith.NET/Structs/MemoryBarrier.cs +++ b/sources/Zenith.NET/Structs/MemoryBarrier.cs @@ -20,4 +20,26 @@ public static MemoryBarrier Storage(MemoryBarrier? previous) DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite }; } + + public static MemoryBarrier AccelerationStructureRead(MemoryBarrier? previous) + { + return new() + { + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.AccelerationStructureBuild, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.AccelerationStructureRead + }; + } + + public static MemoryBarrier AccelerationStructureWrite(MemoryBarrier? previous) + { + return new() + { + SrcStages = previous?.DstStages ?? PipelineStages.None, + DstStages = PipelineStages.AccelerationStructureBuild, + SrcAccess = previous?.DstAccess ?? ResourceAccess.None, + DstAccess = ResourceAccess.AccelerationStructureWrite + }; + } } From 3f51ca20acde8289bb5a00f537ab67c583d40c67 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 21:39:58 +0800 Subject: [PATCH 176/466] Update barrier stages and reorder misc flag assignment Reordered ResourceMiscFlag bitwise OR in D3DTexture and ZenithView.WinUI for clarity (no functional change). Expanded DstStages in MemoryBarrier.AccelerationStructureRead to include FragmentShader and ComputeShader, broadening synchronization coverage. --- .../Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs | 2 +- sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs | 2 +- sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 2 +- sources/Zenith.NET/Structs/MemoryBarrier.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs index 14c9b2f9..48f94171 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs @@ -48,7 +48,7 @@ public D3DTexture(uint width, uint height) Format = ColorFormat(), SampleDesc = new(1), BindFlags = (uint)BindFlag.RenderTarget, - MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, default(SubresourceData*), Texture.GetAddressOf())); diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs index b0fee48e..4368fce6 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs @@ -53,7 +53,7 @@ public D3DTexture(uint width, uint height, D3DImage image) Format = ColorFormat(), SampleDesc = new(1), BindFlags = (uint)BindFlag.RenderTarget, - MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; D3D.Success(D3D.D3D11Device.CreateTexture2D(&desc, default(SubresourceData*), D3D11RenderTarget.GetAddressOf())); diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index b2950e35..3b8117fb 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -153,7 +153,7 @@ public D3DTexture(uint width, uint height) Format = ColorFormat(), SampleDesc = new(1), BindFlags = (uint)BindFlag.RenderTarget, - MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, default(SubresourceData*), Texture.GetAddressOf())); diff --git a/sources/Zenith.NET/Structs/MemoryBarrier.cs b/sources/Zenith.NET/Structs/MemoryBarrier.cs index 13ce7ad6..a5c488cd 100644 --- a/sources/Zenith.NET/Structs/MemoryBarrier.cs +++ b/sources/Zenith.NET/Structs/MemoryBarrier.cs @@ -26,7 +26,7 @@ public static MemoryBarrier AccelerationStructureRead(MemoryBarrier? previous) return new() { SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.AccelerationStructureBuild, + DstStages = PipelineStages.FragmentShader | PipelineStages.ComputeShader | PipelineStages.AccelerationStructureBuild, SrcAccess = previous?.DstAccess ?? ResourceAccess.None, DstAccess = ResourceAccess.AccelerationStructureRead }; From 046e6737d5d1a224ae5cc8f6dbd4bd0ef4a1aa0c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 6 Jun 2026 23:02:23 +0800 Subject: [PATCH 177/466] Add DirectX 12 ray tracing acceleration structure support Implemented DXBottomLevelAccelerationStructure and DXTopLevelAccelerationStructure classes for DirectX 12 ray tracing. Updated DXCommandBuffer to create and update acceleration structures. Added enum and flag mapping functions in DXFormats, and extension methods for type casting in Extensions. Managed buffer allocation, resource views, and memory cleanup for acceleration structures. --- .../DXBottomLevelAccelerationStructure.cs | 140 ++++++++++++++++++ .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 6 +- sources/Zenith.NET.DirectX12/DXFormats.cs | 92 +++++++++++- .../DXTopLevelAccelerationStructure.cs | 121 +++++++++++++++ sources/Zenith.NET.DirectX12/Extensions.cs | 16 ++ 5 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs create mode 100644 sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs new file mode 100644 index 00000000..4e4d79cd --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -0,0 +1,140 @@ +using Silk.NET.Direct3D12; +using Silk.NET.DXGI; +using Silk.NET.Maths; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXBottomLevelAccelerationStructure : BottomLevelAccelerationStructure +{ + public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) + { + using ZenithMarshal.Scope scope = new(); + + TransformBuffer = new(context, new() { SizeInBytes = (uint)(sizeof(Matrix3X4) * desc.Geometries.Length), Residency = MemoryResidency.CpuWriteOnly }, default); + + FillInputs(scope, desc, out BuildRaytracingAccelerationStructureInputs inputs); + + RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); + context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); + + AccelerationStructureBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, Usages = BufferUsages.AccelerationStructure, Residency = MemoryResidency.GpuOnly }, default); + + ScratchBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, Usages = BufferUsages.StorageReadWrite, Residency = MemoryResidency.GpuOnly }, default); + + BuildRaytracingAccelerationStructureDesc buildDesc = new() + { + DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + Inputs = inputs, + ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + }; + + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + } + + public DXBuffer TransformBuffer { get; } + + public DXBuffer AccelerationStructureBuffer { get; } + + public DXBuffer ScratchBuffer { get; } + + public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) + { + using ZenithMarshal.Scope scope = new(); + + FillInputs(scope, newDesc, out BuildRaytracingAccelerationStructureInputs inputs); + + inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; + + BuildRaytracingAccelerationStructureDesc buildDesc = new() + { + DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + Inputs = inputs, + SourceAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + }; + + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + ScratchBuffer.Dispose(); + AccelerationStructureBuffer.Dispose(); + TransformBuffer.Dispose(); + } + + private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) + { + uint geometryCount = (uint)desc.Geometries.Length; + + MappedMemory mappedMemory = TransformBuffer.Map(); + + desc.Geometries.Select(static item => DXFormats.DirectX12(item.TriangleGeometry.Transform)).ToArray().CopyTo(new Span>((Matrix3X4*)mappedMemory.Pointer, (int)geometryCount)); + + TransformBuffer.Unmap(); + + RaytracingGeometryDesc* geometries = (RaytracingGeometryDesc*)ZenithMarshal.Allocate(scope, geometryCount); + for (uint i = 0; i < geometryCount; i++) + { + RayTracingGeometry geometry = desc.Geometries[i]; + + geometries[i] = new() + { + Type = DXFormats.DirectX12(geometry.Type), + Flags = geometry.IsOpaque ? RaytracingGeometryFlags.Opaque : RaytracingGeometryFlags.None + }; + + if (geometry.Type is RayTracingGeometryType.Triangle) + { + RayTracingTriangleGeometry triangle = geometry.TriangleGeometry; + + geometries[i].Triangles = new() + { + Transform3x4 = TransformBuffer.GPUVirtualAddress + (ulong)(sizeof(Matrix3X4) * i), + IndexFormat = triangle.IndexBuffer is not null ? DXFormats.DirectX12(triangle.IndexFormat) : Format.FormatUnknown, + VertexFormat = DXFormats.DirectX12(triangle.VertexFormat), + IndexCount = triangle.IndexCount, + VertexCount = triangle.VertexCount, + IndexBuffer = triangle.IndexBuffer is not null ? triangle.IndexBuffer.DirectX12().GPUVirtualAddress + triangle.IndexOffsetInBytes : 0, + VertexBuffer = new() + { + StartAddress = triangle.VertexBuffer.DirectX12().GPUVirtualAddress + triangle.VertexOffsetInBytes, + StrideInBytes = triangle.VertexStrideInBytes + } + }; + } + else + { + RayTracingAabbGeometry aabb = geometry.AabbGeometry; + + geometries[i].AABBs = new() + { + AABBCount = aabb.Count, + AABBs = new() + { + StartAddress = aabb.Buffer.DirectX12().GPUVirtualAddress + aabb.OffsetInBytes, + StrideInBytes = aabb.StrideInBytes + } + }; + } + } + + inputs = new() + { + Type = RaytracingAccelerationStructureType.BottomLevel, + Flags = DXFormats.DirectX12(desc.BuildFlags), + NumDescs = geometryCount, + DescsLayout = ElementsLayout.Array, + PGeometryDescs = geometries + }; + } +} diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 15b7d1ab..325eeead 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -233,20 +233,22 @@ protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSu protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + return new DXBottomLevelAccelerationStructure(Context, this, desc); } protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + return new DXTopLevelAccelerationStructure(Context, this, desc); } protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) { + accelerationStructure.DirectX12().Update(this, newDesc); } protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { + accelerationStructure.DirectX12().Update(this, newDesc); } protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 7a313fdc..244a1b31 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -1,5 +1,7 @@ -using Silk.NET.Direct3D12; +using System.Numerics; +using Silk.NET.Direct3D12; using Silk.NET.DXGI; +using Silk.NET.Maths; namespace Zenith.NET.DirectX12; @@ -685,4 +687,92 @@ public static BarrierLayout DirectX12(TextureLayout textureLayout) _ => default }; } + + public static RaytracingGeometryType DirectX12(RayTracingGeometryType rayTracingGeometryType) + { + return rayTracingGeometryType switch + { + RayTracingGeometryType.Triangle => RaytracingGeometryType.Triangles, + RayTracingGeometryType.Aabb => RaytracingGeometryType.ProceduralPrimitiveAabbs, + _ => default + }; + } + + public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) + { + RaytracingInstanceFlags result = RaytracingInstanceFlags.None; + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) + { + result |= RaytracingInstanceFlags.TriangleFrontCounterclockwise; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) + { + result |= RaytracingInstanceFlags.TriangleCullDisable; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + { + result |= RaytracingInstanceFlags.ForceOpaque; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) + { + result |= RaytracingInstanceFlags.ForceNonOpaque; + } + + return result; + } + + public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) + { + RaytracingAccelerationStructureBuildFlags result = RaytracingAccelerationStructureBuildFlags.None; + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + { + result |= RaytracingAccelerationStructureBuildFlags.AllowUpdate; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) + { + result |= RaytracingAccelerationStructureBuildFlags.AllowCompaction; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + { + result |= RaytracingAccelerationStructureBuildFlags.PreferFastTrace; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + { + result |= RaytracingAccelerationStructureBuildFlags.PreferFastBuild; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + { + result |= RaytracingAccelerationStructureBuildFlags.MinimizeMemory; + } + + return result; + } + + public static Matrix3X4 DirectX12(Matrix4x4 matrix4x4) + { + return new() + { + M11 = matrix4x4.M11, + M12 = matrix4x4.M21, + M13 = matrix4x4.M31, + M14 = matrix4x4.M41, + M21 = matrix4x4.M12, + M22 = matrix4x4.M22, + M23 = matrix4x4.M32, + M24 = matrix4x4.M42, + M31 = matrix4x4.M13, + M32 = matrix4x4.M23, + M33 = matrix4x4.M33, + M34 = matrix4x4.M43 + }; + } } diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs new file mode 100644 index 00000000..6032fa84 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -0,0 +1,121 @@ +using Silk.NET.Direct3D12; +using Silk.NET.Maths; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXTopLevelAccelerationStructure : TopLevelAccelerationStructure +{ + public DXDescriptorToken Token; + + public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) + { + InstanceBuffer = new(context, new() { SizeInBytes = (uint)(sizeof(RaytracingInstanceDesc) * desc.Instances.Length), Residency = MemoryResidency.CpuWriteOnly }, default); + + FillInputs(desc, out BuildRaytracingAccelerationStructureInputs inputs); + + RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); + context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); + + AccelerationStructureBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, Usages = BufferUsages.AccelerationStructure, Residency = MemoryResidency.GpuOnly }, default); + + ScratchBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, Usages = BufferUsages.StorageReadWrite, Residency = MemoryResidency.GpuOnly }, default); + + BuildRaytracingAccelerationStructureDesc buildDesc = new() + { + DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + Inputs = inputs, + ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + }; + + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + + ShaderResourceViewDesc viewDesc = new() + { + ViewDimension = SrvDimension.RaytracingAccelerationStructure, + Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, + RaytracingAccelerationStructure = new() + { + Location = AccelerationStructureBuffer.GPUVirtualAddress + } + }; + + context.Device.CreateShaderResourceView(default(ID3D12Resource*), &viewDesc, (Token = context.CbvSrvUavHeap.Allocate()).CpuHandle); + } + + public DXBuffer InstanceBuffer { get; } + + public DXBuffer AccelerationStructureBuffer { get; } + + public DXBuffer ScratchBuffer { get; } + + public override ResourceHandle Handle => Token.ResourceHandle; + + public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) + { + FillInputs(newDesc, out BuildRaytracingAccelerationStructureInputs inputs); + + inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; + + BuildRaytracingAccelerationStructureDesc buildDesc = new() + { + DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + Inputs = inputs, + SourceAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + }; + + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + Token.Dispose(); + + ScratchBuffer.Dispose(); + AccelerationStructureBuffer.Dispose(); + InstanceBuffer.Dispose(); + } + + private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) + { + uint instanceCount = (uint)desc.Instances.Length; + + MappedMemory mappedMemory = InstanceBuffer.Map(); + + RaytracingInstanceDesc* instances = (RaytracingInstanceDesc*)mappedMemory.Pointer; + for (uint i = 0; i < instanceCount; i++) + { + RayTracingInstance instance = desc.Instances[i]; + + instances[i] = new() + { + InstanceID = instance.InstanceId, + InstanceMask = instance.VisibilityMask, + Flags = (uint)DXFormats.DirectX12(instance.Flags), + AccelerationStructure = instance.AccelerationStructure.DirectX12().AccelerationStructureBuffer.GPUVirtualAddress + }; + + *(Matrix3X4*)instances[i].Transform = DXFormats.DirectX12(instance.Transform); + } + + InstanceBuffer.Unmap(); + + inputs = new() + { + Type = RaytracingAccelerationStructureType.TopLevel, + Flags = DXFormats.DirectX12(desc.BuildFlags), + NumDescs = instanceCount, + DescsLayout = ElementsLayout.Array, + InstanceDescs = InstanceBuffer.GPUVirtualAddress + }; + } +} diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 234ecc2c..0f94dce8 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -131,4 +131,20 @@ internal DXQueryHeap DirectX12() return (DXQueryHeap)queryHeap; } } + + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + { + internal DXBottomLevelAccelerationStructure DirectX12() + { + return (DXBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + } + } + + extension(TopLevelAccelerationStructure topLevelAccelerationStructure) + { + internal DXTopLevelAccelerationStructure DirectX12() + { + return (DXTopLevelAccelerationStructure)topLevelAccelerationStructure; + } + } } From 170142a6b9e7a4b4d707b5c121c08214c1d501c4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 08:42:12 +0800 Subject: [PATCH 178/466] Set resource name and remove DescsLayout assignment SetResourceName now sets AccelerationStructureBuffer.Name. Also removed DescsLayout assignment from inputs initialization in DXTopLevelAccelerationStructure. --- sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 6032fa84..f2a06c50 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -74,6 +74,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override void SetResourceName(string name) { + AccelerationStructureBuffer.Name = name; } protected override void Destroy() @@ -114,7 +115,6 @@ private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytrac Type = RaytracingAccelerationStructureType.TopLevel, Flags = DXFormats.DirectX12(desc.BuildFlags), NumDescs = instanceCount, - DescsLayout = ElementsLayout.Array, InstanceDescs = InstanceBuffer.GPUVirtualAddress }; } From 97a9eca18c0f2c28b8559e392b11cf64fb8e0df3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 08:48:45 +0800 Subject: [PATCH 179/466] Refactor geometry descriptor construction for DX12 Reworked DXBottomLevelAccelerationStructure to use a union-like Anonymous structure for geometry descriptors, assigning triangles or AABBs based on geometry type. This reduces code duplication and simplifies logic. Also set resource name on AccelerationStructureBuffer. --- .../DXBottomLevelAccelerationStructure.cs | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 4e4d79cd..f3d4e50c 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -63,6 +63,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override void SetResourceName(string name) { + AccelerationStructureBuffer.Name = name; } protected override void Destroy() @@ -90,42 +91,34 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct geometries[i] = new() { Type = DXFormats.DirectX12(geometry.Type), - Flags = geometry.IsOpaque ? RaytracingGeometryFlags.Opaque : RaytracingGeometryFlags.None - }; - - if (geometry.Type is RayTracingGeometryType.Triangle) - { - RayTracingTriangleGeometry triangle = geometry.TriangleGeometry; - - geometries[i].Triangles = new() - { - Transform3x4 = TransformBuffer.GPUVirtualAddress + (ulong)(sizeof(Matrix3X4) * i), - IndexFormat = triangle.IndexBuffer is not null ? DXFormats.DirectX12(triangle.IndexFormat) : Format.FormatUnknown, - VertexFormat = DXFormats.DirectX12(triangle.VertexFormat), - IndexCount = triangle.IndexCount, - VertexCount = triangle.VertexCount, - IndexBuffer = triangle.IndexBuffer is not null ? triangle.IndexBuffer.DirectX12().GPUVirtualAddress + triangle.IndexOffsetInBytes : 0, - VertexBuffer = new() + Flags = geometry.IsOpaque ? RaytracingGeometryFlags.Opaque : RaytracingGeometryFlags.None, + Anonymous = new + ( + triangles: geometry.Type is RayTracingGeometryType.Triangle ? new() { - StartAddress = triangle.VertexBuffer.DirectX12().GPUVirtualAddress + triangle.VertexOffsetInBytes, - StrideInBytes = triangle.VertexStrideInBytes - } - }; - } - else - { - RayTracingAabbGeometry aabb = geometry.AabbGeometry; - - geometries[i].AABBs = new() - { - AABBCount = aabb.Count, - AABBs = new() + Transform3x4 = TransformBuffer.GPUVirtualAddress + (ulong)(sizeof(Matrix3X4) * i), + IndexFormat = geometry.TriangleGeometry.IndexBuffer is not null ? DXFormats.DirectX12(geometry.TriangleGeometry.IndexFormat) : Format.FormatUnknown, + VertexFormat = DXFormats.DirectX12(geometry.TriangleGeometry.VertexFormat), + IndexCount = geometry.TriangleGeometry.IndexCount, + VertexCount = geometry.TriangleGeometry.VertexCount, + IndexBuffer = geometry.TriangleGeometry.IndexBuffer is not null ? geometry.TriangleGeometry.IndexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.IndexOffsetInBytes : 0, + VertexBuffer = new() + { + StartAddress = geometry.TriangleGeometry.VertexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.VertexOffsetInBytes, + StrideInBytes = geometry.TriangleGeometry.VertexStrideInBytes + } + } : null, + aABBs: geometry.Type is RayTracingGeometryType.Aabb ? new() { - StartAddress = aabb.Buffer.DirectX12().GPUVirtualAddress + aabb.OffsetInBytes, - StrideInBytes = aabb.StrideInBytes - } - }; - } + AABBCount = geometry.AabbGeometry.Count, + AABBs = new() + { + StartAddress = geometry.AabbGeometry.Buffer.DirectX12().GPUVirtualAddress + geometry.AabbGeometry.OffsetInBytes, + StrideInBytes = geometry.AabbGeometry.StrideInBytes + } + } : null + ) + }; } inputs = new() @@ -133,7 +126,6 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct Type = RaytracingAccelerationStructureType.BottomLevel, Flags = DXFormats.DirectX12(desc.BuildFlags), NumDescs = geometryCount, - DescsLayout = ElementsLayout.Array, PGeometryDescs = geometries }; } From f46f0b082c040b8fea8508745fb1bf22b912a18e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 08:54:23 +0800 Subject: [PATCH 180/466] Remove forced PerformUpdate flag and refactor transform copy Unconditionally setting the PerformUpdate flag in both bottom and top level acceleration structure updates has been removed, allowing build flags to be set as intended by the caller. Additionally, transform matrix copying in DXBottomLevelAccelerationStructure now uses a for-loop for direct assignment, and the buffer unmap call is moved to after the loop for correctness. --- .../DXBottomLevelAccelerationStructure.cs | 9 ++++----- .../DXTopLevelAccelerationStructure.cs | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index f3d4e50c..0f5e58dc 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -42,7 +42,6 @@ public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructu using ZenithMarshal.Scope scope = new(); FillInputs(scope, newDesc, out BuildRaytracingAccelerationStructureInputs inputs); - inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; BuildRaytracingAccelerationStructureDesc buildDesc = new() @@ -79,15 +78,13 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct MappedMemory mappedMemory = TransformBuffer.Map(); - desc.Geometries.Select(static item => DXFormats.DirectX12(item.TriangleGeometry.Transform)).ToArray().CopyTo(new Span>((Matrix3X4*)mappedMemory.Pointer, (int)geometryCount)); - - TransformBuffer.Unmap(); - + Matrix3X4* transforms = (Matrix3X4*)mappedMemory.Pointer; RaytracingGeometryDesc* geometries = (RaytracingGeometryDesc*)ZenithMarshal.Allocate(scope, geometryCount); for (uint i = 0; i < geometryCount; i++) { RayTracingGeometry geometry = desc.Geometries[i]; + transforms[i] = DXFormats.DirectX12(geometry.TriangleGeometry.Transform); geometries[i] = new() { Type = DXFormats.DirectX12(geometry.Type), @@ -121,6 +118,8 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct }; } + TransformBuffer.Unmap(); + inputs = new() { Type = RaytracingAccelerationStructureType.BottomLevel, diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index f2a06c50..4770a3ca 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -53,7 +53,6 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) { FillInputs(newDesc, out BuildRaytracingAccelerationStructureInputs inputs); - inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; BuildRaytracingAccelerationStructureDesc buildDesc = new() From d3dbbb794a1c32a3b2ad7f010363f67cea37f129 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 09:06:35 +0800 Subject: [PATCH 181/466] Refactor SwapChain to use Drawable, remove depth/stencil Remove DepthStencilFormat from SwapChainDesc and update SwapChain to expose a single Drawable texture instead of CurrentTexture and CurrentDepthStencilTexture. Update all usages to reference Drawable. Simplify swap chain and ImGui creation by omitting depth/stencil formats, streamlining the rendering interface. --- sources/Experiments/CornellBox/App.cs | 6 +++--- .../Platforms/Android/MauiZenithView.cs | 2 +- .../Platforms/MacCatalyst/MauiZenithView.cs | 2 +- .../Platforms/Windows/MauiZenithView.cs | 2 +- .../Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs | 2 +- sources/Views/Zenith.NET.Views.WPF/ZenithView.cs | 2 +- sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs | 2 +- sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 2 +- sources/Zenith.NET/Structs/SwapChainDesc.cs | 2 -- sources/Zenith.NET/SwapChain.cs | 4 +--- 10 files changed, 11 insertions(+), 15 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index d47cc9ab..5fa38e6b 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -72,8 +72,8 @@ static App() surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, Width, Height); } - swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorFormat = PixelFormat.B8G8R8A8UNorm, DepthStencilFormat = PixelFormat.D32FloatS8UInt }); - imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], DepthStencilFormat = PixelFormat.D32FloatS8UInt, SampleCount = SampleCount.Count1 }); + swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorFormat = PixelFormat.B8G8R8A8UNorm }); + imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], SampleCount = SampleCount.Count1 }); camera = new(input, Matrix4x4.CreateTranslation(278.0f, 273.0f, -800.0f)) { Speed = 240.0f, @@ -176,7 +176,7 @@ public static void Run() activeRenderer.Render(commandBuffer); - imGui.Render(commandBuffer, ColorAttachment.Load(swapChain.CurrentTexture)); + imGui.Render(commandBuffer, ColorAttachment.Load(swapChain.Drawable)); swapChain.Present(commandBuffer.Submit()).Wait(); }; diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs index ba0a305f..9a1af845 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs @@ -43,7 +43,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); + handler.VirtualView.OnRenderRequested(swapChain.Drawable); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs index 3a44e5d4..67f58fec 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs @@ -44,7 +44,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); + handler.VirtualView.OnRenderRequested(swapChain.Drawable); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs index ea912b57..0d09e3ed 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs @@ -44,7 +44,7 @@ public void Tick() texture.AcquireSync(); handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); + handler.VirtualView.OnRenderRequested(swapChain.Drawable); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs index 749d1d6e..9911b972 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs @@ -44,7 +44,7 @@ public void Tick() } handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.CurrentTexture); + handler.VirtualView.OnRenderRequested(swapChain.Drawable); } public void Present() diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index 114878f1..42314bc8 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -126,7 +126,7 @@ void IZenithView.Tick() texture.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentTexture)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.Drawable)); texture.ReleaseSync(); } diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs index d3140aaa..f23ff40d 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs @@ -98,7 +98,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentTexture)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.Drawable)); } void IZenithView.Present() diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 3b8117fb..e8289e6f 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -50,7 +50,7 @@ void IZenithView.Tick() texture.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.CurrentTexture)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.Drawable)); texture.ReleaseSync(); } diff --git a/sources/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Zenith.NET/Structs/SwapChainDesc.cs index 06e0c79e..5cf8088b 100644 --- a/sources/Zenith.NET/Structs/SwapChainDesc.cs +++ b/sources/Zenith.NET/Structs/SwapChainDesc.cs @@ -5,6 +5,4 @@ public struct SwapChainDesc public Surface Surface; public PixelFormat ColorFormat; - - public PixelFormat? DepthStencilFormat; } diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 6ee27303..e5973cdb 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -6,9 +6,7 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public ref readonly SwapChainDesc Desc => ref desc; - public abstract Texture CurrentTexture { get; } - - public abstract Texture? CurrentDepthStencilTexture { get; } + public abstract Texture Drawable { get; } public abstract CommandSubmission Acquire(); From 1541210577f75863010d9a97510796f9b42bbd5d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 09:08:15 +0800 Subject: [PATCH 182/466] Refactor ColorFormat to Format for pixel format handling Renamed the ColorFormat property to Format in SwapChainDesc, ZenithViewHelper, and all related usages. Updated all references in swap chain creation, texture creation, and format conversion logic to use the new property name. This improves consistency and clarity in pixel format handling across platforms. --- sources/Experiments/CornellBox/App.cs | 2 +- sources/Views/Zenith.NET.Views.Avalonia/Surface.cs | 6 +++--- .../Platforms/Android/MauiZenithView.cs | 2 +- .../Platforms/MacCatalyst/MauiZenithView.cs | 2 +- .../Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs | 4 ++-- .../Platforms/Windows/MauiZenithView.cs | 2 +- .../Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs | 2 +- sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs | 4 ++-- sources/Views/Zenith.NET.Views.WPF/ZenithView.cs | 2 +- sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs | 2 +- sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 4 ++-- sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 6 +++--- sources/Views/Zenith.NET.Views/ZenithViewHelper.cs | 2 +- sources/Zenith.NET/Structs/SwapChainDesc.cs | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 5fa38e6b..39f9e9c3 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -72,7 +72,7 @@ static App() surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, Width, Height); } - swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorFormat = PixelFormat.B8G8R8A8UNorm }); + swapChain = Context.CreateSwapChain(new() { Surface = surface, Format = PixelFormat.B8G8R8A8UNorm }); imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], SampleCount = SampleCount.Count1 }); camera = new(input, Matrix4x4.CreateTranslation(278.0f, 273.0f, -800.0f)) { diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 0d89c97c..a50fd43d 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -6,7 +6,7 @@ namespace Zenith.NET.Views.Avalonia; internal class Surface(GraphicsContext graphicsContext, uint width, uint height) : DisposableObject { - public Texture Target { get; } = graphicsContext.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.ColorFormat, width, height, 1, SampleCount.Count1)); + public Texture Target { get; } = graphicsContext.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.Format, width, height, 1, SampleCount.Count1)); public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); @@ -35,11 +35,11 @@ protected override void Destroy() private static AvaloniaPixelFormat ColorFormat() { - return ZenithViewHelper.ColorFormat switch + return ZenithViewHelper.Format switch { PixelFormat.R8G8B8A8UNorm => AvaloniaPixelFormat.Rgba8888, PixelFormat.B8G8R8A8UNorm => AvaloniaPixelFormat.Bgra8888, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") }; } } diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs index 9a1af845..817117ae 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs @@ -26,7 +26,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Android(ANativeWindowFromSurface(JniEnvironment.EnvironmentPointer, Holder!.Surface!.Handle), width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs index 67f58fec..8abe924c 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs index 48f94171..c006a134 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs @@ -111,11 +111,11 @@ protected override void Destroy() private static Format ColorFormat() { - return ZenithViewHelper.ColorFormat switch + return ZenithViewHelper.Format switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") }; } } \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs index 0d09e3ed..e354210c 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); this.As().SetSwapChain(texture.SwapChain); diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs index 9911b972..8f4b3fb8 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs index 4368fce6..2e3c411d 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs @@ -121,11 +121,11 @@ protected override void Destroy() private static DXGIFormat ColorFormat() { - return ZenithViewHelper.ColorFormat switch + return ZenithViewHelper.Format switch { PixelFormat.R8G8B8A8UNorm => DXGIFormat.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => DXGIFormat.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") }; } } \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index 42314bc8..fe554e19 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -111,7 +111,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); } } diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs index f23ff40d..6255fab7 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs @@ -81,7 +81,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.Win32(Handle, width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index df226d1e..a5c6037d 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -54,7 +54,7 @@ internal unsafe class Surface(GraphicsContext context, uint width, uint height) { private readonly byte[] pixels = new byte[width * height * 4]; - public Texture Target { get; } = context.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.ColorFormat, width, height, 1, SampleCount.Count1)); + public Texture Target { get; } = context.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.Format, width, height, 1, SampleCount.Count1)); public WriteableBitmap Bitmap { get; } = new((int)width, (int)height); @@ -75,7 +75,7 @@ public void Present() }); } - if (ZenithViewHelper.ColorFormat is PixelFormat.R8G8B8A8UNorm) + if (ZenithViewHelper.Format is PixelFormat.R8G8B8A8UNorm) { for (int i = 0; i < pixels.Length; i += 4) { diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index e8289e6f..57994049 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -33,7 +33,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - ColorFormat = ZenithViewHelper.ColorFormat + Format = ZenithViewHelper.Format }); this.As().SetSwapChain(texture.SwapChain); @@ -216,11 +216,11 @@ protected override void Destroy() private static Format ColorFormat() { - return ZenithViewHelper.ColorFormat switch + return ZenithViewHelper.Format switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") }; } } diff --git a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs index 35a14cf1..1fb22c85 100644 --- a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs +++ b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs @@ -2,5 +2,5 @@ public static class ZenithViewHelper { - public static PixelFormat ColorFormat { get; } = OperatingSystem.IsAndroid() ? PixelFormat.R8G8B8A8UNorm : PixelFormat.B8G8R8A8UNorm; + public static PixelFormat Format { get; } = OperatingSystem.IsAndroid() ? PixelFormat.R8G8B8A8UNorm : PixelFormat.B8G8R8A8UNorm; } diff --git a/sources/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Zenith.NET/Structs/SwapChainDesc.cs index 5cf8088b..449b0d70 100644 --- a/sources/Zenith.NET/Structs/SwapChainDesc.cs +++ b/sources/Zenith.NET/Structs/SwapChainDesc.cs @@ -4,5 +4,5 @@ public struct SwapChainDesc { public Surface Surface; - public PixelFormat ColorFormat; + public PixelFormat Format; } From 34d4e539d4fa71409d345eed14189da9bf91864d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 09:33:43 +0800 Subject: [PATCH 183/466] Set root signature in SetConstantBufferImpl Moved root signature setup from command queue logic to SetConstantBufferImpl, ensuring correct signature and constant buffer view are set based on pipeline type. Centralizes responsibility and simplifies command buffer initialization. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 325eeead..d95213ed 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -432,6 +432,16 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) { + if (pipeline is ComputePipeline) + { + CommandList.SetComputeRootSignature(Context.RootSignature); + CommandList.SetComputeRootConstantBufferView(0, buffer.DirectX12().GPUVirtualAddress + offsetInBytes); + } + else + { + CommandList.SetGraphicsRootSignature(Context.RootSignature); + CommandList.SetGraphicsRootConstantBufferView(0, buffer.DirectX12().GPUVirtualAddress + offsetInBytes); + } } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) @@ -536,17 +546,6 @@ protected override void BeginImpl() ID3D12DescriptorHeap** ppDescriptorHeaps = stackalloc ID3D12DescriptorHeap*[] { Context.CbvSrvUavHeap.Heap, Context.SamplerHeap.Heap }; CommandList.SetDescriptorHeaps(2, ppDescriptorHeaps); - - switch (Queue.Type) - { - case CommandQueueType.Graphics: - CommandList.SetGraphicsRootSignature(Context.RootSignature); - break; - - case CommandQueueType.Compute: - CommandList.SetComputeRootSignature(Context.RootSignature); - break; - } } protected override void EndImpl() From fdf1289967d8175b1b74449c4995446dfe2bbfae Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 13:57:55 +0800 Subject: [PATCH 184/466] Refactor SwapChain acquire/present and queue idle handling Refactored SwapChain to provide default Acquire and Present implementations, introducing AcquireImpl and PresentImpl for backend logic. Updated App to use new acquire/present flow. Added WaitIdle to CommandQueue and updated Destroy to use it. SwapChain.Resize and Refresh now wait for queue idle before proceeding. --- sources/Experiments/CornellBox/App.cs | 4 +++- sources/Zenith.NET/CommandQueue.cs | 7 +++++- sources/Zenith.NET/SwapChain.cs | 32 +++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 39f9e9c3..2ec51a1b 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -172,13 +172,15 @@ public static void Run() return; } + CommandSubmission acquire = swapChain.Acquire(); + CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); activeRenderer.Render(commandBuffer); imGui.Render(commandBuffer, ColorAttachment.Load(swapChain.Drawable)); - swapChain.Present(commandBuffer.Submit()).Wait(); + swapChain.Present(commandBuffer.Submit(acquire)).Wait(); }; window.Resize += _ => diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 356b4e84..03ebc48f 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -57,6 +57,11 @@ internal void Wait(ulong value) } } + internal void WaitIdle() + { + Wait(value); + } + protected abstract ulong GetCompletedValue(); protected abstract CommandBuffer CreateCommandBuffer(); @@ -67,7 +72,7 @@ internal void Wait(ulong value) protected override void Destroy() { - Wait(value); + WaitIdle(); while (commandBuffers.TryDequeue(out CommandBuffer? commandBuffer)) { diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index e5973cdb..279144a1 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -8,15 +8,37 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture Drawable { get; } - public abstract CommandSubmission Acquire(); + public CommandSubmission Acquire() + { + AcquireImpl(); + + CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); + + commandBuffer.Barrier([], [], [TextureBarrier.ColorAttachment(Drawable, null)]); + + return commandBuffer.Submit(); + } + + public CommandSubmission Present(params ReadOnlySpan waits) + { + CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); + + commandBuffer.Barrier([], [], [TextureBarrier.Present(Drawable, TextureBarrier.ColorAttachment(Drawable, null))]); - public abstract CommandSubmission Present(params ReadOnlySpan waits); + CommandSubmission submission = commandBuffer.Submit(waits); + + PresentImpl(); + + return submission; + } public void Resize(uint width, uint height) { desc.Surface.Width = width; desc.Surface.Height = height; + Context.GraphicsQueue.WaitIdle(); + ResizeImpl(); SetResourceName(Name); @@ -26,11 +48,17 @@ public void Refresh(Surface surface) { desc.Surface = surface; + Context.GraphicsQueue.WaitIdle(); + RefreshImpl(); SetResourceName(Name); } + protected abstract void AcquireImpl(); + + protected abstract void PresentImpl(); + protected abstract void ResizeImpl(); protected abstract void RefreshImpl(); From e79239da4992fd19c5e71091ebd637a411fe0ee8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 14:03:06 +0800 Subject: [PATCH 185/466] Implement DirectX 12 swap chain support Added DXSwapChain class with full resource management, updated DXGraphicsContext to instantiate DXSwapChain, and introduced an extension method to access the underlying DXSwapChain from SwapChain instances. --- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 2 +- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 161 ++++++++++++++++++ sources/Zenith.NET.DirectX12/Extensions.cs | 8 + 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 sources/Zenith.NET.DirectX12/DXSwapChain.cs diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 991bb53a..787eb6c3 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -152,7 +152,7 @@ protected override void Initialize(bool useValidationLayer, protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) { - throw new NotImplementedException(); + return new DXSwapChain(this, desc); } protected override Heap CreateHeapImpl(HeapDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs new file mode 100644 index 00000000..794e4b32 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -0,0 +1,161 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; +using Silk.NET.DXGI; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXSwapChain : SwapChain +{ + public ComPtr SwapChain; + + private DXTexture[] textures = []; + private uint index; + + public DXSwapChain(DXGraphicsContext context, SwapChainDesc desc) : base(context, desc) + { + CreateSwapChain(); + CreateTextures(); + } + + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + + public override Texture Drawable => textures[index]; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void AcquireImpl() + { + if (SwapChain.Handle is not null) + { + index = SwapChain.GetCurrentBackBufferIndex(); + } + } + + protected override void PresentImpl() + { + if (SwapChain.Handle is not null) + { + SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); + } + } + + protected override void ResizeImpl() + { + DestroyTextures(); + + if (SwapChain.Handle is not null) + { + SwapChain.ResizeBuffers((uint)textures.Length, Desc.Surface.Width, Desc.Surface.Height, DXFormats.DirectX12(Desc.Format), (uint)SwapChainFlag.AllowTearing).Success(); + } + + CreateTextures(); + } + + protected override void RefreshImpl() + { + DestroyTextures(); + DestroySwapChain(); + + CreateSwapChain(); + CreateTextures(); + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + DestroyTextures(); + DestroySwapChain(); + } + + private void CreateSwapChain() + { + if (Desc.Surface.Type is not SurfaceType.D3D11Interop) + { + const uint BufferCount = 3; + + SwapChainDesc1 swapChainDesc = new() + { + Width = Desc.Surface.Width, + Height = Desc.Surface.Height, + Format = DXFormats.DirectX12(Desc.Format), + SampleDesc = new() + { + Count = 1 + }, + BufferUsage = DXGI.UsageRenderTargetOutput, + BufferCount = BufferCount, + SwapEffect = SwapEffect.FlipDiscard, + AlphaMode = AlphaMode.Ignore, + Flags = (uint)SwapChainFlag.AllowTearing + }; + + Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsQueue.DirectX12().CommandQueue.Handle, + Desc.Surface.NativeHandles[0], + &swapChainDesc, + default(SwapChainFullscreenDesc*), + default(IDXGIOutput*), + (IDXGISwapChain1**)SwapChain.GetAddressOf()).Success(); + + textures = new DXTexture[BufferCount]; + } + else + { + textures = new DXTexture[1]; + } + } + + private void DestroySwapChain() + { + SwapChain.Dispose(); + SwapChain = default; + + index = 0; + } + + private void CreateTextures() + { + TextureDesc desc = new() + { + Type = TextureType.Texture2D, + Format = Desc.Format, + Width = Desc.Surface.Width, + Height = Desc.Surface.Height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment + }; + + if (SwapChain.Handle is not null) + { + for (int i = 0; i < textures.Length; i++) + { + ComPtr resource = new(); + SwapChain.GetBuffer((uint)i, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()); + + textures[i] = new(Context, desc, resource); + } + } + else + { + Context.Device.OpenSharedHandle((void*)Desc.Surface.NativeHandles[0], out ComPtr resource).Success(); + + textures[0] = new(Context, desc, resource); + } + } + + private void DestroyTextures() + { + for (int i = 0; i < textures.Length; i++) + { + textures[i].Dispose(); + } + } +} diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 0f94dce8..9e2a9cff 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -44,6 +44,14 @@ internal DXCommandBuffer DirectX12() } } + extension(SwapChain swapChain) + { + internal DXSwapChain DirectX12() + { + return (DXSwapChain)swapChain; + } + } + extension(Heap heap) { internal DXHeap DirectX12() From e817193f9f4ed128ad7e587c07f1bc6042bd199c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 7 Jun 2026 17:17:04 +0800 Subject: [PATCH 186/466] Update Avalonia and Slangc.NET package versions Upgraded Avalonia to 12.0.4 and Slangc.NET to 2026.10.3 to keep dependencies current and benefit from the latest fixes and improvements. --- sources/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 1b98393c..1541b8c5 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,7 +5,7 @@ - + @@ -20,7 +20,7 @@ - + From 647fc85a05e09780da975f94715ad5c04ec7d5d0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 12:55:42 +0800 Subject: [PATCH 187/466] Refactor resource barriers and sync model Simplifies and unifies resource barrier and synchronization logic: - Replaces PipelineStages, ResourceAccess, and barrier structs with a new BarrierStages enum. - Refactors CommandBuffer and CommandQueue APIs for a stage-based barrier model. - Adds per-subresource layout tracking to Texture. - Updates buffer/texture upload/download and SwapChain present logic. - Removes obsolete APIs and code from the old barrier model. --- sources/Zenith.NET/Buffer.cs | 4 +- sources/Zenith.NET/CommandBuffer.cs | 57 ++++++- sources/Zenith.NET/CommandQueue.cs | 39 +++-- sources/Zenith.NET/Enums/BarrierStages.cs | 17 ++ sources/Zenith.NET/Enums/PipelineStages.cs | 37 ---- sources/Zenith.NET/Enums/ResourceAccess.cs | 41 ----- sources/Zenith.NET/Structs/BufferBarrier.cs | 110 ------------ sources/Zenith.NET/Structs/MemoryBarrier.cs | 45 ----- sources/Zenith.NET/Structs/TextureBarrier.cs | 170 ------------------- sources/Zenith.NET/SwapChain.cs | 27 +-- sources/Zenith.NET/Texture.cs | 17 +- 11 files changed, 114 insertions(+), 450 deletions(-) create mode 100644 sources/Zenith.NET/Enums/BarrierStages.cs delete mode 100644 sources/Zenith.NET/Enums/PipelineStages.cs delete mode 100644 sources/Zenith.NET/Enums/ResourceAccess.cs delete mode 100644 sources/Zenith.NET/Structs/BufferBarrier.cs delete mode 100644 sources/Zenith.NET/Structs/MemoryBarrier.cs delete mode 100644 sources/Zenith.NET/Structs/TextureBarrier.cs diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 1929dae2..76075fb4 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -31,7 +31,7 @@ public void Upload(uint offsetInBytes, BufferData data) } else { - CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); commandBuffer.Upload(this, offsetInBytes, data); commandBuffer.Submit().Wait(); @@ -53,7 +53,7 @@ public void Download(uint offsetInBytes, BufferData data) } else { - CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); commandBuffer.Download(this, offsetInBytes, data); commandBuffer.Submit().Wait(); diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 8f5dee0f..c6e3f28c 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -8,14 +8,29 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) public CommandQueue Queue => queue; - public CommandSubmission Submit(params ReadOnlySpan waits) + public CommandSubmission Submit(params ReadOnlySpan submissions) { - return queue.Submit(this, waits); + queue.Wait(submissions); + queue.Submit(this); + + return queue.Signal(); } - public void Barrier(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) + public void Barrier(BarrierStages after, BarrierStages before) { - BarrierImpl(memoryBarriers, bufferBarriers, textureBarriers); + BarrierImpl(after, before); + } + + public void Transition(Texture texture, TextureSubresource subresource, TextureLayout layout) + { + if (texture[subresource] == layout) + { + return; + } + + TransitionImpl(texture, subresource, texture[subresource], layout); + + texture[subresource] = layout; } public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) @@ -115,22 +130,52 @@ public void CopyBuffer(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOf public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { + TextureLayout dstLayout = dst[dstSubresource]; + + Transition(dst, dstSubresource, TextureLayout.CopyDst); + CopyBufferToTextureImpl(src, srcOffsetInBytes, srcRowStrideInBytes, srcSliceStrideInBytes, dst, dstSubresource, dstOffset, dstExtent); + + Transition(dst, dstSubresource, dstLayout); } public void CopyTexture(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { + TextureLayout srcLayout = src[srcSubresource]; + TextureLayout dstLayout = dst[dstSubresource]; + + Transition(src, srcSubresource, TextureLayout.CopySrc); + Transition(dst, dstSubresource, TextureLayout.CopyDst); + CopyTextureImpl(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, extent); + + Transition(src, srcSubresource, srcLayout); + Transition(dst, dstSubresource, dstLayout); } public void CopyTextureToBuffer(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { + TextureLayout srcLayout = src[srcSubresource]; + + Transition(src, srcSubresource, TextureLayout.CopySrc); + CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstRowStrideInBytes, dstSliceStrideInBytes); + + Transition(src, srcSubresource, srcLayout); } public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { + TextureLayout srcLayout = src[srcSubresource]; + TextureLayout dstLayout = dst[dstSubresource]; + + Transition(src, srcSubresource, TextureLayout.ResolveSrc); + Transition(dst, dstSubresource, TextureLayout.ResolveDst); + ResolveTextureImpl(src, srcSubresource, dst, dstSubresource); + + Transition(src, srcSubresource, srcLayout); + Transition(dst, dstSubresource, dstLayout); } public BottomLevelAccelerationStructure BuildAccelerationStructure(BottomLevelAccelerationStructureDesc desc) @@ -445,7 +490,9 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip return false; } - protected abstract void BarrierImpl(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers); + protected abstract void BarrierImpl(BarrierStages after, BarrierStages before); + + protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout); protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes); diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 03ebc48f..5920e34a 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -10,7 +10,7 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ public CommandQueueType Type { get; } = type; - public CommandBuffer AcquireCommandBuffer() + public CommandBuffer CommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); @@ -21,33 +21,40 @@ public CommandBuffer AcquireCommandBuffer() return commandBuffer; } - internal CommandSubmission Submit(CommandBuffer commandBuffer, ReadOnlySpan waits) + internal CommandSubmission Signal() + { + using Lock.Scope _ = @lock.EnterScope(); + + SignalImpl(++value); + + return new(this, value); + } + + internal void Submit(CommandBuffer commandBuffer) { using Lock.Scope _ = @lock.EnterScope(); commandBuffer.End(); - SubmitImpl(commandBuffer, waits, ++value); + SubmitImpl(commandBuffer); submitteds.Enqueue(new(commandBuffer, value)); - - return new(this, value); } internal void Wait(ulong value) { using Lock.Scope _ = @lock.EnterScope(); - ulong completed = GetCompletedValue(); + ulong completedValue = GetCompletedValue(); - if (completed < value) + if (completedValue < value) { WaitImpl(value); - completed = GetCompletedValue(); + completedValue = GetCompletedValue(); } - while (submitteds.TryPeek(out Submitted submitted) && submitted.Value <= completed) + while (submitteds.TryPeek(out Submitted submitted) && submitted.Value <= completedValue) { submitteds.Dequeue(); @@ -57,23 +64,27 @@ internal void Wait(ulong value) } } - internal void WaitIdle() + internal void Wait(ReadOnlySpan submissions) { - Wait(value); + using Lock.Scope _ = @lock.EnterScope(); + + WaitImpl(submissions); } protected abstract ulong GetCompletedValue(); protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue); + protected abstract void SignalImpl(ulong signalValue); + + protected abstract void SubmitImpl(CommandBuffer commandBuffer); protected abstract void WaitImpl(ulong waitValue); + protected abstract void WaitImpl(ReadOnlySpan submissions); + protected override void Destroy() { - WaitIdle(); - while (commandBuffers.TryDequeue(out CommandBuffer? commandBuffer)) { commandBuffer.Dispose(); diff --git a/sources/Zenith.NET/Enums/BarrierStages.cs b/sources/Zenith.NET/Enums/BarrierStages.cs new file mode 100644 index 00000000..2b22120a --- /dev/null +++ b/sources/Zenith.NET/Enums/BarrierStages.cs @@ -0,0 +1,17 @@ +namespace Zenith.NET; + +[Flags] +public enum BarrierStages +{ + None = 0, + + Vertex = 1 << 0, + + Fragment = 1 << 1, + + Compute = 1 << 2, + + Copy = 1 << 3, + + All = 1 << 4 +} diff --git a/sources/Zenith.NET/Enums/PipelineStages.cs b/sources/Zenith.NET/Enums/PipelineStages.cs deleted file mode 100644 index 47472315..00000000 --- a/sources/Zenith.NET/Enums/PipelineStages.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum PipelineStages -{ - None = 0, - - Indirect = 1 << 0, - - VertexInput = 1 << 1, - - VertexShader = 1 << 2, - - TaskShader = 1 << 3, - - MeshShader = 1 << 4, - - EarlyFragmentTests = 1 << 5, - - FragmentShader = 1 << 6, - - LateFragmentTests = 1 << 7, - - ColorAttachmentOutput = 1 << 8, - - ComputeShader = 1 << 9, - - AccelerationStructureBuild = 1 << 10, - - Copy = 1 << 11, - - Resolve = 1 << 12, - - AllGraphics = 1 << 13, - - AllCommands = 1 << 14 -} diff --git a/sources/Zenith.NET/Enums/ResourceAccess.cs b/sources/Zenith.NET/Enums/ResourceAccess.cs deleted file mode 100644 index 0fb794e9..00000000 --- a/sources/Zenith.NET/Enums/ResourceAccess.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum ResourceAccess -{ - None = 0, - - Vertex = 1 << 0, - - Index = 1 << 1, - - Indirect = 1 << 2, - - Constant = 1 << 3, - - ShaderRead = 1 << 4, - - ShaderWrite = 1 << 5, - - AccelerationStructureRead = 1 << 6, - - AccelerationStructureWrite = 1 << 7, - - ColorAttachmentRead = 1 << 8, - - ColorAttachmentWrite = 1 << 9, - - DepthStencilAttachmentRead = 1 << 10, - - DepthStencilAttachmentWrite = 1 << 11, - - CopyRead = 1 << 12, - - CopyWrite = 1 << 13, - - ResolveRead = 1 << 14, - - ResolveWrite = 1 << 15, - - Present = 1 << 16 -} diff --git a/sources/Zenith.NET/Structs/BufferBarrier.cs b/sources/Zenith.NET/Structs/BufferBarrier.cs deleted file mode 100644 index a05c4b2c..00000000 --- a/sources/Zenith.NET/Structs/BufferBarrier.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace Zenith.NET; - -public struct BufferBarrier -{ - public Buffer Buffer; - - public PipelineStages SrcStages; - - public PipelineStages DstStages; - - public ResourceAccess SrcAccess; - - public ResourceAccess DstAccess; - - public static BufferBarrier Vertex(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexInput, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.Vertex - }; - } - - public static BufferBarrier Index(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexInput, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.Index - }; - } - - public static BufferBarrier Constant(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.Constant - }; - } - - public static BufferBarrier Indirect(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Indirect, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.Indirect - }; - } - - public static BufferBarrier ShaderRead(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ShaderRead - }; - } - - public static BufferBarrier Storage(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite - }; - } - - public static BufferBarrier CopySrc(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Copy, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.CopyRead - }; - } - - public static BufferBarrier CopyDst(Buffer buffer, BufferBarrier? previous) - { - return new() - { - Buffer = buffer, - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Copy, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.CopyWrite - }; - } -} diff --git a/sources/Zenith.NET/Structs/MemoryBarrier.cs b/sources/Zenith.NET/Structs/MemoryBarrier.cs deleted file mode 100644 index a5c488cd..00000000 --- a/sources/Zenith.NET/Structs/MemoryBarrier.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Zenith.NET; - -public struct MemoryBarrier -{ - public PipelineStages SrcStages; - - public PipelineStages DstStages; - - public ResourceAccess SrcAccess; - - public ResourceAccess DstAccess; - - public static MemoryBarrier Storage(MemoryBarrier? previous) - { - return new() - { - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, - SrcAccess = previous?.DstAccess ?? ResourceAccess.ShaderWrite, - DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite - }; - } - - public static MemoryBarrier AccelerationStructureRead(MemoryBarrier? previous) - { - return new() - { - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.FragmentShader | PipelineStages.ComputeShader | PipelineStages.AccelerationStructureBuild, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.AccelerationStructureRead - }; - } - - public static MemoryBarrier AccelerationStructureWrite(MemoryBarrier? previous) - { - return new() - { - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.AccelerationStructureBuild, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.AccelerationStructureWrite - }; - } -} diff --git a/sources/Zenith.NET/Structs/TextureBarrier.cs b/sources/Zenith.NET/Structs/TextureBarrier.cs deleted file mode 100644 index d18d015c..00000000 --- a/sources/Zenith.NET/Structs/TextureBarrier.cs +++ /dev/null @@ -1,170 +0,0 @@ -namespace Zenith.NET; - -public struct TextureBarrier -{ - public Texture Texture; - - public TextureSubresourceRange Range; - - public PipelineStages SrcStages; - - public PipelineStages DstStages; - - public ResourceAccess SrcAccess; - - public ResourceAccess DstAccess; - - public TextureLayout SrcLayout; - - public TextureLayout DstLayout; - - public static TextureBarrier ColorAttachment(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.ColorAttachmentOutput, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ColorAttachmentWrite, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.ColorAttachment - }; - } - - public static TextureBarrier DepthStencilAttachment(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.EarlyFragmentTests | PipelineStages.LateFragmentTests, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.DepthStencilAttachmentWrite, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.DepthStencilAttachment - }; - } - - public static TextureBarrier DepthStencilRead(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.EarlyFragmentTests | PipelineStages.FragmentShader | PipelineStages.LateFragmentTests, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.DepthStencilAttachmentRead, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.DepthStencilReadOnly - }; - } - - public static TextureBarrier ShaderRead(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ShaderRead, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.Sampled - }; - } - - public static TextureBarrier Storage(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.VertexShader | PipelineStages.FragmentShader | PipelineStages.ComputeShader, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ShaderRead | ResourceAccess.ShaderWrite, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.Storage - }; - } - - public static TextureBarrier CopySrc(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Copy, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.CopyRead, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.CopySrc - }; - } - - public static TextureBarrier CopyDst(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Copy, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.CopyWrite, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.CopyDst - }; - } - - public static TextureBarrier ResolveSrc(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Resolve, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ResolveRead, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.ResolveSrc - }; - } - - public static TextureBarrier ResolveDst(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.Resolve, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.ResolveWrite, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.ResolveDst - }; - } - - public static TextureBarrier Present(Texture texture, TextureBarrier? previous) - { - return new() - { - Texture = texture, - Range = TextureSubresourceRange.All(texture), - SrcStages = previous?.DstStages ?? PipelineStages.None, - DstStages = PipelineStages.None, - SrcAccess = previous?.DstAccess ?? ResourceAccess.None, - DstAccess = ResourceAccess.Present, - SrcLayout = previous?.DstLayout ?? TextureLayout.Undefined, - DstLayout = TextureLayout.Present - }; - } -} diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 279144a1..47389bc0 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -8,28 +8,13 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture Drawable { get; } - public CommandSubmission Acquire() + public CommandSubmission Present(params ReadOnlySpan submissions) { - AcquireImpl(); - - CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); - - commandBuffer.Barrier([], [], [TextureBarrier.ColorAttachment(Drawable, null)]); - - return commandBuffer.Submit(); - } - - public CommandSubmission Present(params ReadOnlySpan waits) - { - CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); - - commandBuffer.Barrier([], [], [TextureBarrier.Present(Drawable, TextureBarrier.ColorAttachment(Drawable, null))]); - - CommandSubmission submission = commandBuffer.Submit(waits); + Context.GraphicsQueue.Wait(submissions); PresentImpl(); - return submission; + return Context.GraphicsQueue.Signal(); } public void Resize(uint width, uint height) @@ -37,8 +22,6 @@ public void Resize(uint width, uint height) desc.Surface.Width = width; desc.Surface.Height = height; - Context.GraphicsQueue.WaitIdle(); - ResizeImpl(); SetResourceName(Name); @@ -48,15 +31,11 @@ public void Refresh(Surface surface) { desc.Surface = surface; - Context.GraphicsQueue.WaitIdle(); - RefreshImpl(); SetResourceName(Name); } - protected abstract void AcquireImpl(); - protected abstract void PresentImpl(); protected abstract void ResizeImpl(); diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index b0df7b16..a2937502 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -2,6 +2,8 @@ public abstract class Texture(GraphicsContext context, TextureDesc desc) : GraphicsResource(context) { + private readonly TextureLayout[] layouts = new TextureLayout[desc.MipLevels * desc.ArrayLayers]; + private TextureDesc desc = desc; public ref readonly TextureDesc Desc => ref desc; @@ -10,9 +12,15 @@ public abstract class Texture(GraphicsContext context, TextureDesc desc) : Graph public abstract ResourceHandle StorageHandle { get; } + internal TextureLayout this[TextureSubresource subresource] + { + get => layouts[(subresource.MipLevel * Desc.ArrayLayers) + subresource.ArrayLayer]; + set => layouts[(subresource.MipLevel * Desc.ArrayLayers) + subresource.ArrayLayer] = value; + } + public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); commandBuffer.Upload(this, subresource, offset, extent, data); commandBuffer.Submit().Wait(); @@ -20,9 +28,14 @@ public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D ext public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - CommandBuffer commandBuffer = Context.CopyQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); commandBuffer.Download(this, subresource, offset, extent, data); commandBuffer.Submit().Wait(); } + + public void OverrideLayout(TextureSubresource subresource, TextureLayout layout) + { + this[subresource] = layout; + } } From ed0ed7f3066470970c06b976eadca2b7181cf864 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 13:00:48 +0800 Subject: [PATCH 188/466] Update ImGui renderer pipeline and resource handling - Initialize resourceHandleBindings with custom comparer for ImTextureID - Change color input layout to UByte4UNorm - Switch blend state to NonPremultiplied - Refactor constantsArray assignment for clarity and correctness - Use CommandBuffer() instead of AcquireCommandBuffer() in Extensions.cs --- .../ImGuiRenderer.cs | 35 ++++++++++--------- .../Extensions.cs | 2 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index aa3364c5..5529bdd4 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -69,7 +69,7 @@ float4 FSMain(VSOutput input) : SV_TARGET private readonly GraphicsPipeline graphicsPipeline; private readonly Dictionary textureBindings = []; private readonly Dictionary textureViewBindings = []; - private readonly SortedList resourceHandleBindings = []; + private readonly SortedList resourceHandleBindings = new(Comparer.Create(static (x, y) => x.Handle.CompareTo(y.Handle))); private readonly Dictionary drawDataTextures = []; private Buffer? vertexBuffer; @@ -96,7 +96,7 @@ public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormat InputLayout inputLayout = new(); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.Position }); inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.TexCoord }); - inputLayout.Add(new() { Format = ElementFormat.UByte4, Semantic = ElementSemantic.Color }); + inputLayout.Add(new() { Format = ElementFormat.UByte4UNorm, Semantic = ElementSemantic.Color }); graphicsPipeline = context.CreateGraphicsPipeline(new() { @@ -109,7 +109,7 @@ public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormat { RasterizerState = RasterizerState.CullNone(), DepthStencilState = DepthStencilState.DepthNone(), - BlendState = BlendState.AlphaBlend() + BlendState = BlendState.NonPremultiplied() } }); @@ -328,30 +328,31 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, indexOffset += drawListPtr.IdxBuffer.Size; } - Constants[] constantsArray = ArrayPool.Shared.Rent(resourceHandleBindings.Count); + Matrix4x4 projection = Matrix4x4.CreateOrthographicOffCenter(drawData.DisplayPos.X, + drawData.DisplayPos.X + drawData.DisplaySize.X, + drawData.DisplayPos.Y + drawData.DisplaySize.Y, + drawData.DisplayPos.Y, + 0.0f, + 1.0f); - Constants constants = new() - { - Projection = Matrix4x4.CreateOrthographicOffCenter(drawData.DisplayPos.X, - drawData.DisplayPos.X + drawData.DisplaySize.X, - drawData.DisplayPos.Y + drawData.DisplaySize.Y, - drawData.DisplayPos.Y, - 0.0f, - 1.0f), - Sampler = sampler.Handle - }; + Constants[] constants = ArrayPool.Shared.Rent(resourceHandleBindings.Count); for (int i = 0; i < resourceHandleBindings.Count; i++) { - constantsArray[i] = constants with { Texture = resourceHandleBindings[i] }; + constants[i] = new() + { + Projection = projection, + Sampler = sampler.Handle, + Texture = resourceHandleBindings.Values[i] + }; } - fixed (Constants* pointer = constantsArray) + fixed (Constants* pointer = constants) { constantBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Constants) * resourceHandleBindings.Count) }); } - ArrayPool.Shared.Return(constantsArray); + ArrayPool.Shared.Return(constants); commandBuffer.BeginDebugEvent("ImGui"); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index 9d5792d5..2375914e 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -23,7 +23,7 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) Rgba32[] pixels = new Rgba32[image.Width * image.Height]; image.CopyPixelDataTo(pixels); - CommandBuffer commandBuffer = context.CopyQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = context.CopyQueue.CommandBuffer(); unsafe { From 9fb249d704d58aef6a9ebbacd19a374d8ad18732 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 13:03:58 +0800 Subject: [PATCH 189/466] Update resource transitions and buffer usages Added explicit resource transitions for swap chain, compute, and rasterization renderers to ensure correct texture layouts. Simplified buffer usages in path tracing renderer by removing AccelerationStructure flag. Minor cleanup in Renderer.cs. --- sources/Experiments/CornellBox/App.cs | 10 ++++++---- .../CornellBox/Renderers/PathTracingRenderer.cs | 11 ++++++++--- .../CornellBox/Renderers/RasterizationRenderer.cs | 5 +++++ sources/Experiments/CornellBox/Renderers/Renderer.cs | 1 - 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 2ec51a1b..10c57492 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -172,15 +172,17 @@ public static void Run() return; } - CommandSubmission acquire = swapChain.Acquire(); - - CommandBuffer commandBuffer = Context.GraphicsQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); activeRenderer.Render(commandBuffer); + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment); + imGui.Render(commandBuffer, ColorAttachment.Load(swapChain.Drawable)); - swapChain.Present(commandBuffer.Submit(acquire)).Wait(); + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Present); + + swapChain.Present(commandBuffer.Submit()).Wait(); }; window.Resize += _ => diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 72cba7d4..60f68e9c 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -32,7 +32,7 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), StrideInBytes = (uint)sizeof(Vertex), - Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst, + Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst, Residency = MemoryResidency.GpuOnly }); @@ -45,7 +45,7 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(uint) * indices.Length), StrideInBytes = sizeof(uint), - Usages = BufferUsages.StorageReadOnly | BufferUsages.AccelerationStructure | BufferUsages.CopyDst, + Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst, Residency = MemoryResidency.GpuOnly }); @@ -69,7 +69,7 @@ public PathTracingRenderer() pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); - CommandBuffer commandBuffer = App.Context.ComputeQueue.AcquireCommandBuffer(); + CommandBuffer commandBuffer = App.Context.ComputeQueue.CommandBuffer(); blas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc { @@ -166,11 +166,16 @@ public override void Update(CameraHandler camera) public override void Render(CommandBuffer commandBuffer) { + commandBuffer.Transition(Color, default, TextureLayout.Storage); + commandBuffer.Transition(accumulationTexture!, default, TextureLayout.Storage); + commandBuffer.SetPipeline(pipeline); commandBuffer.SetConstantBuffer(constantBuffer, 0); commandBuffer.Dispatch((App.Width + ThreadGroupSize - 1) / ThreadGroupSize, (App.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1); + commandBuffer.Transition(Color, default, TextureLayout.Sampled); + FrameCount++; } diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 3f74ccea..213889db 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -101,6 +101,9 @@ public override void Update(CameraHandler camera) public override void Render(CommandBuffer commandBuffer) { + commandBuffer.Transition(Color, default, TextureLayout.ColorAttachment); + commandBuffer.Transition(DepthStencil, default, TextureLayout.DepthStencilAttachment); + commandBuffer.BeginRenderPass([ColorAttachment.Clear(Color, new(0.51f, 0.518f, 0.557f, 1.0f))], DepthStencilAttachment.Clear(DepthStencil, 1.0f, 0)); commandBuffer.SetPipeline(pipeline); @@ -111,6 +114,8 @@ public override void Render(CommandBuffer commandBuffer) commandBuffer.DrawIndexed(indexCount, 1, 0, 0, 0); commandBuffer.EndRenderPass(); + + commandBuffer.Transition(Color, default, TextureLayout.Sampled); } public override void Dispose() diff --git a/sources/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Experiments/CornellBox/Renderers/Renderer.cs index d85af918..9c6b52ee 100644 --- a/sources/Experiments/CornellBox/Renderers/Renderer.cs +++ b/sources/Experiments/CornellBox/Renderers/Renderer.cs @@ -55,7 +55,6 @@ public virtual void Resize(uint width, uint height) SampleCount = SampleCount.Count1, Usages = TextureUsages.DepthStencilAttachment }); - } public virtual void Dispose() From 49d5ccc6a71ef4cf16c184c6cfa398b64f259261 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 17:44:11 +0800 Subject: [PATCH 190/466] Refactor barriers, buffer usages, and remove accel struct Swapped Barrier parameter order for consistency. Updated Transition to handle Undefined layout. Refactored BufferUsages enum, removing AccelerationStructure and adjusting CopySrc/CopyDst bits. Removed BufferDesc.AccelerationStructure method. --- sources/Zenith.NET/CommandBuffer.cs | 8 ++++---- sources/Zenith.NET/Enums/BufferUsages.cs | 6 ++---- sources/Zenith.NET/Structs/BufferDesc.cs | 11 ----------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index c6e3f28c..a354a6c6 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -16,14 +16,14 @@ public CommandSubmission Submit(params ReadOnlySpan submissio return queue.Signal(); } - public void Barrier(BarrierStages after, BarrierStages before) + public void Barrier(BarrierStages before, BarrierStages after) { - BarrierImpl(after, before); + BarrierImpl(before, after); } public void Transition(Texture texture, TextureSubresource subresource, TextureLayout layout) { - if (texture[subresource] == layout) + if (layout is TextureLayout.Undefined || texture[subresource] == layout) { return; } @@ -490,7 +490,7 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip return false; } - protected abstract void BarrierImpl(BarrierStages after, BarrierStages before); + protected abstract void BarrierImpl(BarrierStages before, BarrierStages after); protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout); diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs index bfba8e3d..fa8eb33d 100644 --- a/sources/Zenith.NET/Enums/BufferUsages.cs +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -17,9 +17,7 @@ public enum BufferUsages StorageReadWrite = 1 << 5, - AccelerationStructure = 1 << 6, + CopySrc = 1 << 6, - CopySrc = 1 << 7, - - CopyDst = 1 << 8 + CopyDst = 1 << 7 } diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index ffd433f5..73a727a8 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -76,17 +76,6 @@ public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) }; } - public static BufferDesc AccelerationStructure(uint sizeInBytes) - { - return new() - { - SizeInBytes = sizeInBytes, - StrideInBytes = 0, - Usages = BufferUsages.AccelerationStructure | BufferUsages.CopyDst, - Residency = MemoryResidency.GpuOnly - }; - } - public static BufferDesc Staging(uint sizeInBytes) { return new() From 9fbf51bdd5bea9eadb8b78039a8680983fd28b3d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 17:57:22 +0800 Subject: [PATCH 191/466] Refactor DX12 backend: buffers, barriers, and pipelines - Unify buffer/texture constructors to always create resources internally - Refactor barrier handling for global/texture barriers in DXCommandBuffer - Use CreateComputePipelineState for compute pipelines - Return both topology type and value; set topology on command list - Rework DXFormats for structured enum-to-DX12 conversions - Improve buffer resource flag handling (UAV, acceleration structures) - Separate signal/wait in command queue submission logic - Add explicit global barriers after acceleration structure builds - Remove unused usings and redundant code - Apply code style improvements and consistent naming --- .../DXBottomLevelAccelerationStructure.cs | 53 ++- sources/Zenith.NET.DirectX12/DXBuffer.cs | 82 +++-- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 119 +++---- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 19 +- .../Zenith.NET.DirectX12/DXComputePipeline.cs | 12 +- sources/Zenith.NET.DirectX12/DXFormats.cs | 325 ++++++------------ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 4 +- .../DXGraphicsPipeline.cs | 2 +- .../DXMeshShadingPipeline.cs | 2 +- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 6 +- sources/Zenith.NET.DirectX12/DXTexture.cs | 49 +-- .../DXTopLevelAccelerationStructure.cs | 53 ++- sources/Zenith.NET.DirectX12/Usings.cs | 1 - 13 files changed, 361 insertions(+), 366 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 0f5e58dc..929e3c45 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -10,16 +10,29 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu { using ZenithMarshal.Scope scope = new(); - TransformBuffer = new(context, new() { SizeInBytes = (uint)(sizeof(Matrix3X4) * desc.Geometries.Length), Residency = MemoryResidency.CpuWriteOnly }, default); + TransformBuffer = new(context, new() + { + SizeInBytes = (uint)(sizeof(Matrix3X4) * desc.Geometries.Length), + Residency = MemoryResidency.CpuWriteOnly + }); FillInputs(scope, desc, out BuildRaytracingAccelerationStructureInputs inputs); RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - AccelerationStructureBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, Usages = BufferUsages.AccelerationStructure, Residency = MemoryResidency.GpuOnly }, default); + AccelerationStructureBuffer = new(context, new() + { + SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, + Residency = MemoryResidency.GpuOnly + }, ResourceFlags.RaytracingAccelerationStructure); - ScratchBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, Usages = BufferUsages.StorageReadWrite, Residency = MemoryResidency.GpuOnly }, default); + ScratchBuffer = new(context, new() + { + SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly + }); BuildRaytracingAccelerationStructureDesc buildDesc = new() { @@ -29,6 +42,23 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + + GlobalBarrier barrier = new() + { + SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, + SyncAfter = BarrierSync.BuildRaytracingAccelerationStructure, + AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, + AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead + }; + + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; + + commandBuffer.CommandList.Barrier(1, &barrierGroup); } public DXBuffer TransformBuffer { get; } @@ -53,6 +83,23 @@ public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructu }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + + GlobalBarrier barrier = new() + { + SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, + SyncAfter = BarrierSync.BuildRaytracingAccelerationStructure, + AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, + AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead + }; + + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; + + commandBuffer.CommandList.Barrier(1, &barrierGroup); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index c8714b2a..ac8501f0 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -10,32 +10,70 @@ internal unsafe class DXBuffer : Buffer public ulong GPUVirtualAddress; - public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr? resource) : base(context, desc) + public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc) { - if (resource is null) + ResourceDesc1 resourceDesc = ResourceDesc(desc); + + HeapProperties heapProperties = new() { - ResourceDesc1 resourceDesc = ResourceDesc(desc); + Type = DXFormats.DirectX12(desc.Residency) + }; - HeapProperties heapProperties = new() - { - Type = DXFormats.DirectX12(desc.Residency) - }; - - context.Device.CreateCommittedResource3(&heapProperties, - HeapFlags.None, - &resourceDesc, - BarrierLayout.Undefined, - default(ClearValue*), - default(ID3D12ProtectedResourceSession*), - 0, - default(Format*), - SilkMarshal.GuidPtrOf(), - (void**)Resource.GetAddressOf()).Success(); - } - else + context.Device.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); + + GPUVirtualAddress = Resource.GetGPUVirtualAddress(); + + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); + } + + public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr resource) : base(context, desc) + { + Resource = resource; + + GPUVirtualAddress = Resource.GetGPUVirtualAddress(); + + View = new(context, new() { - Resource = resource.Value; - } + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); + } + + public DXBuffer(DXGraphicsContext context, BufferDesc desc, ResourceFlags flags) : base(context, desc) + { + ResourceDesc1 resourceDesc = ResourceDesc(desc); + resourceDesc.Flags |= flags; + + HeapProperties heapProperties = new() + { + Type = DXFormats.DirectX12(desc.Residency) + }; + + context.Device.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); GPUVirtualAddress = Resource.GetGPUVirtualAddress(); diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index d95213ed..9179cd89 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -25,86 +25,63 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - protected override void BarrierImpl(ReadOnlySpan memoryBarriers, ReadOnlySpan bufferBarriers, ReadOnlySpan textureBarriers) + protected override void BarrierImpl(BarrierStages before, BarrierStages after) { - GlobalBarrier* pGlobalBarriers = stackalloc GlobalBarrier[memoryBarriers.Length]; - for (int i = 0; i < memoryBarriers.Length; i++) - { - MemoryBarrier barrier = memoryBarriers[i]; + (BarrierSync syncBefore, BarrierAccess accessBefore) = DXFormats.DirectX12(before); + (BarrierSync syncAfter, BarrierAccess accessAfter) = DXFormats.DirectX12(after); - pGlobalBarriers[i] = new() - { - SyncBefore = DXFormats.DirectX12(barrier.SrcStages), - SyncAfter = DXFormats.DirectX12(barrier.DstStages), - AccessBefore = DXFormats.DirectX12(barrier.SrcAccess), - AccessAfter = DXFormats.DirectX12(barrier.DstAccess) - }; - } + GlobalBarrier barrier = new() + { + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter + }; - DxBufferBarrier* pBufferBarriers = stackalloc DxBufferBarrier[bufferBarriers.Length]; - for (int i = 0; i < bufferBarriers.Length; i++) + BarrierGroup barrierGroup = new() { - BufferBarrier barrier = bufferBarriers[i]; + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; - pBufferBarriers[i] = new() - { - SyncBefore = DXFormats.DirectX12(barrier.SrcStages), - SyncAfter = DXFormats.DirectX12(barrier.DstStages), - AccessBefore = DXFormats.DirectX12(barrier.SrcAccess), - AccessAfter = DXFormats.DirectX12(barrier.DstAccess), - PResource = barrier.Buffer.DirectX12().Resource, - Size = ulong.MaxValue - }; - } + CommandList.Barrier(1, &barrierGroup); + } - DxTextureBarrier* pTextureBarriers = stackalloc DxTextureBarrier[textureBarriers.Length]; - for (int i = 0; i < textureBarriers.Length; i++) - { - TextureBarrier barrier = textureBarriers[i]; + protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) + { + DXTexture dxTexture = texture.DirectX12(); - pTextureBarriers[i] = new() - { - SyncBefore = DXFormats.DirectX12(barrier.SrcStages), - SyncAfter = DXFormats.DirectX12(barrier.DstStages), - AccessBefore = DXFormats.DirectX12(barrier.SrcAccess), - AccessAfter = DXFormats.DirectX12(barrier.DstAccess), - LayoutBefore = DXFormats.DirectX12(barrier.SrcLayout), - LayoutAfter = DXFormats.DirectX12(barrier.DstLayout), - PResource = barrier.Texture.DirectX12().Resource, - Subresources = new() - { - IndexOrFirstMipLevel = barrier.Range.BaseMipLevel, - NumMipLevels = barrier.Range.LevelCount, - FirstArraySlice = barrier.Range.BaseArrayLayer, - NumArraySlices = barrier.Range.LayerCount, - NumPlanes = 1 - } - }; - } + (BarrierSync syncBefore, BarrierAccess accessBefore, BarrierLayout layoutBefore) = DXFormats.DirectX12(srcLayout); + (BarrierSync syncAfter, BarrierAccess accessAfter, BarrierLayout layoutAfter) = DXFormats.DirectX12(dstLayout); - BarrierGroup* pBarrierGroups = stackalloc BarrierGroup[] + DxTextureBarrier barrier = new() { - new() + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter, + LayoutBefore = layoutBefore, + LayoutAfter = layoutAfter, + PResource = dxTexture.Resource, + Subresources = new() { - Type = BarrierType.Global, - NumBarriers = (uint)memoryBarriers.Length, - PGlobalBarriers = pGlobalBarriers - }, - new() - { - Type = BarrierType.Buffer, - NumBarriers = (uint)bufferBarriers.Length, - PBufferBarriers = pBufferBarriers - }, - new() - { - Type = BarrierType.Texture, - NumBarriers = (uint)textureBarriers.Length, - PTextureBarriers = pTextureBarriers + IndexOrFirstMipLevel = subresource.MipLevel, + NumMipLevels = 1, + FirstArraySlice = subresource.ArrayLayer, + NumArraySlices = 1, + NumPlanes = ZenithHelper.HasStencil(dxTexture.Desc.Format) ? 2u : 1u } }; - CommandList.Barrier(3, pBarrierGroups); + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Texture, + NumBarriers = 1, + PTextureBarriers = &barrier + }; + + CommandList.Barrier(1, &barrierGroup); } protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) @@ -284,7 +261,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA }; } - RenderPassDepthStencilDesc depthStencil = default; + RenderPassDepthStencilDesc* pDepthStencil = stackalloc RenderPassDepthStencilDesc[depthStencilAttachment.HasValue ? 1 : 0]; if (depthStencilAttachment.HasValue) { DepthStencilAttachment attachment = depthStencilAttachment.Value; @@ -301,7 +278,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA } }; - depthStencil = new() + pDepthStencil[0] = new() { CpuDescriptor = texture.GetDsvHandle(attachment.Subresource), DepthBeginningAccess = new() @@ -331,7 +308,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA }; } - CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, &depthStencil, RenderPassFlags.None); + CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, pDepthStencil, RenderPassFlags.None); } protected override void EndRenderPassImpl() @@ -384,6 +361,8 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) protected override void SetPipelineImpl(GraphicsPipeline pipeline) { CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + + CommandList.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).Topology); } protected override void SetPipelineImpl(ComputePipeline pipeline) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 1ff7806e..7945a090 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -35,15 +35,14 @@ protected override CommandBuffer CreateCommandBuffer() return new DXCommandBuffer(Context, this); } - protected override void SubmitImpl(CommandBuffer commandBuffer, ReadOnlySpan waits, ulong signalValue) + protected override void SignalImpl(ulong signalValue) { - foreach (CommandSubmission wait in waits) - { - CommandQueue.Wait(wait.Queue.DirectX12().Fence, wait.Value).Success(); - } + CommandQueue.Signal(Fence, signalValue).Success(); + } + protected override void SubmitImpl(CommandBuffer commandBuffer) + { CommandQueue.ExecuteCommandLists(1, (ID3D12CommandList**)commandBuffer.DirectX12().CommandList.GetAddressOf()); - CommandQueue.Signal(Fence, signalValue).Success(); } protected override void WaitImpl(ulong waitValue) @@ -54,6 +53,14 @@ protected override void WaitImpl(ulong waitValue) @event.Reset(); } + protected override void WaitImpl(ReadOnlySpan submissions) + { + foreach (CommandSubmission submission in submissions) + { + CommandQueue.Wait(submission.Queue.DirectX12().Fence, submission.Value).Success(); + } + } + protected override void SetResourceName(string name) { CommandQueue.SetName(name).Success(); diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index d5eea98e..cf77c69c 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -11,19 +11,13 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : { using ZenithMarshal.Scope scope = new(); - PipelineStateStream2 pipelineStateStream = new() + ComputePipelineStateDesc computePipelineStateDesc = new() { - PRootSignature = (nint)context.RootSignature.Handle, + PRootSignature = context.RootSignature, CS = desc.ComputeShader.DirectX12().GetShaderBytecode(scope) }; - PipelineStateStreamDesc pipelineStateStreamDesc = new() - { - SizeInBytes = (uint)sizeof(PipelineStateStream2), - PPipelineStateSubobjectStream = &pipelineStateStream - }; - - context.Device.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + context.Device.CreateComputePipelineState(&computePipelineStateDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 244a1b31..ae1270bb 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Silk.NET.Core.Native; using Silk.NET.Direct3D12; using Silk.NET.DXGI; using Silk.NET.Maths; @@ -18,52 +19,6 @@ public static CommandListType DirectX12(CommandQueueType commandQueueType) }; } - public static DxHeapType DirectX12(MemoryResidency memoryResidency) - { - return memoryResidency switch - { - MemoryResidency.GpuOnly => DxHeapType.Default, - MemoryResidency.CpuReadOnly => DxHeapType.Readback, - MemoryResidency.CpuWriteOnly => DxHeapType.Upload, - _ => default - }; - } - - public static ResourceFlags DirectX12(BufferUsages bufferUsages) - { - ResourceFlags result = ResourceFlags.None; - - if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) - { - result |= ResourceFlags.AllowUnorderedAccess; - } - - if (bufferUsages.HasFlag(BufferUsages.AccelerationStructure)) - { - result |= ResourceFlags.RaytracingAccelerationStructure; - } - - return result; - } - - public static ResourceDimension DirectX12(TextureType textureType) - { - return textureType switch - { - TextureType.Texture1D or - TextureType.Texture1DArray => ResourceDimension.Texture1D, - - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ResourceDimension.Texture2D, - - TextureType.Texture3D => ResourceDimension.Texture3D, - - _ => default - }; - } - public static Format DirectX12(PixelFormat pixelFormat) { return pixelFormat switch @@ -142,6 +97,47 @@ public static Format DirectX12(PixelFormat pixelFormat) }; } + public static DxHeapType DirectX12(MemoryResidency memoryResidency) + { + return memoryResidency switch + { + MemoryResidency.GpuOnly => DxHeapType.Default, + MemoryResidency.CpuReadOnly => DxHeapType.Readback, + MemoryResidency.CpuWriteOnly => DxHeapType.Upload, + _ => default + }; + } + + public static ResourceFlags DirectX12(BufferUsages bufferUsages) + { + ResourceFlags result = ResourceFlags.None; + + if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) + { + result |= ResourceFlags.AllowUnorderedAccess; + } + + return result; + } + + public static ResourceDimension DirectX12(TextureType textureType) + { + return textureType switch + { + TextureType.Texture1D or + TextureType.Texture1DArray => ResourceDimension.Texture1D, + + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ResourceDimension.Texture2D, + + TextureType.Texture3D => ResourceDimension.Texture3D, + + _ => default + }; + } + public static SampleDesc DirectX12(SampleCount sampleCount) { return sampleCount switch @@ -271,20 +267,32 @@ public static ComparisonFunc DirectX12(CompareOp compareOp) }; } - public static PrimitiveTopologyType DirectX12(PrimitiveTopology primitiveTopology) + public static (PrimitiveTopologyType TopologyType, D3DPrimitiveTopology Topology) DirectX12(PrimitiveTopology primitiveTopology) { - return primitiveTopology switch - { - PrimitiveTopology.PointList => PrimitiveTopologyType.Point, + return + ( + primitiveTopology switch + { + PrimitiveTopology.PointList => PrimitiveTopologyType.Point, - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, + PrimitiveTopology.TriangleList or + PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, - _ => default - }; + _ => default + }, + primitiveTopology switch + { + PrimitiveTopology.PointList => D3DPrimitiveTopology.D3DPrimitiveTopologyPointlist, + PrimitiveTopology.LineList => D3DPrimitiveTopology.D3DPrimitiveTopologyLinelist, + PrimitiveTopology.LineStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyLinestrip, + PrimitiveTopology.TriangleList => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglelist, + PrimitiveTopology.TriangleStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglestrip, + _ => default + } + ); } public static Format DirectX12(ElementFormat elementFormat) @@ -494,198 +502,61 @@ public static RenderPassEndingAccessType DirectX12(StoreOp storeOp) }; } - public static BarrierSync DirectX12(PipelineStages pipelineStages) + public static (BarrierSync Sync, BarrierAccess Access, BarrierLayout Layout) DirectX12(TextureLayout textureLayout) { - BarrierSync result = BarrierSync.None; - - if (pipelineStages.HasFlag(PipelineStages.Indirect)) - { - result |= BarrierSync.ExecuteIndirect; - } - - if (pipelineStages.HasFlag(PipelineStages.VertexInput)) - { - result |= BarrierSync.IndexInput | BarrierSync.VertexShading; - } - - if (pipelineStages.HasFlag(PipelineStages.VertexShader)) - { - result |= BarrierSync.VertexShading; - } - - if (pipelineStages.HasFlag(PipelineStages.TaskShader)) - { - result |= BarrierSync.VertexShading; - } - - if (pipelineStages.HasFlag(PipelineStages.MeshShader)) - { - result |= BarrierSync.VertexShading; - } - - if (pipelineStages.HasFlag(PipelineStages.EarlyFragmentTests)) - { - result |= BarrierSync.DepthStencil; - } - - if (pipelineStages.HasFlag(PipelineStages.FragmentShader)) - { - result |= BarrierSync.PixelShading; - } - - if (pipelineStages.HasFlag(PipelineStages.LateFragmentTests)) - { - result |= BarrierSync.DepthStencil; - } - - if (pipelineStages.HasFlag(PipelineStages.ColorAttachmentOutput)) - { - result |= BarrierSync.RenderTarget; - } - - if (pipelineStages.HasFlag(PipelineStages.ComputeShader)) - { - result |= BarrierSync.ComputeShading; - } - - if (pipelineStages.HasFlag(PipelineStages.AccelerationStructureBuild)) - { - result |= BarrierSync.BuildRaytracingAccelerationStructure; - } - - if (pipelineStages.HasFlag(PipelineStages.Copy)) - { - result |= BarrierSync.Copy; - } - - if (pipelineStages.HasFlag(PipelineStages.Resolve)) - { - result |= BarrierSync.Resolve; - } - - if (pipelineStages.HasFlag(PipelineStages.AllGraphics)) - { - result |= BarrierSync.AllShading; - } - - if (pipelineStages.HasFlag(PipelineStages.AllCommands)) + return textureLayout switch { - result |= BarrierSync.All; - } - - return result; + TextureLayout.Undefined => (BarrierSync.None, BarrierAccess.NoAccess, BarrierLayout.Undefined), + TextureLayout.General => (BarrierSync.All, BarrierAccess.Common, BarrierLayout.Common), + TextureLayout.Sampled => (BarrierSync.AllShading, BarrierAccess.ShaderResource, BarrierLayout.ShaderResource), + TextureLayout.Storage => (BarrierSync.AllShading, BarrierAccess.UnorderedAccess, BarrierLayout.UnorderedAccess), + TextureLayout.ColorAttachment => (BarrierSync.RenderTarget, BarrierAccess.RenderTarget, BarrierLayout.RenderTarget), + TextureLayout.DepthStencilAttachment => (BarrierSync.DepthStencil, BarrierAccess.DepthStencilWrite, BarrierLayout.DepthStencilWrite), + TextureLayout.DepthStencilReadOnly => (BarrierSync.DepthStencil, BarrierAccess.DepthStencilRead, BarrierLayout.DepthStencilRead), + TextureLayout.CopySrc => (BarrierSync.Copy, BarrierAccess.CopySource, BarrierLayout.CopySource), + TextureLayout.CopyDst => (BarrierSync.Copy, BarrierAccess.CopyDest, BarrierLayout.CopyDest), + TextureLayout.ResolveSrc => (BarrierSync.Resolve, BarrierAccess.ResolveSource, BarrierLayout.ResolveSource), + TextureLayout.ResolveDst => (BarrierSync.Resolve, BarrierAccess.ResolveDest, BarrierLayout.ResolveDest), + TextureLayout.Present => (BarrierSync.All, BarrierAccess.Common, BarrierLayout.Present), + _ => (BarrierSync.None, BarrierAccess.NoAccess, BarrierLayout.Undefined) + }; } - public static BarrierAccess DirectX12(ResourceAccess resourceAccess) + public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages barrierStages) { - if (resourceAccess is ResourceAccess.None) - { - return BarrierAccess.NoAccess; - } - - BarrierAccess result = BarrierAccess.Common; - - if (resourceAccess.HasFlag(ResourceAccess.Vertex)) - { - result |= BarrierAccess.VertexBuffer; - } - - if (resourceAccess.HasFlag(ResourceAccess.Index)) - { - result |= BarrierAccess.IndexBuffer; - } - - if (resourceAccess.HasFlag(ResourceAccess.Indirect)) - { - result |= BarrierAccess.IndirectArgument; - } - - if (resourceAccess.HasFlag(ResourceAccess.Constant)) - { - result |= BarrierAccess.ConstantBuffer; - } - - if (resourceAccess.HasFlag(ResourceAccess.ShaderRead)) - { - result |= BarrierAccess.ShaderResource; - } - - if (resourceAccess.HasFlag(ResourceAccess.ShaderWrite)) - { - result |= BarrierAccess.UnorderedAccess; - } + BarrierSync sync = BarrierSync.None; + BarrierAccess access = BarrierAccess.Common; - if (resourceAccess.HasFlag(ResourceAccess.AccelerationStructureRead)) + if (barrierStages.HasFlag(BarrierStages.Vertex)) { - result |= BarrierAccess.RaytracingAccelerationStructureRead; + sync |= BarrierSync.VertexShading; + access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; } - if (resourceAccess.HasFlag(ResourceAccess.AccelerationStructureWrite)) + if (barrierStages.HasFlag(BarrierStages.Fragment)) { - result |= BarrierAccess.RaytracingAccelerationStructureWrite; + sync |= BarrierSync.PixelShading; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; } - if (resourceAccess.HasFlag(ResourceAccess.ColorAttachmentRead)) + if (barrierStages.HasFlag(BarrierStages.Compute)) { - result |= BarrierAccess.RenderTarget; + sync |= BarrierSync.ComputeShading; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.UnorderedAccess; } - if (resourceAccess.HasFlag(ResourceAccess.ColorAttachmentWrite)) + if (barrierStages.HasFlag(BarrierStages.Copy)) { - result |= BarrierAccess.RenderTarget; + sync |= BarrierSync.Copy; + access |= BarrierAccess.CopySource | BarrierAccess.CopyDest; } - if (resourceAccess.HasFlag(ResourceAccess.DepthStencilAttachmentRead)) + if (barrierStages.HasFlag(BarrierStages.All)) { - result |= BarrierAccess.DepthStencilRead; + sync |= BarrierSync.All; } - if (resourceAccess.HasFlag(ResourceAccess.DepthStencilAttachmentWrite)) - { - result |= BarrierAccess.DepthStencilWrite; - } - - if (resourceAccess.HasFlag(ResourceAccess.CopyRead)) - { - result |= BarrierAccess.CopySource; - } - - if (resourceAccess.HasFlag(ResourceAccess.CopyWrite)) - { - result |= BarrierAccess.CopyDest; - } - - if (resourceAccess.HasFlag(ResourceAccess.ResolveRead)) - { - result |= BarrierAccess.ResolveSource; - } - - if (resourceAccess.HasFlag(ResourceAccess.ResolveWrite)) - { - result |= BarrierAccess.ResolveDest; - } - - return result; - } - - public static BarrierLayout DirectX12(TextureLayout textureLayout) - { - return textureLayout switch - { - TextureLayout.Undefined => BarrierLayout.Undefined, - TextureLayout.General => BarrierLayout.Common, - TextureLayout.Sampled => BarrierLayout.ShaderResource, - TextureLayout.Storage => BarrierLayout.UnorderedAccess, - TextureLayout.ColorAttachment => BarrierLayout.RenderTarget, - TextureLayout.DepthStencilAttachment => BarrierLayout.DepthStencilWrite, - TextureLayout.DepthStencilReadOnly => BarrierLayout.DepthStencilRead, - TextureLayout.CopySrc => BarrierLayout.CopySource, - TextureLayout.CopyDst => BarrierLayout.CopyDest, - TextureLayout.ResolveSrc => BarrierLayout.ResolveSource, - TextureLayout.ResolveDst => BarrierLayout.ResolveDest, - TextureLayout.Present => BarrierLayout.Present, - _ => default - }; + return (sync, access); } public static RaytracingGeometryType DirectX12(RayTracingGeometryType rayTracingGeometryType) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 787eb6c3..8813ac7c 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -180,7 +180,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) protected override Buffer CreateBufferImpl(BufferDesc desc) { - return new DXBuffer(this, desc, default); + return new DXBuffer(this, desc); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) @@ -190,7 +190,7 @@ protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc) { - return new DXTexture(this, desc, default); + return new DXTexture(this, desc); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 8c944b80..2fc6876d 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -15,7 +15,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) PipelineStateStream2 pipelineStateStream = new() { PRootSignature = (nint)context.RootSignature.Handle, - PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology), + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).TopologyType, VS = desc.VertexShader.DirectX12().GetShaderBytecode(scope), PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), SampleMask = uint.MaxValue diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index f77868bb..ffd62a88 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -15,7 +15,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc PipelineStateStream2 pipelineStateStream = new() { PRootSignature = (nint)context.RootSignature.Handle, - PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology), + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).TopologyType, PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), AS = (desc.TaskShader?.DirectX12().GetShaderBytecode(scope)) ?? default, MS = desc.MeshShader.DirectX12().GetShaderBytecode(scope), diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index a7c65161..f11a665f 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -17,7 +17,11 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context context.Device.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); - Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, Residency = MemoryResidency.CpuReadOnly }, default); + Buffer = new(context, new() + { + SizeInBytes = sizeof(ulong) * desc.Count, + Residency = MemoryResidency.CpuReadOnly + }); } public DXBuffer Buffer { get; } diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index ced340a4..ae3b7bfb 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -11,29 +11,38 @@ internal unsafe class DXTexture : Texture public ComPtr Resource; - public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr? resource) : base(context, desc) + public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, desc) { - if (resource is null) + ResourceDesc1 resourceDesc = ResourceDesc(desc); + + HeapProperties heapProperties = new() { - ResourceDesc1 resourceDesc = ResourceDesc(desc); - - HeapProperties heapProperties = new(DxHeapType.Default); - - context.Device.CreateCommittedResource3(&heapProperties, - HeapFlags.None, - &resourceDesc, - BarrierLayout.Undefined, - default(ClearValue*), - default(ID3D12ProtectedResourceSession*), - 0, - default(Format*), - SilkMarshal.GuidPtrOf(), - (void**)Resource.GetAddressOf()).Success(); - } - else + Type = DxHeapType.Default + }; + + context.Device.CreateCommittedResource3(&heapProperties, + HeapFlags.None, + &resourceDesc, + BarrierLayout.Undefined, + default(ClearValue*), + default(ID3D12ProtectedResourceSession*), + 0, + default(Format*), + SilkMarshal.GuidPtrOf(), + (void**)Resource.GetAddressOf()).Success(); + + View = new(context, new() { - Resource = resource.Value; - } + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) + }); + } + + public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr resource) : base(context, desc) + { + Resource = resource; View = new(context, new() { diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 4770a3ca..f9c30e52 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -9,16 +9,29 @@ internal unsafe class DXTopLevelAccelerationStructure : TopLevelAccelerationStru public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { - InstanceBuffer = new(context, new() { SizeInBytes = (uint)(sizeof(RaytracingInstanceDesc) * desc.Instances.Length), Residency = MemoryResidency.CpuWriteOnly }, default); + InstanceBuffer = new(context, new() + { + SizeInBytes = (uint)(sizeof(RaytracingInstanceDesc) * desc.Instances.Length), + Residency = MemoryResidency.CpuWriteOnly + }); FillInputs(desc, out BuildRaytracingAccelerationStructureInputs inputs); RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - AccelerationStructureBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, Usages = BufferUsages.AccelerationStructure, Residency = MemoryResidency.GpuOnly }, default); + AccelerationStructureBuffer = new(context, new() + { + SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, + Residency = MemoryResidency.GpuOnly + }, ResourceFlags.RaytracingAccelerationStructure); - ScratchBuffer = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, Usages = BufferUsages.StorageReadWrite, Residency = MemoryResidency.GpuOnly }, default); + ScratchBuffer = new(context, new() + { + SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly + }); BuildRaytracingAccelerationStructureDesc buildDesc = new() { @@ -29,6 +42,23 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + GlobalBarrier barrier = new() + { + SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, + SyncAfter = BarrierSync.AllShading, + AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, + AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead + }; + + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; + + commandBuffer.CommandList.Barrier(1, &barrierGroup); + ShaderResourceViewDesc viewDesc = new() { ViewDimension = SrvDimension.RaytracingAccelerationStructure, @@ -64,6 +94,23 @@ public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureD }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + + GlobalBarrier barrier = new() + { + SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, + SyncAfter = BarrierSync.AllShading, + AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, + AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead + }; + + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; + + commandBuffer.CommandList.Barrier(1, &barrierGroup); } public override nint GetNativeObject(NativeObjectType type) diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 25539438..2e2db785 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,5 +1,4 @@ global using DxBlendOp = Silk.NET.Direct3D12.BlendOp; -global using DxBufferBarrier = Silk.NET.Direct3D12.BufferBarrier; global using DxCullMode = Silk.NET.Direct3D12.CullMode; global using DxFillMode = Silk.NET.Direct3D12.FillMode; global using DxHeapDesc = Silk.NET.Direct3D12.HeapDesc; From 768d73f7a119aec1ec48c44d5ec2c39595c408af Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 17:58:24 +0800 Subject: [PATCH 192/466] Update back buffer index handling in DXSwapChain Moved back buffer index update from AcquireImpl (now removed) to PresentImpl and ResizeImpl. This ensures the index is refreshed after presenting or resizing the swap chain, improving correctness and clarity. --- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 794e4b32..f9a73a74 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -26,19 +26,13 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - protected override void AcquireImpl() - { - if (SwapChain.Handle is not null) - { - index = SwapChain.GetCurrentBackBufferIndex(); - } - } - protected override void PresentImpl() { if (SwapChain.Handle is not null) { SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); + + index = SwapChain.GetCurrentBackBufferIndex(); } } @@ -142,6 +136,8 @@ private void CreateTextures() textures[i] = new(Context, desc, resource); } + + index = SwapChain.GetCurrentBackBufferIndex(); } else { From 1ad1ecda99a57e60724b3fe409c0fd67d4e84c5b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 18:02:10 +0800 Subject: [PATCH 193/466] Ensure success checks for GetBuffer/OpenSharedHandle calls Added .Success() checks to SwapChain.GetBuffer and Context.Device.OpenSharedHandle calls. Refactored OpenSharedHandle usage to match GetBuffer pattern and explicitly declared resource variable for clarity and consistency. --- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index f9a73a74..c73c76bc 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -132,7 +132,7 @@ private void CreateTextures() for (int i = 0; i < textures.Length; i++) { ComPtr resource = new(); - SwapChain.GetBuffer((uint)i, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()); + SwapChain.GetBuffer((uint)i, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); textures[i] = new(Context, desc, resource); } @@ -141,7 +141,8 @@ private void CreateTextures() } else { - Context.Device.OpenSharedHandle((void*)Desc.Surface.NativeHandles[0], out ComPtr resource).Success(); + ComPtr resource = new(); + Context.Device.OpenSharedHandle((void*)Desc.Surface.NativeHandles[0], SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); textures[0] = new(Context, desc, resource); } From 5ed0d4d49672cf438248661f80e1af48075f9d2b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 9 Jun 2026 23:46:55 +0800 Subject: [PATCH 194/466] Refactor: use multi-line object initializers for clarity Refactored object initializations from single-line to multi-line format with explicit property assignments across several DirectX-related structures. This improves code readability and consistency, especially for objects with multiple or nested properties. --- .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 10 +++++-- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 28 +++++++++++++---- sources/Zenith.NET.DirectX12/DXTexture.cs | 30 +++++++++++++++---- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index ee80e461..b10c5aea 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -8,9 +8,15 @@ internal readonly struct DXDescriptorToken(DXDescriptorHeap heap, uint slot) : I public readonly ResourceHandle ResourceHandle = new(slot, 0); - public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = heap.CpuStartHandle.Ptr + (slot * heap.IncrementSize) }; + public readonly CpuDescriptorHandle CpuHandle = new() + { + Ptr = heap.CpuStartHandle.Ptr + (slot * heap.IncrementSize) + }; - public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = heap.GpuStartHandle.Ptr + (slot * heap.IncrementSize) }; + public readonly GpuDescriptorHandle GpuHandle = new() + { + Ptr = heap.GpuStartHandle.Ptr + (slot * heap.IncrementSize) + }; public void Dispose() { diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 8813ac7c..77d8d74a 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -72,7 +72,10 @@ protected override void Initialize(bool useValidationLayer, throw new NotSupportedException("Direct3D 12 Feature Level 12_0 is not supported on the selected adapter."); } - FeatureDataShaderModel shaderModel = new() { HighestShaderModel = D3DShaderModel.ShaderModel66 }; + FeatureDataShaderModel shaderModel = new() + { + HighestShaderModel = D3DShaderModel.ShaderModel66 + }; Device.CheckFeatureSupport(Feature.ShaderModel, &shaderModel, (uint)sizeof(FeatureDataShaderModel)).Success(); if (shaderModel.HighestShaderModel < D3DShaderModel.ShaderModel66) { @@ -97,7 +100,10 @@ protected override void Initialize(bool useValidationLayer, { ParameterType = RootParameterType.TypeCbv, ShaderVisibility = ShaderVisibility.All, - Descriptor = new() { Flags = RootDescriptorFlags.DataVolatile } + Descriptor = new() + { + Flags = RootDescriptorFlags.DataVolatile + } }; RootSignatureDesc2 rootSignatureDesc = new() @@ -118,8 +124,17 @@ protected override void Initialize(bool useValidationLayer, D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, rootSignatureBlob.GetAddressOf(), rootSignatureError.GetAddressOf()).Success(); Device.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); - IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; - CommandSignatureDesc commandSignatureDesc = new() { ByteStride = (uint)sizeof(IndirectDrawArgs), NumArgumentDescs = 1, PArgumentDescs = &indirectArgumentDesc }; + IndirectArgumentDesc indirectArgumentDesc = new() + { + Type = IndirectArgumentType.Draw + }; + + CommandSignatureDesc commandSignatureDesc = new() + { + ByteStride = (uint)sizeof(IndirectDrawArgs), + NumArgumentDescs = 1, + PArgumentDescs = &indirectArgumentDesc + }; Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DrawIndexed; @@ -134,7 +149,10 @@ protected override void Initialize(bool useValidationLayer, commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); - CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; + CommandQueueDesc commandQueueDesc = new() + { + Type = CommandListType.Direct + }; Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Compute; diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index ae3b7bfb..23265b30 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -72,13 +72,19 @@ public CpuDescriptorHandle GetRtvHandle(TextureSubresource subresource) { rtvTokens[subresource] = token = Context.RtvHeap.Allocate(); - RenderTargetViewDesc viewDesc = new(DXFormats.DirectX12(Desc.Format)); + RenderTargetViewDesc viewDesc = new() + { + Format = DXFormats.DirectX12(Desc.Format) + }; switch (Desc.Type) { case TextureType.Texture1D: viewDesc.ViewDimension = RtvDimension.Texture1D; - viewDesc.Texture1D = new(subresource.MipLevel); + viewDesc.Texture1D = new() + { + MipSlice = subresource.MipLevel + }; break; case TextureType.Texture1DArray: @@ -95,7 +101,10 @@ public CpuDescriptorHandle GetRtvHandle(TextureSubresource subresource) if (Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = RtvDimension.Texture2D; - viewDesc.Texture2D = new(subresource.MipLevel); + viewDesc.Texture2D = new() + { + MipSlice = subresource.MipLevel + }; } else { @@ -150,13 +159,19 @@ public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) { dsvTokens[subresource] = token = Context.DsvHeap.Allocate(); - DepthStencilViewDesc viewDesc = new(DXFormats.DirectX12(Desc.Format)); + DepthStencilViewDesc viewDesc = new() + { + Format = DXFormats.DirectX12(Desc.Format) + }; switch (Desc.Type) { case TextureType.Texture1D: viewDesc.ViewDimension = DsvDimension.Texture1D; - viewDesc.Texture1D = new(subresource.MipLevel); + viewDesc.Texture1D = new() + { + MipSlice = subresource.MipLevel + }; break; case TextureType.Texture1DArray: @@ -173,7 +188,10 @@ public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) if (Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = DsvDimension.Texture2D; - viewDesc.Texture2D = new(subresource.MipLevel); + viewDesc.Texture2D = new() + { + MipSlice = subresource.MipLevel + }; } else { From f490ed8eb18bb6b05e23957ccd5aa2c138fc0768 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 03:09:32 +0800 Subject: [PATCH 195/466] Refactor: use multi-line object initializers for clarity Refactored all object initializations to use multi-line style with each property on its own line and braces on separate lines. This improves readability and consistency for complex initializations across the codebase. No functional changes were made. --- sources/Experiments/CornellBox/App.cs | 14 +++- .../CornellBox/Helpers/CornellBoxGeometry.cs | 79 ++++++++++++++++--- .../Renderers/PathTracingRenderer.cs | 24 +++++- .../Renderers/RasterizationRenderer.cs | 37 +++++++-- .../ImGuiRenderer.cs | 65 ++++++++++++--- .../Extensions.cs | 14 +++- .../Zenith.NET.Views.Avalonia/Surface.cs | 13 ++- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 13 ++- sources/Zenith.NET/CommandBuffer.cs | 28 ++++++- 9 files changed, 244 insertions(+), 43 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 10c57492..c57d5b0c 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -72,8 +72,18 @@ static App() 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 }); - imGui = new(input, new() { ColorFormats = [PixelFormat.B8G8R8A8UNorm], SampleCount = SampleCount.Count1 }); + swapChain = Context.CreateSwapChain(new() + { + Surface = surface, + Format = PixelFormat.B8G8R8A8UNorm + }); + + imGui = new(input, new() + { + ColorFormats = [PixelFormat.B8G8R8A8UNorm], + SampleCount = SampleCount.Count1 + }); + camera = new(input, Matrix4x4.CreateTranslation(278.0f, 273.0f, -800.0f)) { Speed = 240.0f, diff --git a/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs b/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs index 3da4d69e..431304ae 100644 --- a/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs +++ b/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs @@ -82,12 +82,48 @@ public static void Create(out Vertex[] vertices, out uint[] indices, out Materia indices = [.. indicesList]; materials = [ - new() { Albedo = new(0.63f, 0.06f, 0.06f), Emission = 0.00f, Metallic = 0.0f, Roughness = 0.90f }, - new() { Albedo = new(0.14f, 0.45f, 0.09f), Emission = 0.00f, Metallic = 0.0f, Roughness = 0.90f }, - new() { Albedo = new(0.73f, 0.71f, 0.68f), Emission = 0.00f, Metallic = 0.0f, Roughness = 0.90f }, - new() { Albedo = new(1.00f, 0.85f, 0.60f), Emission = 25.0f, Metallic = 0.0f, Roughness = 0.50f }, - new() { Albedo = new(0.73f, 0.71f, 0.68f), Emission = 0.00f, Metallic = 0.0f, Roughness = 0.30f }, - new() { Albedo = new(0.95f, 0.93f, 0.88f), Emission = 0.00f, Metallic = 1.0f, Roughness = 0.05f } + new() + { + Albedo = new(0.63f, 0.06f, 0.06f), + Emission = 0.00f, + Metallic = 0.0f, + Roughness = 0.90f + }, + new() + { + Albedo = new(0.14f, 0.45f, 0.09f), + Emission = 0.00f, + Metallic = 0.0f, + Roughness = 0.90f + }, + new() + { + Albedo = new(0.73f, 0.71f, 0.68f), + Emission = 0.00f, + Metallic = 0.0f, + Roughness = 0.90f + }, + new() + { + Albedo = new(1.00f, 0.85f, 0.60f), + Emission = 25.0f, + Metallic = 0.0f, + Roughness = 0.50f + }, + new() + { + Albedo = new(0.73f, 0.71f, 0.68f), + Emission = 0.00f, + Metallic = 0.0f, + Roughness = 0.30f + }, + new() + { + Albedo = new(0.95f, 0.93f, 0.88f), + Emission = 0.00f, + Metallic = 1.0f, + Roughness = 0.05f + } ]; } @@ -103,10 +139,33 @@ private static void AddQuad(List vertices, uint startIndex = (uint)vertices.Count; - vertices.Add(new() { Position = v0, Normal = normal, MaterialId = materialId }); - vertices.Add(new() { Position = v1, Normal = normal, MaterialId = materialId }); - vertices.Add(new() { Position = v2, Normal = normal, MaterialId = materialId }); - vertices.Add(new() { Position = v3, Normal = normal, MaterialId = materialId }); + vertices.Add(new() + { + Position = v0, + Normal = normal, + MaterialId = materialId + }); + + vertices.Add(new() + { + Position = v1, + Normal = normal, + MaterialId = materialId + }); + + vertices.Add(new() + { + Position = v2, + Normal = normal, + MaterialId = materialId + }); + + vertices.Add(new() + { + Position = v3, + Normal = normal, + MaterialId = materialId + }); indices.Add(startIndex); indices.Add(startIndex + 1); diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 60f68e9c..b13f840e 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -38,7 +38,11 @@ public PathTracingRenderer() fixed (Vertex* pointer = vertices) { - vertexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) }); + vertexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) + }); } indexBuffer = App.Context.CreateBuffer(new() @@ -51,7 +55,11 @@ public PathTracingRenderer() fixed (uint* pointer = indices) { - indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); + indexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(uint) * indices.Length) + }); } constantBuffer = App.Context.CreateBuffer(new() @@ -123,7 +131,11 @@ public PathTracingRenderer() fixed (Material* pointer = materials) { - materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); + materialBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Material) * materials.Length) + }); } } @@ -161,7 +173,11 @@ public override void Update(CameraHandler camera) OutputTexture = Color.StorageHandle }; - constantBuffer.Upload(0, new() { Pointer = (nint)(¶meters), SizeInBytes = (uint)sizeof(PathTracingConstants) }); + constantBuffer.Upload(0, new() + { + Pointer = (nint)(¶meters), + SizeInBytes = (uint)sizeof(PathTracingConstants) + }); } public override void Render(CommandBuffer commandBuffer) diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 213889db..a3bf0dfc 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -28,14 +28,22 @@ public RasterizationRenderer() fixed (Vertex* pointer = vertices) { - vertexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) }); + vertexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) + }); } indexBuffer = App.Context.CreateBuffer(BufferDesc.Index((uint)(sizeof(uint) * indices.Length))); fixed (uint* pointer = indices) { - indexBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(uint) * indices.Length) }); + indexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(uint) * indices.Length) + }); } constantBuffer = App.Context.CreateBuffer(new() @@ -46,8 +54,17 @@ public RasterizationRenderer() }); InputLayout inputLayout = new(); - inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); - inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Normal }); + inputLayout.Add(new() + { + Format = ElementFormat.Float4, + Semantic = ElementSemantic.Position + }); + + inputLayout.Add(new() + { + Format = ElementFormat.Float4, + Semantic = ElementSemantic.Normal + }); using Shader vertexShader = App.Context.CreateShader(new() { @@ -79,7 +96,11 @@ public RasterizationRenderer() fixed (Material* pointer = materials) { - materialBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Material) * materials.Length) }); + materialBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Material) * materials.Length) + }); } } @@ -96,7 +117,11 @@ public override void Update(CameraHandler camera) Materials = materialBuffer.StorageReadOnlyHandle }; - constantBuffer.Upload(0, new() { Pointer = (nint)(¶meters), SizeInBytes = (uint)sizeof(RasterizationConstants) }); + constantBuffer.Upload(0, new() + { + Pointer = (nint)(¶meters), + SizeInBytes = (uint)sizeof(RasterizationConstants) + }); } public override void Render(CommandBuffer commandBuffer) diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 5529bdd4..32ba5546 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -94,9 +94,23 @@ public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormat }); InputLayout inputLayout = new(); - inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.Position }); - inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.TexCoord }); - inputLayout.Add(new() { Format = ElementFormat.UByte4UNorm, Semantic = ElementSemantic.Color }); + inputLayout.Add(new() + { + Format = ElementFormat.Float2, + Semantic = ElementSemantic.Position + }); + + inputLayout.Add(new() + { + Format = ElementFormat.Float2, + Semantic = ElementSemantic.TexCoord + }); + + inputLayout.Add(new() + { + Format = ElementFormat.UByte4UNorm, + Semantic = ElementSemantic.Color + }); graphicsPipeline = context.CreateGraphicsPipeline(new() { @@ -173,7 +187,12 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, 1, SampleCount.Count1)); - Extent3D extent = new() { Width = (uint)textureData.Width, Height = (uint)textureData.Height, Depth = 1 }; + Extent3D extent = new() + { + Width = (uint)textureData.Width, + Height = (uint)textureData.Height, + Depth = 1 + }; if (textureData.Format is ImTextureFormat.Rgba32) { @@ -215,8 +234,19 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, { ImTextureRect rect = textureData.Updates[j]; - Offset3D offset = new() { X = rect.X, Y = rect.Y, Z = 0 }; - Extent3D extent = new() { Width = rect.W, Height = rect.H, Depth = 1 }; + Offset3D offset = new() + { + X = rect.X, + Y = rect.Y, + Z = 0 + }; + + Extent3D extent = new() + { + Width = rect.W, + Height = rect.H, + Depth = 1 + }; using ZenithMarshal.Scope scope = new(); @@ -321,8 +351,17 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, { ImDrawListPtr drawListPtr = drawData.CmdLists[i]; - vertexBuffer.Upload((uint)(sizeof(ImDrawVert) * vertexOffset), new() { Pointer = (nint)drawListPtr.VtxBuffer.Data, SizeInBytes = (uint)(sizeof(ImDrawVert) * drawListPtr.VtxBuffer.Size) }); - indexBuffer.Upload((uint)(sizeof(ushort) * indexOffset), new() { Pointer = (nint)drawListPtr.IdxBuffer.Data, SizeInBytes = (uint)(sizeof(ushort) * drawListPtr.IdxBuffer.Size) }); + vertexBuffer.Upload((uint)(sizeof(ImDrawVert) * vertexOffset), new() + { + Pointer = (nint)drawListPtr.VtxBuffer.Data, + SizeInBytes = (uint)(sizeof(ImDrawVert) * drawListPtr.VtxBuffer.Size) + }); + + indexBuffer.Upload((uint)(sizeof(ushort) * indexOffset), new() + { + Pointer = (nint)drawListPtr.IdxBuffer.Data, + SizeInBytes = (uint)(sizeof(ushort) * drawListPtr.IdxBuffer.Size) + }); vertexOffset += drawListPtr.VtxBuffer.Size; indexOffset += drawListPtr.IdxBuffer.Size; @@ -342,14 +381,18 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment, constants[i] = new() { Projection = projection, - Sampler = sampler.Handle, - Texture = resourceHandleBindings.Values[i] + Texture = resourceHandleBindings.Values[i], + Sampler = sampler.Handle }; } fixed (Constants* pointer = constants) { - constantBuffer.Upload(0, new() { Pointer = (nint)pointer, SizeInBytes = (uint)(sizeof(Constants) * resourceHandleBindings.Count) }); + constantBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Constants) * resourceHandleBindings.Count) + }); } ArrayPool.Shared.Return(constants); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index 2375914e..af8317dd 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -29,7 +29,12 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) { fixed (Rgba32* pPixels = pixels) { - Extent3D extent = new() { Width = (uint)image.Width, Height = (uint)image.Height, Depth = 1 }; + Extent3D extent = new() + { + Width = (uint)image.Width, + Height = (uint)image.Height, + Depth = 1 + }; TextureData data = new() { @@ -53,7 +58,12 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) fixed (Rgba32* pPixels = pixels) { - Extent3D extent = new() { Width = mipWidth, Height = mipHeight, Depth = 1 }; + Extent3D extent = new() + { + Width = mipWidth, + Height = mipHeight, + Depth = 1 + }; TextureData data = new() { diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index a50fd43d..18533bd8 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -18,13 +18,22 @@ public void Present() { using ILockedFramebuffer lockedFramebuffer = Bitmap.Lock(); - Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() + Extent3D extent = new() + { + Width = Width, + Height = Height, + Depth = 1 + }; + + TextureData data = new() { Pointer = lockedFramebuffer.Address, SizeInBytes = (uint)(lockedFramebuffer.RowBytes * Height), RowStrideInBytes = (uint)lockedFramebuffer.RowBytes, SliceStrideInBytes = (uint)(lockedFramebuffer.RowBytes * Height) - }); + }; + + Target.Download(default, default, extent, data); } protected override void Destroy() diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index a5c6037d..c3eaaae8 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -66,13 +66,22 @@ public void Present() { fixed (byte* pPixels = pixels) { - Target.Download(default, default, new() { Width = Width, Height = Height, Depth = 1 }, new() + Extent3D extent = new() + { + Width = Width, + Height = Height, + Depth = 1 + }; + + TextureData data = new() { Pointer = (nint)pPixels, SizeInBytes = (uint)pixels.Length, RowStrideInBytes = Width * 4, SliceStrideInBytes = (uint)pixels.Length - }); + }; + + Target.Download(default, default, extent, data); } if (ZenithViewHelper.Format is PixelFormat.R8G8B8A8UNorm) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index a354a6c6..aeccb89e 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -223,8 +223,18 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept out uint height, out _); - scissors[i] = new() { Width = width, Height = height }; - viewports[i] = new() { Width = width, Height = height, MaxDepth = 1.0f }; + scissors[i] = new() + { + Width = width, + Height = height + }; + + viewports[i] = new() + { + Width = width, + Height = height, + MaxDepth = 1.0f + }; } } else if (depthStencilAttachment.HasValue) @@ -239,8 +249,18 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept out uint height, out _); - scissors[0] = new() { Width = width, Height = height }; - viewports[0] = new() { Width = width, Height = height, MaxDepth = 1.0f }; + scissors[0] = new() + { + Width = width, + Height = height + }; + + viewports[0] = new() + { + Width = width, + Height = height, + MaxDepth = 1.0f + }; } SetScissorsImpl(scissors); From 271bf8a7b63ab5aed47e934b4fc3261d1b70349d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 03:12:43 +0800 Subject: [PATCH 196/466] Refactor: use object initializer shorthand syntax Refactored object initializations across the codebase to use C#'s object initializer shorthand (new() { ... }) for improved readability and conciseness. No functional changes were made. --- .../DXBottomLevelAccelerationStructure.cs | 2 +- sources/Zenith.NET.DirectX12/DXBuffer.cs | 15 ++------ .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 35 ++++--------------- .../Zenith.NET.DirectX12/DXDescriptorToken.cs | 10 ++---- sources/Zenith.NET.DirectX12/DXFormats.cs | 30 ++++------------ .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 20 +++-------- sources/Zenith.NET.DirectX12/DXHeap.cs | 5 +-- sources/Zenith.NET.DirectX12/DXTexture.cs | 35 ++++--------------- sources/Zenith.NET.DirectX12/DXTextureView.cs | 15 ++------ .../DXTopLevelAccelerationStructure.cs | 5 +-- 10 files changed, 35 insertions(+), 137 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 929e3c45..383aed54 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -145,7 +145,7 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct VertexFormat = DXFormats.DirectX12(geometry.TriangleGeometry.VertexFormat), IndexCount = geometry.TriangleGeometry.IndexCount, VertexCount = geometry.TriangleGeometry.VertexCount, - IndexBuffer = geometry.TriangleGeometry.IndexBuffer is not null ? geometry.TriangleGeometry.IndexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.IndexOffsetInBytes : 0, + IndexBuffer = geometry.TriangleGeometry.IndexBuffer is not null ? geometry.TriangleGeometry.IndexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.IndexOffsetInBytes : 0ul, VertexBuffer = new() { StartAddress = geometry.TriangleGeometry.VertexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.VertexOffsetInBytes, diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index ac8501f0..2da814b4 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -14,10 +14,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc) : base(context, desc { ResourceDesc1 resourceDesc = ResourceDesc(desc); - HeapProperties heapProperties = new() - { - Type = DXFormats.DirectX12(desc.Residency) - }; + HeapProperties heapProperties = new() { Type = DXFormats.DirectX12(desc.Residency) }; context.Device.CreateCommittedResource3(&heapProperties, HeapFlags.None, @@ -59,10 +56,7 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ResourceFlags flags) ResourceDesc1 resourceDesc = ResourceDesc(desc); resourceDesc.Flags |= flags; - HeapProperties heapProperties = new() - { - Type = DXFormats.DirectX12(desc.Residency) - }; + HeapProperties heapProperties = new() { Type = DXFormats.DirectX12(desc.Residency) }; context.Device.CreateCommittedResource3(&heapProperties, HeapFlags.None, @@ -132,10 +126,7 @@ public static ResourceDesc1 ResourceDesc(BufferDesc desc) Height = 1, DepthOrArraySize = 1, MipLevels = 1, - SampleDesc = new() - { - Count = 1 - }, + SampleDesc = new() { Count = 1 }, Layout = DxTextureLayout.LayoutRowMajor, Flags = DXFormats.DirectX12(desc.Usages) }; diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 9179cd89..96a0c61c 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -237,10 +237,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA DXTexture texture = attachment.Texture.DirectX12(); - ClearValue clearValue = new() - { - Format = DXFormats.DirectX12(texture.Desc.Format) - }; + ClearValue clearValue = new() { Format = DXFormats.DirectX12(texture.Desc.Format) }; *(Vector4*)clearValue.Anonymous.Color = attachment.ClearColor; pRenderTargets[i] = new() @@ -249,15 +246,9 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA BeginningAccess = new() { Type = DXFormats.DirectX12(attachment.LoadOp), - Clear = new() - { - ClearValue = clearValue - } + Clear = new() { ClearValue = clearValue } }, - EndingAccess = new() - { - Type = DXFormats.DirectX12(attachment.StoreOp) - } + EndingAccess = new() { Type = DXFormats.DirectX12(attachment.StoreOp) } }; } @@ -284,27 +275,15 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA DepthBeginningAccess = new() { Type = DXFormats.DirectX12(attachment.DepthLoadOp), - Clear = new() - { - ClearValue = clearValue - } - }, - DepthEndingAccess = new() - { - Type = ZenithHelper.HasDepth(texture.Desc.Format) ? DXFormats.DirectX12(attachment.DepthStoreOp) : RenderPassEndingAccessType.NoAccess + 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 - } + Clear = new() { ClearValue = clearValue } }, - StencilEndingAccess = new() - { - Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilStoreOp) : RenderPassEndingAccessType.NoAccess - } + StencilEndingAccess = new() { Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilStoreOp) : RenderPassEndingAccessType.NoAccess } }; } diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs index b10c5aea..ee80e461 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -8,15 +8,9 @@ internal readonly struct DXDescriptorToken(DXDescriptorHeap heap, uint slot) : I public readonly ResourceHandle ResourceHandle = new(slot, 0); - public readonly CpuDescriptorHandle CpuHandle = new() - { - Ptr = heap.CpuStartHandle.Ptr + (slot * heap.IncrementSize) - }; + public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = heap.CpuStartHandle.Ptr + (slot * heap.IncrementSize) }; - public readonly GpuDescriptorHandle GpuHandle = new() - { - Ptr = heap.GpuStartHandle.Ptr + (slot * heap.IncrementSize) - }; + public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = heap.GpuStartHandle.Ptr + (slot * heap.IncrementSize) }; public void Dispose() { diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index ae1270bb..4c5cf227 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -142,30 +142,12 @@ public static SampleDesc DirectX12(SampleCount sampleCount) { return sampleCount switch { - SampleCount.Count1 => new() - { - Count = 1 - }, - SampleCount.Count2 => new() - { - Count = 2 - }, - SampleCount.Count4 => new() - { - Count = 4 - }, - SampleCount.Count8 => new() - { - Count = 8 - }, - SampleCount.Count16 => new() - { - Count = 16 - }, - SampleCount.Count32 => new() - { - Count = 32 - }, + SampleCount.Count1 => new() { Count = 1 }, + SampleCount.Count2 => new() { Count = 2 }, + SampleCount.Count4 => new() { Count = 4 }, + SampleCount.Count8 => new() { Count = 8 }, + SampleCount.Count16 => new() { Count = 16 }, + SampleCount.Count32 => new() { Count = 32 }, _ => default }; } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 77d8d74a..808e4ff4 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -72,10 +72,7 @@ protected override void Initialize(bool useValidationLayer, throw new NotSupportedException("Direct3D 12 Feature Level 12_0 is not supported on the selected adapter."); } - FeatureDataShaderModel shaderModel = new() - { - HighestShaderModel = D3DShaderModel.ShaderModel66 - }; + FeatureDataShaderModel shaderModel = new() { HighestShaderModel = D3DShaderModel.ShaderModel66 }; Device.CheckFeatureSupport(Feature.ShaderModel, &shaderModel, (uint)sizeof(FeatureDataShaderModel)).Success(); if (shaderModel.HighestShaderModel < D3DShaderModel.ShaderModel66) { @@ -100,10 +97,7 @@ protected override void Initialize(bool useValidationLayer, { ParameterType = RootParameterType.TypeCbv, ShaderVisibility = ShaderVisibility.All, - Descriptor = new() - { - Flags = RootDescriptorFlags.DataVolatile - } + Descriptor = new() { Flags = RootDescriptorFlags.DataVolatile } }; RootSignatureDesc2 rootSignatureDesc = new() @@ -124,10 +118,7 @@ protected override void Initialize(bool useValidationLayer, D3D12.SerializeVersionedRootSignature(&versionedRootSignatureDesc, rootSignatureBlob.GetAddressOf(), rootSignatureError.GetAddressOf()).Success(); Device.CreateRootSignature(0, rootSignatureBlob.GetBufferPointer(), rootSignatureBlob.GetBufferSize(), SilkMarshal.GuidPtrOf(), (void**)RootSignature.GetAddressOf()).Success(); - IndirectArgumentDesc indirectArgumentDesc = new() - { - Type = IndirectArgumentType.Draw - }; + IndirectArgumentDesc indirectArgumentDesc = new() { Type = IndirectArgumentType.Draw }; CommandSignatureDesc commandSignatureDesc = new() { @@ -149,10 +140,7 @@ protected override void Initialize(bool useValidationLayer, commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); - CommandQueueDesc commandQueueDesc = new() - { - Type = CommandListType.Direct - }; + CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); commandQueueDesc.Type = CommandListType.Compute; diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index adbc7e1d..eeb420dc 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -13,10 +13,7 @@ public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) DxHeapDesc heapDesc = new() { SizeInBytes = ZenithHelper.Align(desc.SizeInBytes, DXGraphicsContext.DefaultHeapAlignment), - Properties = new() - { - Type = DXFormats.DirectX12(desc.Residency) - }, + Properties = new() { Type = DXFormats.DirectX12(desc.Residency) }, Alignment = DXGraphicsContext.DefaultHeapAlignment }; diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 23265b30..00fe3f05 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -15,10 +15,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, de { ResourceDesc1 resourceDesc = ResourceDesc(desc); - HeapProperties heapProperties = new() - { - Type = DxHeapType.Default - }; + HeapProperties heapProperties = new() { Type = DxHeapType.Default }; context.Device.CreateCommittedResource3(&heapProperties, HeapFlags.None, @@ -72,19 +69,13 @@ public CpuDescriptorHandle GetRtvHandle(TextureSubresource subresource) { rtvTokens[subresource] = token = Context.RtvHeap.Allocate(); - RenderTargetViewDesc viewDesc = new() - { - Format = DXFormats.DirectX12(Desc.Format) - }; + RenderTargetViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; switch (Desc.Type) { case TextureType.Texture1D: viewDesc.ViewDimension = RtvDimension.Texture1D; - viewDesc.Texture1D = new() - { - MipSlice = subresource.MipLevel - }; + viewDesc.Texture1D = new() { MipSlice = subresource.MipLevel }; break; case TextureType.Texture1DArray: @@ -101,10 +92,7 @@ public CpuDescriptorHandle GetRtvHandle(TextureSubresource subresource) if (Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = RtvDimension.Texture2D; - viewDesc.Texture2D = new() - { - MipSlice = subresource.MipLevel - }; + viewDesc.Texture2D = new() { MipSlice = subresource.MipLevel }; } else { @@ -159,19 +147,13 @@ public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) { dsvTokens[subresource] = token = Context.DsvHeap.Allocate(); - DepthStencilViewDesc viewDesc = new() - { - Format = DXFormats.DirectX12(Desc.Format) - }; + DepthStencilViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; switch (Desc.Type) { case TextureType.Texture1D: viewDesc.ViewDimension = DsvDimension.Texture1D; - viewDesc.Texture1D = new() - { - MipSlice = subresource.MipLevel - }; + viewDesc.Texture1D = new() { MipSlice = subresource.MipLevel }; break; case TextureType.Texture1DArray: @@ -188,10 +170,7 @@ public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) if (Desc.SampleCount is SampleCount.Count1) { viewDesc.ViewDimension = DsvDimension.Texture2D; - viewDesc.Texture2D = new() - { - MipSlice = subresource.MipLevel - }; + viewDesc.Texture2D = new() { MipSlice = subresource.MipLevel }; } else { diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs index 93cebe18..541bdd21 100644 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -136,27 +136,18 @@ private DXDescriptorToken CreateUavToken() { DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); - UnorderedAccessViewDesc viewDesc = new() - { - Format = DXFormats.DirectX12(Desc.Format) - }; + UnorderedAccessViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; switch (Desc.Type) { case TextureType.Texture1D: viewDesc.ViewDimension = UavDimension.Texture1D; - viewDesc.Texture1D = new() - { - MipSlice = Desc.Range.BaseMipLevel - }; + viewDesc.Texture1D = new() { MipSlice = Desc.Range.BaseMipLevel }; break; case TextureType.Texture2D: viewDesc.ViewDimension = UavDimension.Texture2D; - viewDesc.Texture2D = new() - { - MipSlice = Desc.Range.BaseMipLevel - }; + viewDesc.Texture2D = new() { MipSlice = Desc.Range.BaseMipLevel }; break; case TextureType.Texture3D: diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index f9c30e52..7d6ad649 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -63,10 +63,7 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe { ViewDimension = SrvDimension.RaytracingAccelerationStructure, Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, - RaytracingAccelerationStructure = new() - { - Location = AccelerationStructureBuffer.GPUVirtualAddress - } + RaytracingAccelerationStructure = new() { Location = AccelerationStructureBuffer.GPUVirtualAddress } }; context.Device.CreateShaderResourceView(default(ID3D12Resource*), &viewDesc, (Token = context.CbvSrvUavHeap.Allocate()).CpuHandle); From 93e0f34e079e5ccb33083afda5719729b8b1f750 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 03:20:35 +0800 Subject: [PATCH 197/466] Remove Silk.NET.MoltenVK.Native package reference The Silk.NET.MoltenVK.Native package was removed from both Directory.Packages.props and Zenith.NET.Vulkan.csproj, as it is no longer needed. No other dependencies were affected. --- sources/Directory.Packages.props | 1 - sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 1541b8c5..d08dc565 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -14,7 +14,6 @@ - diff --git a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj index 39132514..56ad22a6 100644 --- a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj +++ b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj @@ -9,7 +9,6 @@ - From 14bd3b72833f2f533696682993c1f093f0f129f6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 14:44:40 +0800 Subject: [PATCH 198/466] Refactor DX12 resource names and cleanup resource disposal Refactor field/property names for acceleration structures to shorter forms (`Transform`, `AccelerationStructure`, `Scratch`, `Instance`) and update all usages. Simplify border color assignment in `DXSampler` and condense `SampleDesc` initialization in `DXSwapChain`. Remove explicit `Resource.Dispose()` calls from `Destroy` methods in several classes to improve resource management. Remove primitive topology setting from graphics pipeline setup in `DXCommandBuffer`. --- .../DXBottomLevelAccelerationStructure.cs | 36 ++++++++--------- sources/Zenith.NET.DirectX12/DXBuffer.cs | 1 - .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 2 - .../Zenith.NET.DirectX12/DXCommandQueue.cs | 1 - sources/Zenith.NET.DirectX12/DXSampler.cs | 7 +--- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 5 +-- sources/Zenith.NET.DirectX12/DXTexture.cs | 1 - .../DXTopLevelAccelerationStructure.cs | 40 +++++++++---------- 8 files changed, 40 insertions(+), 53 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 383aed54..59a604a3 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -10,7 +10,7 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu { using ZenithMarshal.Scope scope = new(); - TransformBuffer = new(context, new() + Transform = new(context, new() { SizeInBytes = (uint)(sizeof(Matrix3X4) * desc.Geometries.Length), Residency = MemoryResidency.CpuWriteOnly @@ -21,13 +21,13 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - AccelerationStructureBuffer = new(context, new() + AccelerationStructure = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, Residency = MemoryResidency.GpuOnly }, ResourceFlags.RaytracingAccelerationStructure); - ScratchBuffer = new(context, new() + Scratch = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, Usages = BufferUsages.StorageReadWrite, @@ -36,9 +36,9 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu BuildRaytracingAccelerationStructureDesc buildDesc = new() { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, Inputs = inputs, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); @@ -61,11 +61,11 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu commandBuffer.CommandList.Barrier(1, &barrierGroup); } - public DXBuffer TransformBuffer { get; } + public DXBuffer Transform { get; } - public DXBuffer AccelerationStructureBuffer { get; } + public DXBuffer AccelerationStructure { get; } - public DXBuffer ScratchBuffer { get; } + public DXBuffer Scratch { get; } public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) { @@ -76,10 +76,10 @@ public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructu BuildRaytracingAccelerationStructureDesc buildDesc = new() { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, Inputs = inputs, - SourceAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + SourceAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); @@ -109,21 +109,21 @@ public override nint GetNativeObject(NativeObjectType type) protected override void SetResourceName(string name) { - AccelerationStructureBuffer.Name = name; + AccelerationStructure.Name = name; } protected override void Destroy() { - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - TransformBuffer.Dispose(); + Scratch.Dispose(); + AccelerationStructure.Dispose(); + Transform.Dispose(); } private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) { uint geometryCount = (uint)desc.Geometries.Length; - MappedMemory mappedMemory = TransformBuffer.Map(); + MappedMemory mappedMemory = Transform.Map(); Matrix3X4* transforms = (Matrix3X4*)mappedMemory.Pointer; RaytracingGeometryDesc* geometries = (RaytracingGeometryDesc*)ZenithMarshal.Allocate(scope, geometryCount); @@ -140,7 +140,7 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct ( triangles: geometry.Type is RayTracingGeometryType.Triangle ? new() { - Transform3x4 = TransformBuffer.GPUVirtualAddress + (ulong)(sizeof(Matrix3X4) * i), + Transform3x4 = Transform.GPUVirtualAddress + (ulong)(sizeof(Matrix3X4) * i), IndexFormat = geometry.TriangleGeometry.IndexBuffer is not null ? DXFormats.DirectX12(geometry.TriangleGeometry.IndexFormat) : Format.FormatUnknown, VertexFormat = DXFormats.DirectX12(geometry.TriangleGeometry.VertexFormat), IndexCount = geometry.TriangleGeometry.IndexCount, @@ -165,7 +165,7 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct }; } - TransformBuffer.Unmap(); + Transform.Unmap(); inputs = new() { diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 2da814b4..44c69eca 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -113,7 +113,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { View.Dispose(); - Resource.Dispose(); } diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 96a0c61c..b18aef35 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -14,7 +14,6 @@ internal unsafe class DXCommandBuffer : CommandBuffer public DXCommandBuffer(DXGraphicsContext context, CommandQueue queue) : base(context, queue) { context.Device.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), SilkMarshal.GuidPtrOf(), (void**)CommandAllocator.GetAddressOf()).Success(); - context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); } @@ -340,7 +339,6 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) protected override void SetPipelineImpl(GraphicsPipeline pipeline) { CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); - CommandList.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).Topology); } diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 7945a090..5097bd47 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -71,7 +71,6 @@ protected override void Destroy() base.Destroy(); Fence.Dispose(); - @event.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs index 70455ab0..bff9d354 100644 --- a/sources/Zenith.NET.DirectX12/DXSampler.cs +++ b/sources/Zenith.NET.DirectX12/DXSampler.cs @@ -10,8 +10,6 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de { Token = context.SamplerHeap.Allocate(); - (float r, float g, float b, float a) = DXFormats.DirectX12(desc.BorderColor); - DxSamplerDesc samplerDesc = new() { Filter = DXFormats.DirectX12(desc.MinFilter, desc.MagFilter, desc.MipFilter, desc.MaxAnisotropy, desc.CompareOp), @@ -25,10 +23,7 @@ public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, de MaxLOD = desc.MaxLod }; - samplerDesc.BorderColor[0] = r; - samplerDesc.BorderColor[1] = g; - samplerDesc.BorderColor[2] = b; - samplerDesc.BorderColor[3] = a; + (samplerDesc.BorderColor[0], samplerDesc.BorderColor[1], samplerDesc.BorderColor[2], samplerDesc.BorderColor[3]) = DXFormats.DirectX12(desc.BorderColor); context.Device.CreateSampler(&samplerDesc, Token.CpuHandle); } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index c73c76bc..4e1ff128 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -78,10 +78,7 @@ private void CreateSwapChain() Width = Desc.Surface.Width, Height = Desc.Surface.Height, Format = DXFormats.DirectX12(Desc.Format), - SampleDesc = new() - { - Count = 1 - }, + SampleDesc = new() { Count = 1 }, BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = BufferCount, SwapEffect = SwapEffect.FlipDiscard, diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 00fe3f05..db617487 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -234,7 +234,6 @@ protected override void Destroy() dsvTokens.Clear(); View.Dispose(); - Resource.Dispose(); } diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 7d6ad649..240298f0 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -9,7 +9,7 @@ internal unsafe class DXTopLevelAccelerationStructure : TopLevelAccelerationStru public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { - InstanceBuffer = new(context, new() + Instance = new(context, new() { SizeInBytes = (uint)(sizeof(RaytracingInstanceDesc) * desc.Instances.Length), Residency = MemoryResidency.CpuWriteOnly @@ -20,13 +20,13 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - AccelerationStructureBuffer = new(context, new() + AccelerationStructure = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, Residency = MemoryResidency.GpuOnly }, ResourceFlags.RaytracingAccelerationStructure); - ScratchBuffer = new(context, new() + Scratch = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, Usages = BufferUsages.StorageReadWrite, @@ -35,9 +35,9 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe BuildRaytracingAccelerationStructureDesc buildDesc = new() { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, Inputs = inputs, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); @@ -63,17 +63,17 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe { ViewDimension = SrvDimension.RaytracingAccelerationStructure, Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, - RaytracingAccelerationStructure = new() { Location = AccelerationStructureBuffer.GPUVirtualAddress } + RaytracingAccelerationStructure = new() { Location = AccelerationStructure.GPUVirtualAddress } }; context.Device.CreateShaderResourceView(default(ID3D12Resource*), &viewDesc, (Token = context.CbvSrvUavHeap.Allocate()).CpuHandle); } - public DXBuffer InstanceBuffer { get; } + public DXBuffer Instance { get; } - public DXBuffer AccelerationStructureBuffer { get; } + public DXBuffer AccelerationStructure { get; } - public DXBuffer ScratchBuffer { get; } + public DXBuffer Scratch { get; } public override ResourceHandle Handle => Token.ResourceHandle; @@ -84,10 +84,10 @@ public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureD BuildRaytracingAccelerationStructureDesc buildDesc = new() { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, Inputs = inputs, - SourceAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + SourceAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); @@ -117,23 +117,23 @@ public override nint GetNativeObject(NativeObjectType type) protected override void SetResourceName(string name) { - AccelerationStructureBuffer.Name = name; + AccelerationStructure.Name = name; } protected override void Destroy() { Token.Dispose(); - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - InstanceBuffer.Dispose(); + Scratch.Dispose(); + AccelerationStructure.Dispose(); + Instance.Dispose(); } private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) { uint instanceCount = (uint)desc.Instances.Length; - MappedMemory mappedMemory = InstanceBuffer.Map(); + MappedMemory mappedMemory = Instance.Map(); RaytracingInstanceDesc* instances = (RaytracingInstanceDesc*)mappedMemory.Pointer; for (uint i = 0; i < instanceCount; i++) @@ -145,20 +145,20 @@ private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytrac InstanceID = instance.InstanceId, InstanceMask = instance.VisibilityMask, Flags = (uint)DXFormats.DirectX12(instance.Flags), - AccelerationStructure = instance.AccelerationStructure.DirectX12().AccelerationStructureBuffer.GPUVirtualAddress + AccelerationStructure = instance.AccelerationStructure.DirectX12().AccelerationStructure.GPUVirtualAddress }; *(Matrix3X4*)instances[i].Transform = DXFormats.DirectX12(instance.Transform); } - InstanceBuffer.Unmap(); + Instance.Unmap(); inputs = new() { Type = RaytracingAccelerationStructureType.TopLevel, Flags = DXFormats.DirectX12(desc.BuildFlags), NumDescs = instanceCount, - InstanceDescs = InstanceBuffer.GPUVirtualAddress + InstanceDescs = Instance.GPUVirtualAddress }; } } From 2e2b4dfc168e8ecdf53ef54225045f81d7f40516 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 15:06:01 +0800 Subject: [PATCH 199/466] Refactor DXFormats and Extensions for clarity and coverage Reorganized and expanded DXFormats static conversion methods for DirectX 12 enum/flag mapping, improving maintainability and adding support for new types. Refactored Extensions to group and simplify DirectX12 conversion methods by type, enhancing readability. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 716 ++++++++++----------- sources/Zenith.NET.DirectX12/Extensions.cs | 90 +-- 2 files changed, 403 insertions(+), 403 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 4c5cf227..89375b51 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -8,175 +8,118 @@ namespace Zenith.NET.DirectX12; internal static class DXFormats { - public static CommandListType DirectX12(CommandQueueType commandQueueType) + public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - return commandQueueType switch - { - CommandQueueType.Graphics => CommandListType.Direct, - CommandQueueType.Compute => CommandListType.Compute, - CommandQueueType.Copy => CommandListType.Copy, - _ => default - }; - } + RaytracingAccelerationStructureBuildFlags result = RaytracingAccelerationStructureBuildFlags.None; - public static Format DirectX12(PixelFormat pixelFormat) - { - return pixelFormat switch + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - PixelFormat.R8UNorm => Format.FormatR8Unorm, - PixelFormat.R8SNorm => Format.FormatR8SNorm, - PixelFormat.R8UInt => Format.FormatR8Uint, - PixelFormat.R8SInt => Format.FormatR8Sint, - - PixelFormat.R16UNorm => Format.FormatR16Unorm, - PixelFormat.R16SNorm => Format.FormatR16SNorm, - PixelFormat.R16UInt => Format.FormatR16Uint, - PixelFormat.R16SInt => Format.FormatR16Sint, - PixelFormat.R16Float => Format.FormatR16Float, - - PixelFormat.R32UInt => Format.FormatR32Uint, - PixelFormat.R32SInt => Format.FormatR32Sint, - PixelFormat.R32Float => Format.FormatR32Float, - - PixelFormat.R8G8UNorm => Format.FormatR8G8Unorm, - PixelFormat.R8G8SNorm => Format.FormatR8G8SNorm, - PixelFormat.R8G8UInt => Format.FormatR8G8Uint, - PixelFormat.R8G8SInt => Format.FormatR8G8Sint, - - PixelFormat.R16G16UNorm => Format.FormatR16G16Unorm, - PixelFormat.R16G16SNorm => Format.FormatR16G16SNorm, - PixelFormat.R16G16UInt => Format.FormatR16G16Uint, - PixelFormat.R16G16SInt => Format.FormatR16G16Sint, - PixelFormat.R16G16Float => Format.FormatR16G16Float, - - PixelFormat.R32G32UInt => Format.FormatR32G32Uint, - PixelFormat.R32G32SInt => Format.FormatR32G32Sint, - PixelFormat.R32G32Float => Format.FormatR32G32Float, - - PixelFormat.R32G32B32UInt => Format.FormatR32G32B32Uint, - PixelFormat.R32G32B32SInt => Format.FormatR32G32B32Sint, - PixelFormat.R32G32B32Float => Format.FormatR32G32B32Float, - - PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, - PixelFormat.R8G8B8A8SNorm => Format.FormatR8G8B8A8SNorm, - PixelFormat.R8G8B8A8UInt => Format.FormatR8G8B8A8Uint, - PixelFormat.R8G8B8A8SInt => Format.FormatR8G8B8A8Sint, - PixelFormat.R8G8B8A8SRgb => Format.FormatR8G8B8A8UnormSrgb, - - PixelFormat.R16G16B16A16UNorm => Format.FormatR16G16B16A16Unorm, - PixelFormat.R16G16B16A16SNorm => Format.FormatR16G16B16A16SNorm, - PixelFormat.R16G16B16A16UInt => Format.FormatR16G16B16A16Uint, - PixelFormat.R16G16B16A16SInt => Format.FormatR16G16B16A16Sint, - PixelFormat.R16G16B16A16Float => Format.FormatR16G16B16A16Float, - - PixelFormat.R32G32B32A32UInt => Format.FormatR32G32B32A32Uint, - PixelFormat.R32G32B32A32SInt => Format.FormatR32G32B32A32Sint, - PixelFormat.R32G32B32A32Float => Format.FormatR32G32B32A32Float, - - PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - PixelFormat.B8G8R8A8SRgb => Format.FormatB8G8R8A8UnormSrgb, - - PixelFormat.D16UNorm => Format.FormatD16Unorm, - PixelFormat.D24UNormS8UInt => Format.FormatD24UnormS8Uint, - PixelFormat.D32Float => Format.FormatD32Float, - PixelFormat.D32FloatS8UInt => Format.FormatD32FloatS8X24Uint, + result |= RaytracingAccelerationStructureBuildFlags.AllowUpdate; + } - PixelFormat.BC4UNorm => Format.FormatBC4Unorm, - PixelFormat.BC4SNorm => Format.FormatBC4SNorm, + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) + { + result |= RaytracingAccelerationStructureBuildFlags.AllowCompaction; + } - PixelFormat.BC5UNorm => Format.FormatBC5Unorm, - PixelFormat.BC5SNorm => Format.FormatBC5SNorm, + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + { + result |= RaytracingAccelerationStructureBuildFlags.PreferFastTrace; + } - PixelFormat.BC6HUFloat => Format.FormatBC6HUF16, - PixelFormat.BC6HSFloat => Format.FormatBC6HSF16, + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + { + result |= RaytracingAccelerationStructureBuildFlags.PreferFastBuild; + } - PixelFormat.BC7UNorm => Format.FormatBC7Unorm, - PixelFormat.BC7SRgb => Format.FormatBC7UnormSrgb, + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + { + result |= RaytracingAccelerationStructureBuildFlags.MinimizeMemory; + } - _ => default - }; + return result; } - public static DxHeapType DirectX12(MemoryResidency memoryResidency) + public static TextureAddressMode DirectX12(AddressMode addressMode) { - return memoryResidency switch + return addressMode switch { - MemoryResidency.GpuOnly => DxHeapType.Default, - MemoryResidency.CpuReadOnly => DxHeapType.Readback, - MemoryResidency.CpuWriteOnly => DxHeapType.Upload, + AddressMode.Wrap => TextureAddressMode.Wrap, + AddressMode.Mirror => TextureAddressMode.Mirror, + AddressMode.Clamp => TextureAddressMode.Clamp, + AddressMode.Border => TextureAddressMode.Border, _ => default }; } - public static ResourceFlags DirectX12(BufferUsages bufferUsages) + public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages barrierStages) { - ResourceFlags result = ResourceFlags.None; + BarrierSync sync = BarrierSync.None; + BarrierAccess access = BarrierAccess.Common; - if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) + if (barrierStages.HasFlag(BarrierStages.Vertex)) { - result |= ResourceFlags.AllowUnorderedAccess; + sync |= BarrierSync.VertexShading; + access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; } - return result; - } + if (barrierStages.HasFlag(BarrierStages.Fragment)) + { + sync |= BarrierSync.PixelShading; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; + } - public static ResourceDimension DirectX12(TextureType textureType) - { - return textureType switch + if (barrierStages.HasFlag(BarrierStages.Compute)) { - TextureType.Texture1D or - TextureType.Texture1DArray => ResourceDimension.Texture1D, + sync |= BarrierSync.ComputeShading; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.UnorderedAccess; + } - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ResourceDimension.Texture2D, + if (barrierStages.HasFlag(BarrierStages.Copy)) + { + sync |= BarrierSync.Copy; + access |= BarrierAccess.CopySource | BarrierAccess.CopyDest; + } - TextureType.Texture3D => ResourceDimension.Texture3D, + if (barrierStages.HasFlag(BarrierStages.All)) + { + sync |= BarrierSync.All; + } - _ => default - }; + return (sync, access); } - public static SampleDesc DirectX12(SampleCount sampleCount) + public static Blend DirectX12(BlendFactor blendFactor) { - return sampleCount switch + return blendFactor switch { - SampleCount.Count1 => new() { Count = 1 }, - SampleCount.Count2 => new() { Count = 2 }, - SampleCount.Count4 => new() { Count = 4 }, - SampleCount.Count8 => new() { Count = 8 }, - SampleCount.Count16 => new() { Count = 16 }, - SampleCount.Count32 => new() { Count = 32 }, + BlendFactor.Zero => Blend.Zero, + BlendFactor.One => Blend.One, + BlendFactor.SrcColor => Blend.SrcColor, + BlendFactor.OneMinusSrcColor => Blend.InvSrcColor, + BlendFactor.DstColor => Blend.DestColor, + BlendFactor.OneMinusDstColor => Blend.InvDestColor, + BlendFactor.SrcAlpha => Blend.SrcAlpha, + BlendFactor.OneMinusSrcAlpha => Blend.InvSrcAlpha, + BlendFactor.DstAlpha => Blend.DestAlpha, + BlendFactor.OneMinusDstAlpha => Blend.InvDestAlpha, + BlendFactor.Constant => Blend.BlendFactor, + BlendFactor.OneMinusConstant => Blend.InvBlendFactor, _ => default }; } - public static ResourceFlags DirectX12(TextureUsages textureUsages) + public static DxBlendOp DirectX12(BlendOp blendOp) { - ResourceFlags result = ResourceFlags.None; - - if (!textureUsages.HasFlag(TextureUsages.Sampled)) - { - result |= ResourceFlags.DenyShaderResource; - } - - if (textureUsages.HasFlag(TextureUsages.Storage)) - { - result |= ResourceFlags.AllowUnorderedAccess; - } - - if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) - { - result |= ResourceFlags.AllowRenderTarget; - } - - if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) + return blendOp switch { - result |= ResourceFlags.AllowDepthStencil; - } - - return result; + BlendOp.Add => DxBlendOp.Add, + BlendOp.Subtract => DxBlendOp.Subtract, + BlendOp.ReverseSubtract => DxBlendOp.RevSubtract, + BlendOp.Min => DxBlendOp.Min, + BlendOp.Max => DxBlendOp.Max, + _ => default + }; } public static (float R, float G, float B, float A) DirectX12(BorderColor borderColor) @@ -190,45 +133,52 @@ public static (float R, float G, float B, float A) DirectX12(BorderColor borderC }; } - public static Filter DirectX12(FilterMode minFilter, FilterMode magFilter, FilterMode mipFilter, uint maxAnisotropy, CompareOp compareOp) + public static ResourceFlags DirectX12(BufferUsages bufferUsages) { - if (maxAnisotropy > 1) + ResourceFlags result = ResourceFlags.None; + + if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) { - return compareOp is CompareOp.Never ? Filter.Anisotropic : Filter.ComparisonAnisotropic; + result |= ResourceFlags.AllowUnorderedAccess; } - return (minFilter, magFilter, mipFilter, compareOp) switch + return result; + } + + public static ColorWriteEnable DirectX12(ColorWrites colorWrites) + { + ColorWriteEnable result = ColorWriteEnable.None; + + if (colorWrites.HasFlag(ColorWrites.Red)) { - (FilterMode.Point, FilterMode.Point, FilterMode.Point, CompareOp.Never) => Filter.MinMagMipPoint, - (FilterMode.Point, FilterMode.Point, FilterMode.Linear, CompareOp.Never) => Filter.MinMagPointMipLinear, - (FilterMode.Point, FilterMode.Linear, FilterMode.Point, CompareOp.Never) => Filter.MinPointMagLinearMipPoint, - (FilterMode.Point, FilterMode.Linear, FilterMode.Linear, CompareOp.Never) => Filter.MinPointMagMipLinear, - (FilterMode.Linear, FilterMode.Point, FilterMode.Point, CompareOp.Never) => Filter.MinLinearMagMipPoint, - (FilterMode.Linear, FilterMode.Point, FilterMode.Linear, CompareOp.Never) => Filter.MinLinearMagPointMipLinear, - (FilterMode.Linear, FilterMode.Linear, FilterMode.Point, CompareOp.Never) => Filter.MinMagLinearMipPoint, - (FilterMode.Linear, FilterMode.Linear, FilterMode.Linear, CompareOp.Never) => Filter.MinMagMipLinear, + result |= ColorWriteEnable.Red; + } - (FilterMode.Point, FilterMode.Point, FilterMode.Point, _) => Filter.ComparisonMinMagMipPoint, - (FilterMode.Point, FilterMode.Point, FilterMode.Linear, _) => Filter.ComparisonMinMagPointMipLinear, - (FilterMode.Point, FilterMode.Linear, FilterMode.Point, _) => Filter.ComparisonMinPointMagLinearMipPoint, - (FilterMode.Point, FilterMode.Linear, FilterMode.Linear, _) => Filter.ComparisonMinPointMagMipLinear, - (FilterMode.Linear, FilterMode.Point, FilterMode.Point, _) => Filter.ComparisonMinLinearMagMipPoint, - (FilterMode.Linear, FilterMode.Point, FilterMode.Linear, _) => Filter.ComparisonMinLinearMagPointMipLinear, - (FilterMode.Linear, FilterMode.Linear, FilterMode.Point, _) => Filter.ComparisonMinMagLinearMipPoint, - (FilterMode.Linear, FilterMode.Linear, FilterMode.Linear, _) => Filter.ComparisonMinMagMipLinear, + if (colorWrites.HasFlag(ColorWrites.Green)) + { + result |= ColorWriteEnable.Green; + } - _ => default - }; + if (colorWrites.HasFlag(ColorWrites.Blue)) + { + result |= ColorWriteEnable.Blue; + } + + if (colorWrites.HasFlag(ColorWrites.Alpha)) + { + result |= ColorWriteEnable.Alpha; + } + + return result; } - public static TextureAddressMode DirectX12(AddressMode addressMode) + public static CommandListType DirectX12(CommandQueueType commandQueueType) { - return addressMode switch + return commandQueueType switch { - AddressMode.Wrap => TextureAddressMode.Wrap, - AddressMode.Mirror => TextureAddressMode.Mirror, - AddressMode.Clamp => TextureAddressMode.Clamp, - AddressMode.Border => TextureAddressMode.Border, + CommandQueueType.Graphics => CommandListType.Direct, + CommandQueueType.Compute => CommandListType.Compute, + CommandQueueType.Copy => CommandListType.Copy, _ => default }; } @@ -249,32 +199,15 @@ public static ComparisonFunc DirectX12(CompareOp compareOp) }; } - public static (PrimitiveTopologyType TopologyType, D3DPrimitiveTopology Topology) DirectX12(PrimitiveTopology primitiveTopology) + public static DxCullMode DirectX12(CullMode cullMode) { - return - ( - primitiveTopology switch - { - PrimitiveTopology.PointList => PrimitiveTopologyType.Point, - - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, - - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, - - _ => default - }, - primitiveTopology switch - { - PrimitiveTopology.PointList => D3DPrimitiveTopology.D3DPrimitiveTopologyPointlist, - PrimitiveTopology.LineList => D3DPrimitiveTopology.D3DPrimitiveTopologyLinelist, - PrimitiveTopology.LineStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyLinestrip, - PrimitiveTopology.TriangleList => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglelist, - PrimitiveTopology.TriangleStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglestrip, - _ => default - } - ); + return cullMode switch + { + CullMode.None => DxCullMode.None, + CullMode.Front => DxCullMode.Front, + CullMode.Back => DxCullMode.Back, + _ => default + }; } public static Format DirectX12(ElementFormat elementFormat) @@ -346,91 +279,192 @@ public static DxFillMode DirectX12(FillMode fillMode) }; } - public static DxCullMode DirectX12(CullMode cullMode) + public static Filter DirectX12(FilterMode minFilter, FilterMode magFilter, FilterMode mipFilter, uint maxAnisotropy, CompareOp compareOp) { - return cullMode switch + if (maxAnisotropy > 1) { - CullMode.None => DxCullMode.None, - CullMode.Front => DxCullMode.Front, - CullMode.Back => DxCullMode.Back, + return compareOp is CompareOp.Never ? Filter.Anisotropic : Filter.ComparisonAnisotropic; + } + + return (minFilter, magFilter, mipFilter, compareOp) switch + { + (FilterMode.Point, FilterMode.Point, FilterMode.Point, CompareOp.Never) => Filter.MinMagMipPoint, + (FilterMode.Point, FilterMode.Point, FilterMode.Linear, CompareOp.Never) => Filter.MinMagPointMipLinear, + (FilterMode.Point, FilterMode.Linear, FilterMode.Point, CompareOp.Never) => Filter.MinPointMagLinearMipPoint, + (FilterMode.Point, FilterMode.Linear, FilterMode.Linear, CompareOp.Never) => Filter.MinPointMagMipLinear, + (FilterMode.Linear, FilterMode.Point, FilterMode.Point, CompareOp.Never) => Filter.MinLinearMagMipPoint, + (FilterMode.Linear, FilterMode.Point, FilterMode.Linear, CompareOp.Never) => Filter.MinLinearMagPointMipLinear, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Point, CompareOp.Never) => Filter.MinMagLinearMipPoint, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Linear, CompareOp.Never) => Filter.MinMagMipLinear, + + (FilterMode.Point, FilterMode.Point, FilterMode.Point, _) => Filter.ComparisonMinMagMipPoint, + (FilterMode.Point, FilterMode.Point, FilterMode.Linear, _) => Filter.ComparisonMinMagPointMipLinear, + (FilterMode.Point, FilterMode.Linear, FilterMode.Point, _) => Filter.ComparisonMinPointMagLinearMipPoint, + (FilterMode.Point, FilterMode.Linear, FilterMode.Linear, _) => Filter.ComparisonMinPointMagMipLinear, + (FilterMode.Linear, FilterMode.Point, FilterMode.Point, _) => Filter.ComparisonMinLinearMagMipPoint, + (FilterMode.Linear, FilterMode.Point, FilterMode.Linear, _) => Filter.ComparisonMinLinearMagPointMipLinear, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Point, _) => Filter.ComparisonMinMagLinearMipPoint, + (FilterMode.Linear, FilterMode.Linear, FilterMode.Linear, _) => Filter.ComparisonMinMagMipLinear, + _ => default }; } - public static DxStencilOp DirectX12(StencilOp stencilOp) + public static Format DirectX12(IndexFormat indexFormat) { - return stencilOp switch + return indexFormat switch { - StencilOp.Keep => DxStencilOp.Keep, - StencilOp.Zero => DxStencilOp.Zero, - StencilOp.Replace => DxStencilOp.Replace, - StencilOp.IncrementAndClamp => DxStencilOp.IncrSat, - StencilOp.DecrementAndClamp => DxStencilOp.DecrSat, - StencilOp.Invert => DxStencilOp.Invert, - StencilOp.IncrementAndWrap => DxStencilOp.Incr, - StencilOp.DecrementAndWrap => DxStencilOp.Decr, + IndexFormat.UInt16 => Format.FormatR16Uint, + IndexFormat.UInt32 => Format.FormatR32Uint, + _ => default + }; + } + + public static RenderPassBeginningAccessType DirectX12(LoadOp loadOp) + { + return loadOp switch + { + LoadOp.Load => RenderPassBeginningAccessType.Preserve, + LoadOp.Clear => RenderPassBeginningAccessType.Clear, + LoadOp.DontCare => RenderPassBeginningAccessType.Discard, + _ => default + }; + } + + public static Matrix3X4 DirectX12(Matrix4x4 matrix4x4) + { + return new() + { + M11 = matrix4x4.M11, + M12 = matrix4x4.M21, + M13 = matrix4x4.M31, + M14 = matrix4x4.M41, + M21 = matrix4x4.M12, + M22 = matrix4x4.M22, + M23 = matrix4x4.M32, + M24 = matrix4x4.M42, + M31 = matrix4x4.M13, + M32 = matrix4x4.M23, + M33 = matrix4x4.M33, + M34 = matrix4x4.M43 + }; + } + + public static DxHeapType DirectX12(MemoryResidency memoryResidency) + { + return memoryResidency switch + { + MemoryResidency.GpuOnly => DxHeapType.Default, + MemoryResidency.CpuReadOnly => DxHeapType.Readback, + MemoryResidency.CpuWriteOnly => DxHeapType.Upload, _ => default }; } - public static Blend DirectX12(BlendFactor blendFactor) - { - return blendFactor switch - { - BlendFactor.Zero => Blend.Zero, - BlendFactor.One => Blend.One, - BlendFactor.SrcColor => Blend.SrcColor, - BlendFactor.OneMinusSrcColor => Blend.InvSrcColor, - BlendFactor.DstColor => Blend.DestColor, - BlendFactor.OneMinusDstColor => Blend.InvDestColor, - BlendFactor.SrcAlpha => Blend.SrcAlpha, - BlendFactor.OneMinusSrcAlpha => Blend.InvSrcAlpha, - BlendFactor.DstAlpha => Blend.DestAlpha, - BlendFactor.OneMinusDstAlpha => Blend.InvDestAlpha, - BlendFactor.Constant => Blend.BlendFactor, - BlendFactor.OneMinusConstant => Blend.InvBlendFactor, - _ => default - }; - } + public static Format DirectX12(PixelFormat pixelFormat) + { + return pixelFormat switch + { + PixelFormat.R8UNorm => Format.FormatR8Unorm, + PixelFormat.R8SNorm => Format.FormatR8SNorm, + PixelFormat.R8UInt => Format.FormatR8Uint, + PixelFormat.R8SInt => Format.FormatR8Sint, + + PixelFormat.R16UNorm => Format.FormatR16Unorm, + PixelFormat.R16SNorm => Format.FormatR16SNorm, + PixelFormat.R16UInt => Format.FormatR16Uint, + PixelFormat.R16SInt => Format.FormatR16Sint, + PixelFormat.R16Float => Format.FormatR16Float, + + PixelFormat.R32UInt => Format.FormatR32Uint, + PixelFormat.R32SInt => Format.FormatR32Sint, + PixelFormat.R32Float => Format.FormatR32Float, + + PixelFormat.R8G8UNorm => Format.FormatR8G8Unorm, + PixelFormat.R8G8SNorm => Format.FormatR8G8SNorm, + PixelFormat.R8G8UInt => Format.FormatR8G8Uint, + PixelFormat.R8G8SInt => Format.FormatR8G8Sint, + + PixelFormat.R16G16UNorm => Format.FormatR16G16Unorm, + PixelFormat.R16G16SNorm => Format.FormatR16G16SNorm, + PixelFormat.R16G16UInt => Format.FormatR16G16Uint, + PixelFormat.R16G16SInt => Format.FormatR16G16Sint, + PixelFormat.R16G16Float => Format.FormatR16G16Float, + + PixelFormat.R32G32UInt => Format.FormatR32G32Uint, + PixelFormat.R32G32SInt => Format.FormatR32G32Sint, + PixelFormat.R32G32Float => Format.FormatR32G32Float, + + PixelFormat.R32G32B32UInt => Format.FormatR32G32B32Uint, + PixelFormat.R32G32B32SInt => Format.FormatR32G32B32Sint, + PixelFormat.R32G32B32Float => Format.FormatR32G32B32Float, + + PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, + PixelFormat.R8G8B8A8SNorm => Format.FormatR8G8B8A8SNorm, + PixelFormat.R8G8B8A8UInt => Format.FormatR8G8B8A8Uint, + PixelFormat.R8G8B8A8SInt => Format.FormatR8G8B8A8Sint, + PixelFormat.R8G8B8A8SRgb => Format.FormatR8G8B8A8UnormSrgb, + + PixelFormat.R16G16B16A16UNorm => Format.FormatR16G16B16A16Unorm, + PixelFormat.R16G16B16A16SNorm => Format.FormatR16G16B16A16SNorm, + PixelFormat.R16G16B16A16UInt => Format.FormatR16G16B16A16Uint, + PixelFormat.R16G16B16A16SInt => Format.FormatR16G16B16A16Sint, + PixelFormat.R16G16B16A16Float => Format.FormatR16G16B16A16Float, + + PixelFormat.R32G32B32A32UInt => Format.FormatR32G32B32A32Uint, + PixelFormat.R32G32B32A32SInt => Format.FormatR32G32B32A32Sint, + PixelFormat.R32G32B32A32Float => Format.FormatR32G32B32A32Float, + + PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, + PixelFormat.B8G8R8A8SRgb => Format.FormatB8G8R8A8UnormSrgb, + + PixelFormat.D16UNorm => Format.FormatD16Unorm, + PixelFormat.D24UNormS8UInt => Format.FormatD24UnormS8Uint, + PixelFormat.D32Float => Format.FormatD32Float, + PixelFormat.D32FloatS8UInt => Format.FormatD32FloatS8X24Uint, + + PixelFormat.BC4UNorm => Format.FormatBC4Unorm, + PixelFormat.BC4SNorm => Format.FormatBC4SNorm, + + PixelFormat.BC5UNorm => Format.FormatBC5Unorm, + PixelFormat.BC5SNorm => Format.FormatBC5SNorm, + + PixelFormat.BC6HUFloat => Format.FormatBC6HUF16, + PixelFormat.BC6HSFloat => Format.FormatBC6HSF16, + + PixelFormat.BC7UNorm => Format.FormatBC7Unorm, + PixelFormat.BC7SRgb => Format.FormatBC7UnormSrgb, - public static DxBlendOp DirectX12(BlendOp blendOp) - { - return blendOp switch - { - BlendOp.Add => DxBlendOp.Add, - BlendOp.Subtract => DxBlendOp.Subtract, - BlendOp.ReverseSubtract => DxBlendOp.RevSubtract, - BlendOp.Min => DxBlendOp.Min, - BlendOp.Max => DxBlendOp.Max, _ => default }; } - public static ColorWriteEnable DirectX12(ColorWrites colorWrites) + public static (PrimitiveTopologyType TopologyType, D3DPrimitiveTopology Topology) DirectX12(PrimitiveTopology primitiveTopology) { - ColorWriteEnable result = ColorWriteEnable.None; - - if (colorWrites.HasFlag(ColorWrites.Red)) - { - result |= ColorWriteEnable.Red; - } - - if (colorWrites.HasFlag(ColorWrites.Green)) - { - result |= ColorWriteEnable.Green; - } + return + ( + primitiveTopology switch + { + PrimitiveTopology.PointList => PrimitiveTopologyType.Point, - if (colorWrites.HasFlag(ColorWrites.Blue)) - { - result |= ColorWriteEnable.Blue; - } + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, - if (colorWrites.HasFlag(ColorWrites.Alpha)) - { - result |= ColorWriteEnable.Alpha; - } + PrimitiveTopology.TriangleList or + PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, - return result; + _ => default + }, + primitiveTopology switch + { + PrimitiveTopology.PointList => D3DPrimitiveTopology.D3DPrimitiveTopologyPointlist, + PrimitiveTopology.LineList => D3DPrimitiveTopology.D3DPrimitiveTopologyLinelist, + PrimitiveTopology.LineStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyLinestrip, + PrimitiveTopology.TriangleList => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglelist, + PrimitiveTopology.TriangleStrip => D3DPrimitiveTopology.D3DPrimitiveTopologyTrianglestrip, + _ => default + } + ); } public static (QueryHeapType HeapType, DxQueryType Type) DirectX12(QueryType queryType) @@ -453,23 +487,69 @@ public static (QueryHeapType HeapType, DxQueryType Type) DirectX12(QueryType que ); } - public static Format DirectX12(IndexFormat indexFormat) + public static RaytracingGeometryType DirectX12(RayTracingGeometryType rayTracingGeometryType) { - return indexFormat switch + return rayTracingGeometryType switch { - IndexFormat.UInt16 => Format.FormatR16Uint, - IndexFormat.UInt32 => Format.FormatR32Uint, + RayTracingGeometryType.Triangle => RaytracingGeometryType.Triangles, + RayTracingGeometryType.Aabb => RaytracingGeometryType.ProceduralPrimitiveAabbs, _ => default }; } - public static RenderPassBeginningAccessType DirectX12(LoadOp loadOp) + public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) { - return loadOp switch + RaytracingInstanceFlags result = RaytracingInstanceFlags.None; + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { - LoadOp.Load => RenderPassBeginningAccessType.Preserve, - LoadOp.Clear => RenderPassBeginningAccessType.Clear, - LoadOp.DontCare => RenderPassBeginningAccessType.Discard, + result |= RaytracingInstanceFlags.TriangleFrontCounterclockwise; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) + { + result |= RaytracingInstanceFlags.TriangleCullDisable; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + { + result |= RaytracingInstanceFlags.ForceOpaque; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) + { + result |= RaytracingInstanceFlags.ForceNonOpaque; + } + + return result; + } + + public static SampleDesc DirectX12(SampleCount sampleCount) + { + return sampleCount switch + { + SampleCount.Count1 => new() { Count = 1 }, + SampleCount.Count2 => new() { Count = 2 }, + SampleCount.Count4 => new() { Count = 4 }, + SampleCount.Count8 => new() { Count = 8 }, + SampleCount.Count16 => new() { Count = 16 }, + SampleCount.Count32 => new() { Count = 32 }, + _ => default + }; + } + + public static DxStencilOp DirectX12(StencilOp stencilOp) + { + return stencilOp switch + { + StencilOp.Keep => DxStencilOp.Keep, + StencilOp.Zero => DxStencilOp.Zero, + StencilOp.Replace => DxStencilOp.Replace, + StencilOp.IncrementAndClamp => DxStencilOp.IncrSat, + StencilOp.DecrementAndClamp => DxStencilOp.DecrSat, + StencilOp.Invert => DxStencilOp.Invert, + StencilOp.IncrementAndWrap => DxStencilOp.Incr, + StencilOp.DecrementAndWrap => DxStencilOp.Decr, _ => default }; } @@ -504,128 +584,48 @@ public static (BarrierSync Sync, BarrierAccess Access, BarrierLayout Layout) Dir }; } - public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages barrierStages) + public static ResourceDimension DirectX12(TextureType textureType) { - BarrierSync sync = BarrierSync.None; - BarrierAccess access = BarrierAccess.Common; - - if (barrierStages.HasFlag(BarrierStages.Vertex)) - { - sync |= BarrierSync.VertexShading; - access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; - } - - if (barrierStages.HasFlag(BarrierStages.Fragment)) - { - sync |= BarrierSync.PixelShading; - access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; - } - - if (barrierStages.HasFlag(BarrierStages.Compute)) - { - sync |= BarrierSync.ComputeShading; - access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.UnorderedAccess; - } - - if (barrierStages.HasFlag(BarrierStages.Copy)) + return textureType switch { - sync |= BarrierSync.Copy; - access |= BarrierAccess.CopySource | BarrierAccess.CopyDest; - } + TextureType.Texture1D or + TextureType.Texture1DArray => ResourceDimension.Texture1D, - if (barrierStages.HasFlag(BarrierStages.All)) - { - sync |= BarrierSync.All; - } + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ResourceDimension.Texture2D, - return (sync, access); - } + TextureType.Texture3D => ResourceDimension.Texture3D, - public static RaytracingGeometryType DirectX12(RayTracingGeometryType rayTracingGeometryType) - { - return rayTracingGeometryType switch - { - RayTracingGeometryType.Triangle => RaytracingGeometryType.Triangles, - RayTracingGeometryType.Aabb => RaytracingGeometryType.ProceduralPrimitiveAabbs, _ => default }; } - public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) - { - RaytracingInstanceFlags result = RaytracingInstanceFlags.None; - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) - { - result |= RaytracingInstanceFlags.TriangleFrontCounterclockwise; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) - { - result |= RaytracingInstanceFlags.TriangleCullDisable; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) - { - result |= RaytracingInstanceFlags.ForceOpaque; - } - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) - { - result |= RaytracingInstanceFlags.ForceNonOpaque; - } - - return result; - } - - public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) + public static ResourceFlags DirectX12(TextureUsages textureUsages) { - RaytracingAccelerationStructureBuildFlags result = RaytracingAccelerationStructureBuildFlags.None; - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) - { - result |= RaytracingAccelerationStructureBuildFlags.AllowUpdate; - } + ResourceFlags result = ResourceFlags.None; - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) + if (!textureUsages.HasFlag(TextureUsages.Sampled)) { - result |= RaytracingAccelerationStructureBuildFlags.AllowCompaction; + result |= ResourceFlags.DenyShaderResource; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + if (textureUsages.HasFlag(TextureUsages.Storage)) { - result |= RaytracingAccelerationStructureBuildFlags.PreferFastTrace; + result |= ResourceFlags.AllowUnorderedAccess; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) { - result |= RaytracingAccelerationStructureBuildFlags.PreferFastBuild; + result |= ResourceFlags.AllowRenderTarget; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { - result |= RaytracingAccelerationStructureBuildFlags.MinimizeMemory; + result |= ResourceFlags.AllowDepthStencil; } return result; } - - public static Matrix3X4 DirectX12(Matrix4x4 matrix4x4) - { - return new() - { - M11 = matrix4x4.M11, - M12 = matrix4x4.M21, - M13 = matrix4x4.M31, - M14 = matrix4x4.M41, - M21 = matrix4x4.M12, - M22 = matrix4x4.M22, - M23 = matrix4x4.M32, - M24 = matrix4x4.M42, - M31 = matrix4x4.M13, - M32 = matrix4x4.M23, - M33 = matrix4x4.M33, - M34 = matrix4x4.M43 - }; - } } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 9e2a9cff..2c739cb2 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -28,123 +28,123 @@ internal bool IsSuccess() } } - extension(CommandQueue commandQueue) + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { - internal DXCommandQueue DirectX12() + internal DXBottomLevelAccelerationStructure DirectX12() { - return (DXCommandQueue)commandQueue; + return (DXBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; } } - extension(CommandBuffer commandBuffer) + extension(Buffer buffer) { - internal DXCommandBuffer DirectX12() + internal DXBuffer DirectX12() { - return (DXCommandBuffer)commandBuffer; + return (DXBuffer)buffer; } } - extension(SwapChain swapChain) + extension(BufferView bufferView) { - internal DXSwapChain DirectX12() + internal DXBufferView DirectX12() { - return (DXSwapChain)swapChain; + return (DXBufferView)bufferView; } } - extension(Heap heap) + extension(CommandBuffer commandBuffer) { - internal DXHeap DirectX12() + internal DXCommandBuffer DirectX12() { - return (DXHeap)heap; + return (DXCommandBuffer)commandBuffer; } } - extension(Buffer buffer) + extension(CommandQueue commandQueue) { - internal DXBuffer DirectX12() + internal DXCommandQueue DirectX12() { - return (DXBuffer)buffer; + return (DXCommandQueue)commandQueue; } } - extension(BufferView bufferView) + extension(ComputePipeline computePipeline) { - internal DXBufferView DirectX12() + internal DXComputePipeline DirectX12() { - return (DXBufferView)bufferView; + return (DXComputePipeline)computePipeline; } } - extension(Texture texture) + extension(GraphicsPipeline graphicsPipeline) { - internal DXTexture DirectX12() + internal DXGraphicsPipeline DirectX12() { - return (DXTexture)texture; + return (DXGraphicsPipeline)graphicsPipeline; } } - extension(TextureView textureView) + extension(Heap heap) { - internal DXTextureView DirectX12() + internal DXHeap DirectX12() { - return (DXTextureView)textureView; + return (DXHeap)heap; } } - extension(Sampler sampler) + extension(MeshShadingPipeline meshShadingPipeline) { - internal DXSampler DirectX12() + internal DXMeshShadingPipeline DirectX12() { - return (DXSampler)sampler; + return (DXMeshShadingPipeline)meshShadingPipeline; } } - extension(Shader shader) + extension(QueryHeap queryHeap) { - internal DXShader DirectX12() + internal DXQueryHeap DirectX12() { - return (DXShader)shader; + return (DXQueryHeap)queryHeap; } } - extension(GraphicsPipeline graphicsPipeline) + extension(Sampler sampler) { - internal DXGraphicsPipeline DirectX12() + internal DXSampler DirectX12() { - return (DXGraphicsPipeline)graphicsPipeline; + return (DXSampler)sampler; } } - extension(ComputePipeline computePipeline) + extension(Shader shader) { - internal DXComputePipeline DirectX12() + internal DXShader DirectX12() { - return (DXComputePipeline)computePipeline; + return (DXShader)shader; } } - extension(MeshShadingPipeline meshShadingPipeline) + extension(SwapChain swapChain) { - internal DXMeshShadingPipeline DirectX12() + internal DXSwapChain DirectX12() { - return (DXMeshShadingPipeline)meshShadingPipeline; + return (DXSwapChain)swapChain; } } - extension(QueryHeap queryHeap) + extension(Texture texture) { - internal DXQueryHeap DirectX12() + internal DXTexture DirectX12() { - return (DXQueryHeap)queryHeap; + return (DXTexture)texture; } } - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + extension(TextureView textureView) { - internal DXBottomLevelAccelerationStructure DirectX12() + internal DXTextureView DirectX12() { - return (DXBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + return (DXTextureView)textureView; } } From 720a9eff3977e3fafc74a25c66963c2c20737112 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 15:11:28 +0800 Subject: [PATCH 200/466] Add BOM to Zenith.NET.slnx file A Byte Order Mark (BOM) was added to the beginning of the Zenith.NET.slnx file. No other changes were made to the file content. This may improve compatibility with some editors and tools that expect a BOM in XML files. --- Zenith.NET.slnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 62db645d..07b969ce 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -1,4 +1,4 @@ - + From acefeabe9d18d99bf082a9e9b5e24b0edc22e44f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 15:21:34 +0800 Subject: [PATCH 201/466] Update Microsoft.WindowsAppSDK to version 2.2.0 Upgraded the Microsoft.WindowsAppSDK NuGet package from 2.1.3 to 2.2.0 in Directory.Packages.props to incorporate the latest fixes and improvements. --- sources/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index d08dc565..9f1f0d20 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -9,7 +9,7 @@ - + From 925ed02ea15f63043f528ffed936fdeb6745f8e3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 10 Jun 2026 15:28:01 +0800 Subject: [PATCH 202/466] Replace SampleDesc constructor with object initializer Updated SampleDesc initialization to use object initializer syntax (SampleDesc = new() { Count = 1 }) instead of the constructor (SampleDesc = new(1)) for improved clarity and consistency. --- .../Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs | 4 ++-- sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs | 2 +- sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs index c006a134..8a733f82 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs @@ -30,7 +30,7 @@ public D3DTexture(uint width, uint height) Width = width, Height = height, Format = ColorFormat(), - SampleDesc = new(1), + SampleDesc = new() { Count = 1 }, BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = 3, Scaling = Scaling.Stretch, @@ -46,7 +46,7 @@ public D3DTexture(uint width, uint height) MipLevels = 1, ArraySize = 1, Format = ColorFormat(), - SampleDesc = new(1), + SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs index 2e3c411d..841670a9 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs @@ -51,7 +51,7 @@ public D3DTexture(uint width, uint height, D3DImage image) MipLevels = 1, ArraySize = 1, Format = ColorFormat(), - SampleDesc = new(1), + SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 57994049..53a61e15 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -135,7 +135,7 @@ public D3DTexture(uint width, uint height) Width = width, Height = height, Format = ColorFormat(), - SampleDesc = new(1), + SampleDesc = new() { Count = 1 }, BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = 3, Scaling = Scaling.Stretch, @@ -151,7 +151,7 @@ public D3DTexture(uint width, uint height) MipLevels = 1, ArraySize = 1, Format = ColorFormat(), - SampleDesc = new(1), + SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; From fe61e44dbfc4c2c4d721db3e5535efa0dbf05ad1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 12 Jun 2026 22:00:49 +0800 Subject: [PATCH 203/466] Initial Metal backend integration for Zenith.NET Adds core Metal backend classes and stubs, including MTLGraphicsContext, MTLCommandQueue, and MTLCapabilities. Implements resource management, device capability queries, and basic command queue operations to establish groundwork for Metal support. --- sources/Zenith.NET.Metal/Extensions.cs | 68 +++++++++++++++++++ sources/Zenith.NET.Metal/MTLCapabilities.cs | 12 ++++ sources/Zenith.NET.Metal/MTLCommandQueue.cs | 59 ++++++++++++++++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 56 ++++++++++++++- 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLCapabilities.cs create mode 100644 sources/Zenith.NET.Metal/MTLCommandQueue.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index c44f7770..684b267e 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -25,4 +25,72 @@ internal void Success() } } } + + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + { + } + + extension(Buffer buffer) + { + } + + extension(BufferView bufferView) + { + } + + extension(CommandBuffer commandBuffer) + { + } + + extension(CommandQueue commandQueue) + { + internal MTLCommandQueue Metal() + { + return (MTLCommandQueue)commandQueue; + } + } + + extension(ComputePipeline computePipeline) + { + } + + extension(GraphicsPipeline graphicsPipeline) + { + } + + extension(Heap heap) + { + } + + extension(MeshShadingPipeline meshShadingPipeline) + { + } + + extension(QueryHeap queryHeap) + { + } + + extension(Sampler sampler) + { + } + + extension(Shader shader) + { + } + + extension(SwapChain swapChain) + { + } + + extension(Texture texture) + { + } + + extension(TextureView textureView) + { + } + + extension(TopLevelAccelerationStructure topLevelAccelerationStructure) + { + } } \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLCapabilities.cs b/sources/Zenith.NET.Metal/MTLCapabilities.cs new file mode 100644 index 00000000..89bd8281 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLCapabilities.cs @@ -0,0 +1,12 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLCapabilities(MTLGraphicsContext context) : Capabilities +{ + public override string DeviceName { get; } = context.Device.Name; + + public override bool RayTracingSupported { get; } = context.Device.SupportsRaytracingFromRender; + + public override bool MeshShadingSupported { get; } = context.Device.SupportsFamily(MTLGPUFamily.Apple7); +} diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs new file mode 100644 index 00000000..6395ef5b --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -0,0 +1,59 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type, MTL4CommandQueue commandQueue) : CommandQueue(context, type) +{ + public MTL4CommandQueue CommandQueue = commandQueue; + + public MTLSharedEvent Event = context.Device.MakeSharedEvent(); + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override ulong GetCompletedValue() + { + return Event.SignaledValue; + } + + protected override CommandBuffer CreateCommandBuffer() + { + throw new NotImplementedException(); + } + + protected override void SignalImpl(ulong signalValue) + { + CommandQueue.SignalEvent(Event, signalValue); + } + + protected override void SubmitImpl(CommandBuffer commandBuffer) + { + throw new NotImplementedException(); + } + + protected override void WaitImpl(ulong waitValue) + { + Event.Wait(waitValue, ulong.MaxValue); + } + + protected override void WaitImpl(ReadOnlySpan submissions) + { + foreach (CommandSubmission submission in submissions) + { + CommandQueue.WaitForEvent(submission.Queue.Metal().Event, submission.Value); + } + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + base.Destroy(); + + Event.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 7f9e8ca1..0001454e 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -4,7 +4,29 @@ namespace Zenith.NET.Metal; internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Metal, useValidationLayer) { - public MTLDevice Device { get; } = MTLDevice.CreateSystemDefaultDevice(); + public MTLDevice Device = MTLDevice.CreateSystemDefaultDevice(); + + public MTL4Compiler Compiler = MTL4Compiler.Null; + + public MTLResidencySet ResidencySet = MTLResidencySet.Null; + + public MTL4CommandQueue GraphicsCommandQueue = MTL4CommandQueue.Null; + + public MTL4CommandQueue ComputeCommandQueue = MTL4CommandQueue.Null; + + public MTL4CommandQueue CopyCommandQueue = MTL4CommandQueue.Null; + + public void AddResidency(MTLAllocation allocation) + { + ResidencySet.AddAllocation(allocation); + ResidencySet.Commit(); + } + + public void RemoveResidency(MTLAllocation allocation) + { + ResidencySet.RemoveAllocation(allocation); + ResidencySet.Commit(); + } public override nint GetNativeObject(NativeObjectType type) { @@ -18,7 +40,31 @@ protected override void Initialize(bool useValidationLayer, out CommandQueue copyQueue, out ValidationLayer? validationLayer) { - throw new NotImplementedException(); + if (!Device.SupportsFamily(MTLGPUFamily.Metal4)) + { + throw new NotSupportedException("Metal 4 is required but not supported by this device."); + } + + Compiler = Device.MakeCompiler(new(), out NSError error); + error.Success(); + + ResidencySet = Device.MakeResidencySet(new(), out error); + error.Success(); + + GraphicsCommandQueue = Device.MakeMTL4CommandQueue(); + GraphicsCommandQueue.AddResidencySet(ResidencySet); + + ComputeCommandQueue = Device.MakeMTL4CommandQueue(); + ComputeCommandQueue.AddResidencySet(ResidencySet); + + CopyCommandQueue = Device.MakeMTL4CommandQueue(); + CopyCommandQueue.AddResidencySet(ResidencySet); + + capabilities = new MTLCapabilities(this); + graphicsQueue = new MTLCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); + computeQueue = new MTLCommandQueue(this, CommandQueueType.Compute, ComputeCommandQueue); + copyQueue = new MTLCommandQueue(this, CommandQueueType.Copy, CopyCommandQueue); + validationLayer = null; } protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) @@ -95,6 +141,12 @@ protected override void Destroy() { base.Destroy(); + CopyCommandQueue.Dispose(); + ComputeCommandQueue.Dispose(); + GraphicsCommandQueue.Dispose(); + + ResidencySet.Dispose(); + Compiler.Dispose(); Device.Dispose(); } } From 6b750cf5c3c85ddf370388af57b05efae2792b80 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 12 Jun 2026 22:05:54 +0800 Subject: [PATCH 204/466] Update Microsoft.Maui.Controls to version 10.0.71 Upgraded the Microsoft.Maui.Controls NuGet package from 10.0.70 to 10.0.71 in Directory.Packages.props to include the latest fixes and improvements. --- sources/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 9f1f0d20..adb64224 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -8,7 +8,7 @@ - + From f5148a7fb27133b12820d1e97cd368c1cc07c456 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 11:20:26 +0800 Subject: [PATCH 205/466] Add Metal buffer and buffer view support Implemented MTLBuffer and MTLBufferView for Metal backend, including resource creation, mapping, and residency management. Updated MTLGraphicsContext to use these classes. Added MTLFormats for residency mapping, extension methods for Metal casting, and a Vulkan formats placeholder. Updated Usings.cs for type aliasing. --- sources/Zenith.NET.Metal/Extensions.cs | 8 +++ sources/Zenith.NET.Metal/MTLBuffer.cs | 57 +++++++++++++++++++ sources/Zenith.NET.Metal/MTLBufferView.cs | 23 ++++++++ sources/Zenith.NET.Metal/MTLFormats.cs | 17 ++++++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 4 +- sources/Zenith.NET.Metal/Usings.cs | 2 +- sources/Zenith.NET.Vulkan/VKFormats.cs | 5 ++ 7 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLBuffer.cs create mode 100644 sources/Zenith.NET.Metal/MTLBufferView.cs create mode 100644 sources/Zenith.NET.Metal/MTLFormats.cs create mode 100644 sources/Zenith.NET.Vulkan/VKFormats.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 684b267e..1a58dc8b 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -32,10 +32,18 @@ internal void Success() extension(Buffer buffer) { + internal MTLBuffer Metal() + { + return (MTLBuffer)buffer; + } } extension(BufferView bufferView) { + internal MTLBufferView Metal() + { + return (MTLBufferView)bufferView; + } } extension(CommandBuffer commandBuffer) diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs new file mode 100644 index 00000000..a48c18e9 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -0,0 +1,57 @@ +namespace Zenith.NET.Metal; + +internal class MTLBuffer : Buffer +{ + public MtlBuffer Buffer; + + public MTLBuffer(MTLGraphicsContext context, BufferDesc desc) : base(context, desc) + { + Buffer = context.Device.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency)); + + context.AddResidency(Buffer); + + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); + } + + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public MTLBufferView View { get; } + + public override ResourceHandle ConstantHandle => View.ConstantHandle; + + public override ResourceHandle StorageReadOnlyHandle => View.StorageReadOnlyHandle; + + public override ResourceHandle StorageReadWriteHandle => View.StorageReadWriteHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + public override MappedMemory Map() + { + return new(Buffer.Contents(), Desc.SizeInBytes); + } + + public override void Unmap() + { + } + + protected override void SetResourceName(string name) + { + Buffer.Label = name; + } + + protected override void Destroy() + { + Context.RemoveResidency(Buffer); + + View.Dispose(); + Buffer.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs new file mode 100644 index 00000000..88c7a796 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -0,0 +1,23 @@ +namespace Zenith.NET.Metal; + +internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) +{ + public override ResourceHandle ConstantHandle { get; } + + public override ResourceHandle StorageReadOnlyHandle { get; } + + public override ResourceHandle StorageReadWriteHandle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + } +} diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs new file mode 100644 index 00000000..43b83dfb --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -0,0 +1,17 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal static class MTLFormats +{ + public static MTLResourceOptions Metal(MemoryResidency memoryResidency) + { + return memoryResidency switch + { + MemoryResidency.GpuOnly => MTLResourceOptions.StorageModePrivate, + MemoryResidency.CpuReadOnly => MTLResourceOptions.StorageModeShared, + MemoryResidency.CpuWriteOnly => MTLResourceOptions.StorageModeShared | MTLResourceOptions.CPUCacheModeWriteCombined, + _ => default + }; + } +} diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 0001454e..41c358ef 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -89,12 +89,12 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) protected override Buffer CreateBufferImpl(BufferDesc desc) { - throw new NotImplementedException(); + return new MTLBuffer(this, desc); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) { - throw new NotImplementedException(); + return new MTLBufferView(this, desc); } protected override Texture CreateTextureImpl(TextureDesc desc) diff --git a/sources/Zenith.NET.Metal/Usings.cs b/sources/Zenith.NET.Metal/Usings.cs index 5f282702..987ca098 100644 --- a/sources/Zenith.NET.Metal/Usings.cs +++ b/sources/Zenith.NET.Metal/Usings.cs @@ -1 +1 @@ - \ No newline at end of file +global using MtlBuffer = Metal.NET.MTLBuffer; \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs new file mode 100644 index 00000000..281d17b9 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -0,0 +1,5 @@ +namespace Zenith.NET.Vulkan; + +internal static class VKFormats +{ +} From eb8e850c10ca92bbf73bad50e9c162af9725fc73 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 11:46:05 +0800 Subject: [PATCH 206/466] Refactor buffer mapping to use nint pointers directly Removed MappedMemory struct and updated Buffer.Map() and all related code to use nint pointers instead of a wrapper struct. Simplified buffer access and improved consistency across buffer operations. --- .../DXBottomLevelAccelerationStructure.cs | 4 ++-- sources/Zenith.NET.DirectX12/DXBuffer.cs | 4 ++-- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 4 ++-- .../DXTopLevelAccelerationStructure.cs | 4 ++-- sources/Zenith.NET.Metal/MTLBuffer.cs | 4 ++-- sources/Zenith.NET/Buffer.cs | 10 +++++----- sources/Zenith.NET/Structs/MappedMemory.cs | 8 -------- sources/Zenith.NET/Structs/TransferLayout.cs | 8 ++++---- 8 files changed, 19 insertions(+), 27 deletions(-) delete mode 100644 sources/Zenith.NET/Structs/MappedMemory.cs diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 59a604a3..dd9d9807 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -123,9 +123,9 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct { uint geometryCount = (uint)desc.Geometries.Length; - MappedMemory mappedMemory = Transform.Map(); + nint pointer = Transform.Map(); - Matrix3X4* transforms = (Matrix3X4*)mappedMemory.Pointer; + Matrix3X4* transforms = (Matrix3X4*)pointer; RaytracingGeometryDesc* geometries = (RaytracingGeometryDesc*)ZenithMarshal.Allocate(scope, geometryCount); for (uint i = 0; i < geometryCount; i++) { diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 44c69eca..9cf81f2c 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -92,12 +92,12 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - public override MappedMemory Map() + public override nint Map() { void* pointer; Resource.Map(0, default(DxRange*), &pointer).Success(); - return new((nint)pointer, Desc.SizeInBytes); + return (nint)pointer; } public override void Unmap() diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index f11a665f..b0613f09 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -33,9 +33,9 @@ public override nint GetNativeObject(NativeObjectType type) protected override void GetResultsImpl(Span results, uint startIndex) { - MappedMemory mappedMemory = Buffer.Map(); + nint pointer = Buffer.Map(); - new Span((void*)(mappedMemory.Pointer + (sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + new Span((void*)(pointer + (sizeof(ulong) * startIndex)), results.Length).CopyTo(results); Buffer.Unmap(); } diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 240298f0..5b95643a 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -133,9 +133,9 @@ private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytrac { uint instanceCount = (uint)desc.Instances.Length; - MappedMemory mappedMemory = Instance.Map(); + nint pointer = Instance.Map(); - RaytracingInstanceDesc* instances = (RaytracingInstanceDesc*)mappedMemory.Pointer; + RaytracingInstanceDesc* instances = (RaytracingInstanceDesc*)pointer; for (uint i = 0; i < instanceCount; i++) { RayTracingInstance instance = desc.Instances[i]; diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs index a48c18e9..0913ab38 100644 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -33,9 +33,9 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - public override MappedMemory Map() + public override nint Map() { - return new(Buffer.Contents(), Desc.SizeInBytes); + return Buffer.Contents(); } public override void Unmap() diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 76075fb4..f1a01ad9 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -12,7 +12,7 @@ public abstract class Buffer(GraphicsContext context, BufferDesc desc) : Graphic public abstract ResourceHandle StorageReadWriteHandle { get; } - public abstract MappedMemory Map(); + public abstract nint Map(); public abstract void Unmap(); @@ -20,11 +20,11 @@ public void Upload(uint offsetInBytes, BufferData data) { if (desc.Residency is MemoryResidency.CpuWriteOnly) { - MappedMemory mappedMemory = Map(); + nint pointer = Map(); unsafe { - new ReadOnlySpan((void*)data.Pointer, (int)data.SizeInBytes).CopyTo(new((void*)(mappedMemory.Pointer + offsetInBytes), (int)data.SizeInBytes)); + new ReadOnlySpan((void*)data.Pointer, (int)data.SizeInBytes).CopyTo(new((void*)(pointer + offsetInBytes), (int)data.SizeInBytes)); } Unmap(); @@ -42,11 +42,11 @@ public void Download(uint offsetInBytes, BufferData data) { if (desc.Residency is MemoryResidency.CpuReadOnly) { - MappedMemory mappedMemory = Map(); + nint pointer = Map(); unsafe { - new ReadOnlySpan((void*)(mappedMemory.Pointer + offsetInBytes), (int)data.SizeInBytes).CopyTo(new((void*)data.Pointer, (int)data.SizeInBytes)); + new ReadOnlySpan((void*)(pointer + offsetInBytes), (int)data.SizeInBytes).CopyTo(new((void*)data.Pointer, (int)data.SizeInBytes)); } Unmap(); diff --git a/sources/Zenith.NET/Structs/MappedMemory.cs b/sources/Zenith.NET/Structs/MappedMemory.cs deleted file mode 100644 index d9334119..00000000 --- a/sources/Zenith.NET/Structs/MappedMemory.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public readonly struct MappedMemory(nint pointer, uint sizeInBytes) -{ - public readonly nint Pointer = pointer; - - public readonly uint SizeInBytes = sizeInBytes; -} diff --git a/sources/Zenith.NET/Structs/TransferLayout.cs b/sources/Zenith.NET/Structs/TransferLayout.cs index 6e12611b..ddbcc5f9 100644 --- a/sources/Zenith.NET/Structs/TransferLayout.cs +++ b/sources/Zenith.NET/Structs/TransferLayout.cs @@ -14,13 +14,13 @@ internal struct TransferLayout public readonly void Upload(Buffer buffer) { - MappedMemory mappedMemory = buffer.Map(); + nint pointer = buffer.Map(); unsafe { for (uint row = 0; row < Rows; row++) { - new ReadOnlySpan((void*)(Pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(mappedMemory.Pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); + new ReadOnlySpan((void*)(Pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); } } @@ -29,13 +29,13 @@ public readonly void Upload(Buffer buffer) public readonly void Download(Buffer buffer) { - MappedMemory mappedMemory = buffer.Map(); + nint pointer = buffer.Map(); unsafe { for (uint row = 0; row < Rows; row++) { - new ReadOnlySpan((void*)(mappedMemory.Pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(Pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); + new ReadOnlySpan((void*)(pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(Pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); } } From 875744e987d0495d507dedb59f47f3cf78e7830a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 12:29:56 +0800 Subject: [PATCH 207/466] Add ToResourceHandle extension and update buffer handles Introduce ToResourceHandle(ulong) extension to convert GPU addresses to ResourceHandle. Refactor MTLBufferView to compute handle properties on demand using the new extension for improved consistency and maintainability. --- sources/Zenith.NET.Metal/Extensions.cs | 8 ++++++++ sources/Zenith.NET.Metal/MTLBufferView.cs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 1a58dc8b..b4266b60 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -26,6 +26,14 @@ internal void Success() } } + extension(ulong value) + { + internal ResourceHandle ToResourceHandle() + { + return new((uint)value, (uint)(value >> 32)); + } + } + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { } diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs index 88c7a796..cf6fca09 100644 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -2,11 +2,11 @@ internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) { - public override ResourceHandle ConstantHandle { get; } + public override ResourceHandle ConstantHandle => (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToResourceHandle(); - public override ResourceHandle StorageReadOnlyHandle { get; } + public override ResourceHandle StorageReadOnlyHandle => (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToResourceHandle(); - public override ResourceHandle StorageReadWriteHandle { get; } + public override ResourceHandle StorageReadWriteHandle => (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToResourceHandle(); public override nint GetNativeObject(NativeObjectType type) { From 07b7ed9fecdd4665b33b8346ba7300d206032522 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 13:00:41 +0800 Subject: [PATCH 208/466] Add Metal texture and view support, update buffer handling Introduce MTLTexture and MTLTextureView for Metal texture resource management, including creation, residency, and view logic. Add Metal-specific casting helpers in Extensions. Stub out Metal format conversion methods in MTLFormats. Update MTLGraphicsContext to create Metal textures and views. Track GPU addresses in MTLBuffer and MTLBufferView. Update usings for new types. --- sources/Zenith.NET.Metal/Extensions.cs | 8 +++ sources/Zenith.NET.Metal/MTLBuffer.cs | 6 +- sources/Zenith.NET.Metal/MTLBufferView.cs | 8 ++- sources/Zenith.NET.Metal/MTLFormats.cs | 26 +++++++- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 4 +- sources/Zenith.NET.Metal/MTLTexture.cs | 62 +++++++++++++++++++ sources/Zenith.NET.Metal/MTLTextureView.cs | 28 +++++++++ sources/Zenith.NET.Metal/Usings.cs | 3 +- 8 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLTexture.cs create mode 100644 sources/Zenith.NET.Metal/MTLTextureView.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index b4266b60..f657458b 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -100,10 +100,18 @@ internal MTLCommandQueue Metal() extension(Texture texture) { + internal MTLTexture Metal() + { + return (MTLTexture)texture; + } } extension(TextureView textureView) { + internal MTLTextureView Metal() + { + return (MTLTextureView)textureView; + } } extension(TopLevelAccelerationStructure topLevelAccelerationStructure) diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs index 0913ab38..2b3bfca0 100644 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -4,11 +4,13 @@ internal class MTLBuffer : Buffer { public MtlBuffer Buffer; + public ulong GpuAddress; + public MTLBuffer(MTLGraphicsContext context, BufferDesc desc) : base(context, desc) { - Buffer = context.Device.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency)); + context.AddResidency(Buffer = context.Device.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency))); - context.AddResidency(Buffer); + GpuAddress = Buffer.GpuAddress; View = new(context, new() { diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs index cf6fca09..fde672e5 100644 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -2,11 +2,13 @@ internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) { - public override ResourceHandle ConstantHandle => (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToResourceHandle(); + public ResourceHandle ResourceHandle = (desc.Buffer.Metal().GpuAddress + desc.OffsetInBytes).ToResourceHandle(); - public override ResourceHandle StorageReadOnlyHandle => (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToResourceHandle(); + public override ResourceHandle ConstantHandle => ResourceHandle; - public override ResourceHandle StorageReadWriteHandle => (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToResourceHandle(); + public override ResourceHandle StorageReadOnlyHandle => ResourceHandle; + + public override ResourceHandle StorageReadWriteHandle => ResourceHandle; public override nint GetNativeObject(NativeObjectType type) { diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 43b83dfb..9189f716 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -8,10 +8,30 @@ public static MTLResourceOptions Metal(MemoryResidency memoryResidency) { return memoryResidency switch { - MemoryResidency.GpuOnly => MTLResourceOptions.StorageModePrivate, - MemoryResidency.CpuReadOnly => MTLResourceOptions.StorageModeShared, - MemoryResidency.CpuWriteOnly => MTLResourceOptions.StorageModeShared | MTLResourceOptions.CPUCacheModeWriteCombined, + MemoryResidency.GpuOnly => MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, + MemoryResidency.CpuReadOnly => MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModeShared | MTLResourceOptions.HazardTrackingModeUntracked, + MemoryResidency.CpuWriteOnly => MTLResourceOptions.CPUCacheModeWriteCombined | MTLResourceOptions.StorageModeShared | MTLResourceOptions.HazardTrackingModeUntracked, _ => default }; } + + internal static MTLTextureType Metal(TextureType type) + { + throw new NotImplementedException(); + } + + internal static MTLPixelFormat Metal(PixelFormat format) + { + throw new NotImplementedException(); + } + + internal static nuint Metal(SampleCount sampleCount) + { + throw new NotImplementedException(); + } + + internal static MTLTextureUsage Metal(TextureUsages usages) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 41c358ef..2e96fa7e 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -99,12 +99,12 @@ protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc) { - throw new NotImplementedException(); + return new MTLTexture(this, desc); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { - throw new NotImplementedException(); + return new MTLTextureView(this, desc); } protected override Sampler CreateSamplerImpl(SamplerDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs new file mode 100644 index 00000000..ded55d90 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -0,0 +1,62 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLTexture : Texture +{ + public MtlTexture Texture; + + public MTLTexture(MTLGraphicsContext context, TextureDesc desc) : base(context, desc) + { + MTLTextureDescriptor descriptor = new() + { + TextureType = MTLFormats.Metal(desc.Type), + PixelFormat = MTLFormats.Metal(desc.Format), + Width = desc.Width, + Height = desc.Height, + Depth = desc.Depth, + MipmapLevelCount = desc.MipLevels, + SampleCount = MTLFormats.Metal(desc.SampleCount), + ArrayLength = desc.ArrayLayers, + ResourceOptions = MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, + Usage = MTLFormats.Metal(desc.Usages), + AllowGPUOptimizedContents = true + }; + + context.AddResidency(Texture = context.Device.MakeTexture(descriptor)); + + View = new(context, new() + { + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) + }); + } + + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public MTLTextureView View { get; } + + public override ResourceHandle SampledHandle => View.SampledHandle; + + public override ResourceHandle StorageHandle => View.StorageHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + Texture.Label = name; + } + + protected override void Destroy() + { + Context.RemoveResidency(Texture); + + View.Dispose(); + Texture.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs new file mode 100644 index 00000000..3585afd2 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLTextureView.cs @@ -0,0 +1,28 @@ +namespace Zenith.NET.Metal; + +internal class MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) +{ + public MtlTexture Texture = desc.Texture.Metal().Texture.MakeTextureView(MTLFormats.Metal(desc.Format), + MTLFormats.Metal(desc.Type), + new(desc.Range.BaseMipLevel, desc.Range.LevelCount), + new(desc.Range.BaseArrayLayer, desc.Range.LayerCount)); + + public override ResourceHandle SampledHandle => Texture.GpuResourceID.Impl.ToResourceHandle(); + + public override ResourceHandle StorageHandle => Texture.GpuResourceID.Impl.ToResourceHandle(); + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + Texture.Label = name; + } + + protected override void Destroy() + { + Texture.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/Usings.cs b/sources/Zenith.NET.Metal/Usings.cs index 987ca098..62393445 100644 --- a/sources/Zenith.NET.Metal/Usings.cs +++ b/sources/Zenith.NET.Metal/Usings.cs @@ -1 +1,2 @@ -global using MtlBuffer = Metal.NET.MTLBuffer; \ No newline at end of file +global using MtlBuffer = Metal.NET.MTLBuffer; +global using MtlTexture = Metal.NET.MTLTexture; \ No newline at end of file From f1339962808d8ad74f70a4d8afe2d461aca217f1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 13:03:00 +0800 Subject: [PATCH 209/466] Implement Metal size/alignment and refactor descriptor Replaced NotImplementedException in MTLGraphicsContext.GetSizeAndAlignmentImpl with Metal API calls for buffer and texture size/alignment. Refactored MTLTextureDescriptor construction into a static Descriptor method in MTLTexture and updated the constructor to use it. --- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 8 +++-- sources/Zenith.NET.Metal/MTLTexture.cs | 35 ++++++++++--------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 2e96fa7e..92599edb 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -79,12 +79,16 @@ protected override Heap CreateHeapImpl(HeapDesc desc) protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - throw new NotImplementedException(); + MTLSizeAndAlign sizeAndAlign = Device.HeapBufferSizeAndAlign(desc.SizeInBytes, MTLFormats.Metal(desc.Residency)); + + return new(sizeAndAlign.Size, sizeAndAlign.Align); } protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { - throw new NotImplementedException(); + MTLSizeAndAlign sizeAndAlign = Device.HeapTextureSizeAndAlign(MTLTexture.Descriptor(desc)); + + return new(sizeAndAlign.Size, sizeAndAlign.Align); } protected override Buffer CreateBufferImpl(BufferDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs index ded55d90..bb5f7eb4 100644 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -8,22 +8,7 @@ internal class MTLTexture : Texture public MTLTexture(MTLGraphicsContext context, TextureDesc desc) : base(context, desc) { - MTLTextureDescriptor descriptor = new() - { - TextureType = MTLFormats.Metal(desc.Type), - PixelFormat = MTLFormats.Metal(desc.Format), - Width = desc.Width, - Height = desc.Height, - Depth = desc.Depth, - MipmapLevelCount = desc.MipLevels, - SampleCount = MTLFormats.Metal(desc.SampleCount), - ArrayLength = desc.ArrayLayers, - ResourceOptions = MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, - Usage = MTLFormats.Metal(desc.Usages), - AllowGPUOptimizedContents = true - }; - - context.AddResidency(Texture = context.Device.MakeTexture(descriptor)); + context.AddResidency(Texture = context.Device.MakeTexture(Descriptor(desc))); View = new(context, new() { @@ -59,4 +44,22 @@ protected override void Destroy() View.Dispose(); Texture.Dispose(); } + + public static MTLTextureDescriptor Descriptor(TextureDesc desc) + { + return new() + { + TextureType = MTLFormats.Metal(desc.Type), + PixelFormat = MTLFormats.Metal(desc.Format), + Width = desc.Width, + Height = desc.Height, + Depth = desc.Depth, + MipmapLevelCount = desc.MipLevels, + SampleCount = MTLFormats.Metal(desc.SampleCount), + ArrayLength = desc.ArrayLayers, + ResourceOptions = MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, + Usage = MTLFormats.Metal(desc.Usages), + AllowGPUOptimizedContents = true + }; + } } From 90d98920039a41705378fd744b850dbebc040c99 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 13:23:35 +0800 Subject: [PATCH 210/466] Refactor Metal type conversions and ResourceHandle cast Refactor ulong to ResourceHandle conversion to use Unsafe.As for direct casting. Change Metal type conversion methods in MTLFormats.cs from internal to public and update parameter names for clarity. --- sources/Zenith.NET.Metal/Extensions.cs | 3 ++- sources/Zenith.NET.Metal/MTLFormats.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index f657458b..f661adeb 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Runtime.CompilerServices; using Metal.NET; namespace Zenith.NET.Metal; @@ -30,7 +31,7 @@ internal void Success() { internal ResourceHandle ToResourceHandle() { - return new((uint)value, (uint)(value >> 32)); + return Unsafe.As(ref value); } } diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 9189f716..0fa672f3 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -15,22 +15,22 @@ public static MTLResourceOptions Metal(MemoryResidency memoryResidency) }; } - internal static MTLTextureType Metal(TextureType type) + public static MTLTextureType Metal(TextureType textureType) { throw new NotImplementedException(); } - internal static MTLPixelFormat Metal(PixelFormat format) + public static MTLPixelFormat Metal(PixelFormat pixelFormat) { throw new NotImplementedException(); } - internal static nuint Metal(SampleCount sampleCount) + public static nuint Metal(SampleCount sampleCount) { throw new NotImplementedException(); } - internal static MTLTextureUsage Metal(TextureUsages usages) + public static MTLTextureUsage Metal(TextureUsages textureUsages) { throw new NotImplementedException(); } From f7bf86dd796521be64bf3f8fed9f68c7a4f4a62e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 15:33:00 +0800 Subject: [PATCH 211/466] Refactor numeric types and clarify array layer logic Replaced float with double for text positioning in ZenithView for improved precision. Updated Math.Clamp to use double bounds in FrameScheduler. Changed ArrayLayers multiplication order in TextureDesc for clarity. --- sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs | 4 ++-- sources/Views/Zenith.NET.Views.WPF/ZenithView.cs | 4 ++-- sources/Views/Zenith.NET.Views/FrameScheduler.cs | 2 +- sources/Zenith.NET/Structs/TextureDesc.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index 7fc4ab17..195ab007 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs @@ -71,8 +71,8 @@ public override void Render(DrawingContext context) fontSize * dpi, new SolidColorBrush(Colors.White) { Opacity = 0.98 }); - float x = (float)(Bounds.Width - mainText.Width) / 2; - float y = (float)(Bounds.Height - mainText.Height) / 2; + double x = (Bounds.Width - mainText.Width) / 2.0; + double y = (Bounds.Height - mainText.Height) / 2.0; context.DrawText(shadowText, new(x + 1.0, y + 1.0)); context.DrawText(mainText, new(x, y)); diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index fe554e19..294d4e7f 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -79,8 +79,8 @@ protected override void OnRender(DrawingContext drawingContext) new SolidColorBrush(Colors.White) { Opacity = 0.98 }, dpi); - float x = (float)(ActualWidth - mainText.Width) / 2; - float y = (float)(ActualHeight - mainText.Height) / 2; + double x = (ActualWidth - mainText.Width) / 2.0; + double y = (ActualHeight - mainText.Height) / 2.0; drawingContext.DrawText(shadowText, new(x + 1.0, y + 1.0)); drawingContext.DrawText(mainText, new(x, y)); diff --git a/sources/Views/Zenith.NET.Views/FrameScheduler.cs b/sources/Views/Zenith.NET.Views/FrameScheduler.cs index 2130bcf1..161e2fa3 100644 --- a/sources/Views/Zenith.NET.Views/FrameScheduler.cs +++ b/sources/Views/Zenith.NET.Views/FrameScheduler.cs @@ -38,7 +38,7 @@ static FrameScheduler() double memoryThroughputMBps = iterations * bufferSize / (1024.0 * 1024.0) / stopwatch.Elapsed.TotalSeconds; - double performanceScore = Math.Clamp(memoryThroughputMBps / 5000.0, 0, 1); + double performanceScore = Math.Clamp(memoryThroughputMBps / 5000.0, 0.0, 1.0); Interval = TimeSpan.FromSeconds(double.Lerp(maxInterval, minInterval, performanceScore)); } diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index b7bd69b5..e26d07a5 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -126,7 +126,7 @@ public static TextureDesc TextureCubeArray(PixelFormat format, uint size, uint c Height = size, Depth = 1, MipLevels = mipLevels, - ArrayLayers = 6 * cubeCount, + ArrayLayers = cubeCount * 6, SampleCount = SampleCount.Count1, Usages = TextureUsages.Sampled | TextureUsages.CopyDst }; From 183aff681793adafb95638fef79c1b4c1b4654df Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 15:56:39 +0800 Subject: [PATCH 212/466] Refactor residency management; add Metal heap support Refactor resource residency by renaming AddResidency/RemoveResidency to Register/Unregister in MTLGraphicsContext and updating all usages. MTLBuffer and MTLTexture now register/unregister resources and create their View objects immediately. Introduce MTLHeap class for Metal heap abstraction, enabling buffer and texture creation from heaps. Update global usings and implement CreateHeapImpl to return MTLHeap. --- sources/Zenith.NET.Metal/MTLBuffer.cs | 18 ++++++- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 6 +-- sources/Zenith.NET.Metal/MTLHeap.cs | 49 +++++++++++++++++++ sources/Zenith.NET.Metal/MTLTexture.cs | 17 ++++++- sources/Zenith.NET.Metal/Usings.cs | 3 +- 5 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLHeap.cs diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs index 2b3bfca0..c92927a7 100644 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -8,7 +8,21 @@ internal class MTLBuffer : Buffer public MTLBuffer(MTLGraphicsContext context, BufferDesc desc) : base(context, desc) { - context.AddResidency(Buffer = context.Device.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency))); + context.Register(Buffer = context.Device.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency))); + + GpuAddress = Buffer.GpuAddress; + + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); + } + + public MTLBuffer(MTLGraphicsContext context, BufferDesc desc, MtlBuffer buffer) : base(context, desc) + { + context.Register(Buffer = buffer); GpuAddress = Buffer.GpuAddress; @@ -51,7 +65,7 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.RemoveResidency(Buffer); + Context.Unregister(Buffer); View.Dispose(); Buffer.Dispose(); diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 92599edb..d906bde3 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -16,13 +16,13 @@ internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(Gra public MTL4CommandQueue CopyCommandQueue = MTL4CommandQueue.Null; - public void AddResidency(MTLAllocation allocation) + public void Register(MTLAllocation allocation) { ResidencySet.AddAllocation(allocation); ResidencySet.Commit(); } - public void RemoveResidency(MTLAllocation allocation) + public void Unregister(MTLAllocation allocation) { ResidencySet.RemoveAllocation(allocation); ResidencySet.Commit(); @@ -74,7 +74,7 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) protected override Heap CreateHeapImpl(HeapDesc desc) { - throw new NotImplementedException(); + return new MTLHeap(this, desc); } protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs new file mode 100644 index 00000000..00b12040 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLHeap.cs @@ -0,0 +1,49 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLHeap : Heap +{ + public MtlHeap Heap; + + public MTLHeap(MTLGraphicsContext context, HeapDesc desc) : base(context, desc) + { + MTLHeapDescriptor descriptor = new() + { + Size = (nuint)desc.SizeInBytes, + ResourceOptions = MTLFormats.Metal(desc.Residency), + Type = MTLHeapType.Placement + }; + + context.Register(Heap = context.Device.MakeHeap(descriptor)); + } + + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) + { + return new MTLBuffer(Context, desc, Heap.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency), (nuint)offsetInBytes)); + } + + protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) + { + return new MTLTexture(Context, desc, Heap.MakeTexture(MTLTexture.Descriptor(desc), (nuint)offsetInBytes)); + } + + protected override void SetResourceName(string name) + { + Heap.Label = name; + } + + protected override void Destroy() + { + Context.Unregister(Heap); + + Heap.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs index bb5f7eb4..0653c08f 100644 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -8,7 +8,20 @@ internal class MTLTexture : Texture public MTLTexture(MTLGraphicsContext context, TextureDesc desc) : base(context, desc) { - context.AddResidency(Texture = context.Device.MakeTexture(Descriptor(desc))); + context.Register(Texture = context.Device.MakeTexture(Descriptor(desc))); + + View = new(context, new() + { + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) + }); + } + + public MTLTexture(MTLGraphicsContext context, TextureDesc desc, MtlTexture texture) : base(context, desc) + { + context.Register(Texture = texture); View = new(context, new() { @@ -39,7 +52,7 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.RemoveResidency(Texture); + Context.Unregister(Texture); View.Dispose(); Texture.Dispose(); diff --git a/sources/Zenith.NET.Metal/Usings.cs b/sources/Zenith.NET.Metal/Usings.cs index 62393445..fc76a42c 100644 --- a/sources/Zenith.NET.Metal/Usings.cs +++ b/sources/Zenith.NET.Metal/Usings.cs @@ -1,2 +1,3 @@ global using MtlBuffer = Metal.NET.MTLBuffer; -global using MtlTexture = Metal.NET.MTLTexture; \ No newline at end of file +global using MtlHeap = Metal.NET.MTLHeap; +global using MtlTexture = Metal.NET.MTLTexture; From 4012bb133f2c0838a93986dda11951376db9c0a3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 19:42:52 +0800 Subject: [PATCH 213/466] Refactor memory allocation in ZenithMarshal.Scope Introduce Native(uint length) for zeroed allocation using NativeMemory.AllocZeroed. Update Allocate to use this method and improve efficiency. Adjust Native(ReadOnlySpan data) to allocate and copy data without zero-initialization. --- sources/Zenith.NET/ZenithMarshal.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET/ZenithMarshal.cs b/sources/Zenith.NET/ZenithMarshal.cs index a773221f..41536736 100644 --- a/sources/Zenith.NET/ZenithMarshal.cs +++ b/sources/Zenith.NET/ZenithMarshal.cs @@ -9,10 +9,18 @@ public class Scope : DisposableObject { private readonly List pointers = []; - internal nint Native(ReadOnlySpan data) where T : unmanaged + internal nint Native(uint length) where T : unmanaged { - nint pointer = (nint)NativeMemory.Alloc((uint)(sizeof(T) * data.Length)); + nint pointer = (nint)NativeMemory.AllocZeroed((nuint)(sizeof(T) * length)); + + pointers.Add(pointer); + + return pointer; + } + internal nint Native(ReadOnlySpan data) where T : unmanaged + { + nint pointer = (nint)NativeMemory.Alloc((nuint)(sizeof(T) * data.Length)); data.CopyTo(new((void*)pointer, data.Length)); pointers.Add(pointer); @@ -32,7 +40,7 @@ protected override void Destroy() public static nint Allocate(Scope scope, uint length) where T : unmanaged { - return scope.Native(new T[length]); + return scope.Native(length); } public static nint AllocateAndFill(Scope scope, ReadOnlySpan data) where T : unmanaged From 4c52f5d3de3efeb66743f18c8bff8f7accc51def Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 20:16:12 +0800 Subject: [PATCH 214/466] Add MTLSampler and refactor resource handle usage Introduce MTLSampler for Metal sampler state management and update MTLGraphicsContext to use it. Rename ToResourceHandle to ToHandle for consistency. Refactor MTLBufferView and MTLTextureView to use new handle logic. Add stub conversion methods to MTLFormats for sampler enums and perform minor property and initialization cleanups. --- sources/Zenith.NET.Metal/Extensions.cs | 6 ++- sources/Zenith.NET.Metal/MTLBufferView.cs | 8 ++-- sources/Zenith.NET.Metal/MTLFormats.cs | 20 ++++++++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- sources/Zenith.NET.Metal/MTLHeap.cs | 6 +-- sources/Zenith.NET.Metal/MTLSampler.cs | 47 +++++++++++++++++++ sources/Zenith.NET.Metal/MTLTextureView.cs | 22 ++++++--- 7 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLSampler.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index f661adeb..c30708ee 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -29,7 +29,7 @@ internal void Success() extension(ulong value) { - internal ResourceHandle ToResourceHandle() + internal ResourceHandle ToHandle() { return Unsafe.As(ref value); } @@ -89,6 +89,10 @@ internal MTLCommandQueue Metal() extension(Sampler sampler) { + internal MTLSampler Metal() + { + return (MTLSampler)sampler; + } } extension(Shader shader) diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs index fde672e5..aca5a1bf 100644 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -2,13 +2,13 @@ internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) { - public ResourceHandle ResourceHandle = (desc.Buffer.Metal().GpuAddress + desc.OffsetInBytes).ToResourceHandle(); + public ResourceHandle Handle = (desc.Buffer.Metal().GpuAddress + desc.OffsetInBytes).ToHandle(); - public override ResourceHandle ConstantHandle => ResourceHandle; + public override ResourceHandle ConstantHandle => Handle; - public override ResourceHandle StorageReadOnlyHandle => ResourceHandle; + public override ResourceHandle StorageReadOnlyHandle => Handle; - public override ResourceHandle StorageReadWriteHandle => ResourceHandle; + public override ResourceHandle StorageReadWriteHandle => Handle; public override nint GetNativeObject(NativeObjectType type) { diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 0fa672f3..5afe4683 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -34,4 +34,24 @@ public static MTLTextureUsage Metal(TextureUsages textureUsages) { throw new NotImplementedException(); } + + internal static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode minFilter) + { + throw new NotImplementedException(); + } + + internal static MTLSamplerAddressMode Metal(AddressMode addressV) + { + throw new NotImplementedException(); + } + + internal static MTLSamplerBorderColor Metal(BorderColor borderColor) + { + throw new NotImplementedException(); + } + + internal static MTLCompareFunction Metal(CompareOp compareOp) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index d906bde3..4b746bc1 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -113,7 +113,7 @@ protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) protected override Sampler CreateSamplerImpl(SamplerDesc desc) { - throw new NotImplementedException(); + return new MTLSampler(this, desc); } protected override Shader CreateShaderImpl(ShaderDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs index 00b12040..5af72b51 100644 --- a/sources/Zenith.NET.Metal/MTLHeap.cs +++ b/sources/Zenith.NET.Metal/MTLHeap.cs @@ -8,14 +8,12 @@ internal class MTLHeap : Heap public MTLHeap(MTLGraphicsContext context, HeapDesc desc) : base(context, desc) { - MTLHeapDescriptor descriptor = new() + context.Register(Heap = context.Device.MakeHeap(new() { Size = (nuint)desc.SizeInBytes, ResourceOptions = MTLFormats.Metal(desc.Residency), Type = MTLHeapType.Placement - }; - - context.Register(Heap = context.Device.MakeHeap(descriptor)); + })); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; diff --git a/sources/Zenith.NET.Metal/MTLSampler.cs b/sources/Zenith.NET.Metal/MTLSampler.cs new file mode 100644 index 00000000..a738bca8 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLSampler.cs @@ -0,0 +1,47 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLSampler : Sampler +{ + public MTLSamplerState SamplerState; + + public MTLSampler(MTLGraphicsContext context, SamplerDesc desc) : base(context, desc) + { + SamplerState = context.Device.MakeSamplerState(new() + { + MinFilter = MTLFormats.Metal(desc.MinFilter).MinMagFilter, + MagFilter = MTLFormats.Metal(desc.MagFilter).MinMagFilter, + MipFilter = MTLFormats.Metal(desc.MipFilter).MipFilter, + MaxAnisotropy = desc.MaxAnisotropy, + SAddressMode = MTLFormats.Metal(desc.AddressU), + TAddressMode = MTLFormats.Metal(desc.AddressV), + RAddressMode = MTLFormats.Metal(desc.AddressW), + BorderColor = MTLFormats.Metal(desc.BorderColor), + NormalizedCoordinates = true, + LodMinClamp = desc.MinLod, + LodMaxClamp = desc.MaxLod, + LodBias = desc.LodBias, + CompareFunction = MTLFormats.Metal(desc.CompareOp), + SupportArgumentBuffers = true + }); + + Handle = SamplerState.GpuResourceID.Impl.ToHandle(); + } + + public override ResourceHandle Handle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + SamplerState.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs index 3585afd2..18ab6dd0 100644 --- a/sources/Zenith.NET.Metal/MTLTextureView.cs +++ b/sources/Zenith.NET.Metal/MTLTextureView.cs @@ -1,15 +1,23 @@ namespace Zenith.NET.Metal; -internal class MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) +internal class MTLTextureView : TextureView { - public MtlTexture Texture = desc.Texture.Metal().Texture.MakeTextureView(MTLFormats.Metal(desc.Format), - MTLFormats.Metal(desc.Type), - new(desc.Range.BaseMipLevel, desc.Range.LevelCount), - new(desc.Range.BaseArrayLayer, desc.Range.LayerCount)); + public MtlTexture Texture; - public override ResourceHandle SampledHandle => Texture.GpuResourceID.Impl.ToResourceHandle(); + public MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : base(context, desc) + { + Texture = desc.Texture.Metal().Texture.MakeTextureView(MTLFormats.Metal(desc.Format), + MTLFormats.Metal(desc.Type), + new(desc.Range.BaseMipLevel, desc.Range.LevelCount), + new(desc.Range.BaseArrayLayer, desc.Range.LayerCount)); + + SampledHandle = Texture.GpuResourceID.Impl.ToHandle(); + StorageHandle = Texture.GpuResourceID.Impl.ToHandle(); + } + + public override ResourceHandle SampledHandle { get; } - public override ResourceHandle StorageHandle => Texture.GpuResourceID.Impl.ToResourceHandle(); + public override ResourceHandle StorageHandle { get; } public override nint GetNativeObject(NativeObjectType type) { From c722967e392f670eec7120af0987e3cc63a63855 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 20:32:32 +0800 Subject: [PATCH 215/466] Implement MTLFormats enum mappings and update texture types Implemented mapping functions in MTLFormats to convert engine enums to Metal API types. Updated Metal texture type selection to consider sample count, and modified MTLTexture and MTLTextureView to use the new overload. Enables correct translation for Metal rendering. --- sources/Zenith.NET.Metal/MTLFormats.cs | 187 +++++++++++++++++++-- sources/Zenith.NET.Metal/MTLTexture.cs | 2 +- sources/Zenith.NET.Metal/MTLTextureView.cs | 2 +- 3 files changed, 178 insertions(+), 13 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 5afe4683..87504fee 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -15,43 +15,208 @@ public static MTLResourceOptions Metal(MemoryResidency memoryResidency) }; } - public static MTLTextureType Metal(TextureType textureType) + public static MTLTextureType Metal(TextureType textureType, SampleCount sampleCount) { - throw new NotImplementedException(); + return textureType switch + { + TextureType.Texture1D => MTLTextureType.MTL1D, + TextureType.Texture2D => sampleCount is SampleCount.Count1 ? MTLTextureType.MTL2D : MTLTextureType.MTL2DMultisample, + TextureType.Texture3D => MTLTextureType.MTL3D, + TextureType.TextureCube => MTLTextureType.MTLCube, + TextureType.Texture1DArray => MTLTextureType.MTL1DArray, + TextureType.Texture2DArray => sampleCount is SampleCount.Count1 ? MTLTextureType.MTL2DArray : MTLTextureType.MTL2DMultisampleArray, + TextureType.TextureCubeArray => MTLTextureType.MTLCubeArray, + _ => default + }; } public static MTLPixelFormat Metal(PixelFormat pixelFormat) { - throw new NotImplementedException(); + return pixelFormat switch + { + PixelFormat.R8UNorm => MTLPixelFormat.R8Unorm, + PixelFormat.R8SNorm => MTLPixelFormat.R8Snorm, + PixelFormat.R8UInt => MTLPixelFormat.R8Uint, + PixelFormat.R8SInt => MTLPixelFormat.R8Sint, + + PixelFormat.R16UNorm => MTLPixelFormat.R16Unorm, + PixelFormat.R16SNorm => MTLPixelFormat.R16Snorm, + PixelFormat.R16UInt => MTLPixelFormat.R16Uint, + PixelFormat.R16SInt => MTLPixelFormat.R16Sint, + PixelFormat.R16Float => MTLPixelFormat.R16Float, + + PixelFormat.R32UInt => MTLPixelFormat.R32Uint, + PixelFormat.R32SInt => MTLPixelFormat.R32Sint, + PixelFormat.R32Float => MTLPixelFormat.R32Float, + + PixelFormat.R8G8UNorm => MTLPixelFormat.RG8Unorm, + PixelFormat.R8G8SNorm => MTLPixelFormat.RG8Snorm, + PixelFormat.R8G8UInt => MTLPixelFormat.RG8Uint, + PixelFormat.R8G8SInt => MTLPixelFormat.RG8Sint, + + PixelFormat.R16G16UNorm => MTLPixelFormat.RG16Unorm, + PixelFormat.R16G16SNorm => MTLPixelFormat.RG16Snorm, + PixelFormat.R16G16UInt => MTLPixelFormat.RG16Uint, + PixelFormat.R16G16SInt => MTLPixelFormat.RG16Sint, + PixelFormat.R16G16Float => MTLPixelFormat.RG16Float, + + PixelFormat.R32G32UInt => MTLPixelFormat.RG32Uint, + PixelFormat.R32G32SInt => MTLPixelFormat.RG32Sint, + PixelFormat.R32G32Float => MTLPixelFormat.RG32Float, + + PixelFormat.R8G8B8A8UNorm => MTLPixelFormat.RGBA8Unorm, + PixelFormat.R8G8B8A8SNorm => MTLPixelFormat.RGBA8Snorm, + PixelFormat.R8G8B8A8UInt => MTLPixelFormat.RGBA8Uint, + PixelFormat.R8G8B8A8SInt => MTLPixelFormat.RGBA8Sint, + PixelFormat.R8G8B8A8SRgb => MTLPixelFormat.RGBA8Unorm_sRGB, + + PixelFormat.R16G16B16A16UNorm => MTLPixelFormat.RGBA16Unorm, + PixelFormat.R16G16B16A16SNorm => MTLPixelFormat.RGBA16Snorm, + PixelFormat.R16G16B16A16UInt => MTLPixelFormat.RGBA16Uint, + PixelFormat.R16G16B16A16SInt => MTLPixelFormat.RGBA16Sint, + PixelFormat.R16G16B16A16Float => MTLPixelFormat.RGBA16Float, + + PixelFormat.R32G32B32A32UInt => MTLPixelFormat.RGBA32Uint, + PixelFormat.R32G32B32A32SInt => MTLPixelFormat.RGBA32Sint, + PixelFormat.R32G32B32A32Float => MTLPixelFormat.RGBA32Float, + + PixelFormat.B8G8R8A8UNorm => MTLPixelFormat.BGRA8Unorm, + PixelFormat.B8G8R8A8SRgb => MTLPixelFormat.BGRA8Unorm_sRGB, + + PixelFormat.D16UNorm => MTLPixelFormat.Depth16Unorm, + PixelFormat.D24UNormS8UInt => MTLPixelFormat.Depth24Unorm_Stencil8, + PixelFormat.D32Float => MTLPixelFormat.Depth32Float, + PixelFormat.D32FloatS8UInt => MTLPixelFormat.Depth32Float_Stencil8, + + PixelFormat.BC4UNorm => MTLPixelFormat.BC4_RUnorm, + PixelFormat.BC4SNorm => MTLPixelFormat.BC4_RSnorm, + + PixelFormat.BC5UNorm => MTLPixelFormat.BC5_RGUnorm, + PixelFormat.BC5SNorm => MTLPixelFormat.BC5_RGSnorm, + + PixelFormat.BC6HUFloat => MTLPixelFormat.BC6H_RGBUfloat, + PixelFormat.BC6HSFloat => MTLPixelFormat.BC6H_RGBFloat, + + PixelFormat.BC7UNorm => MTLPixelFormat.BC7_RGBAUnorm, + PixelFormat.BC7SRgb => MTLPixelFormat.BC7_RGBAUnorm_sRGB, + + PixelFormat.ETC2UNorm => MTLPixelFormat.ETC2_RGB8, + PixelFormat.ETC2SRgb => MTLPixelFormat.ETC2_RGB8_sRGB, + PixelFormat.ETC2A1UNorm => MTLPixelFormat.ETC2_RGB8A1, + PixelFormat.ETC2A1SRgb => MTLPixelFormat.ETC2_RGB8A1_sRGB, + PixelFormat.ETC2A8UNorm => MTLPixelFormat.EAC_RGBA8, + PixelFormat.ETC2A8SRgb => MTLPixelFormat.EAC_RGBA8_sRGB, + + PixelFormat.ASTC4x4UNorm => MTLPixelFormat.ASTC_4x4_LDR, + PixelFormat.ASTC4x4SRgb => MTLPixelFormat.ASTC_4x4_sRGB, + PixelFormat.ASTC4x4Float => MTLPixelFormat.ASTC_4x4_HDR, + + PixelFormat.ASTC5x5UNorm => MTLPixelFormat.ASTC_5x5_LDR, + PixelFormat.ASTC5x5SRgb => MTLPixelFormat.ASTC_5x5_sRGB, + PixelFormat.ASTC5x5Float => MTLPixelFormat.ASTC_5x5_HDR, + + PixelFormat.ASTC6x6UNorm => MTLPixelFormat.ASTC_6x6_LDR, + PixelFormat.ASTC6x6SRgb => MTLPixelFormat.ASTC_6x6_sRGB, + PixelFormat.ASTC6x6Float => MTLPixelFormat.ASTC_6x6_HDR, + + PixelFormat.ASTC8x8UNorm => MTLPixelFormat.ASTC_8x8_LDR, + PixelFormat.ASTC8x8SRgb => MTLPixelFormat.ASTC_8x8_sRGB, + PixelFormat.ASTC8x8Float => MTLPixelFormat.ASTC_8x8_HDR, + + PixelFormat.ASTC10x10UNorm => MTLPixelFormat.ASTC_10x10_LDR, + PixelFormat.ASTC10x10SRgb => MTLPixelFormat.ASTC_10x10_sRGB, + PixelFormat.ASTC10x10Float => MTLPixelFormat.ASTC_10x10_HDR, + + PixelFormat.ASTC12x12UNorm => MTLPixelFormat.ASTC_12x12_LDR, + PixelFormat.ASTC12x12SRgb => MTLPixelFormat.ASTC_12x12_sRGB, + PixelFormat.ASTC12x12Float => MTLPixelFormat.ASTC_12x12_HDR, + + _ => default + }; } public static nuint Metal(SampleCount sampleCount) { - throw new NotImplementedException(); + return sampleCount switch + { + SampleCount.Count1 => 1, + SampleCount.Count2 => 2, + SampleCount.Count4 => 4, + SampleCount.Count8 => 8, + SampleCount.Count16 => 16, + SampleCount.Count32 => 32, + _ => default + }; } public static MTLTextureUsage Metal(TextureUsages textureUsages) { - throw new NotImplementedException(); + MTLTextureUsage result = MTLTextureUsage.Unknown; + + if (textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= MTLTextureUsage.ShaderRead; + } + + if (textureUsages.HasFlag(TextureUsages.Storage)) + { + result |= MTLTextureUsage.ShaderWrite; + } + + if (textureUsages.HasFlag(TextureUsages.ColorAttachment) || textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) + { + result |= MTLTextureUsage.RenderTarget; + } + + return result; } - internal static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode minFilter) + internal static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode filterMode) { - throw new NotImplementedException(); + return filterMode switch + { + FilterMode.Point => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest), + FilterMode.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), + _ => default + }; } - internal static MTLSamplerAddressMode Metal(AddressMode addressV) + internal static MTLSamplerAddressMode Metal(AddressMode addressMode) { - throw new NotImplementedException(); + return addressMode switch + { + AddressMode.Wrap => MTLSamplerAddressMode.Repeat, + AddressMode.Mirror => MTLSamplerAddressMode.MirrorRepeat, + AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, + AddressMode.Border => MTLSamplerAddressMode.ClampToBorderColor, + _ => default + }; } internal static MTLSamplerBorderColor Metal(BorderColor borderColor) { - throw new NotImplementedException(); + return borderColor switch + { + BorderColor.TransparentBlack => MTLSamplerBorderColor.TransparentBlack, + BorderColor.OpaqueBlack => MTLSamplerBorderColor.OpaqueBlack, + BorderColor.OpaqueWhite => MTLSamplerBorderColor.OpaqueWhite, + _ => default + }; } internal static MTLCompareFunction Metal(CompareOp compareOp) { - throw new NotImplementedException(); + return compareOp switch + { + CompareOp.Never => MTLCompareFunction.Never, + CompareOp.Less => MTLCompareFunction.Less, + CompareOp.Equal => MTLCompareFunction.Equal, + CompareOp.LessEqual => MTLCompareFunction.LessEqual, + CompareOp.Greater => MTLCompareFunction.Greater, + CompareOp.NotEqual => MTLCompareFunction.NotEqual, + CompareOp.GreaterEqual => MTLCompareFunction.GreaterEqual, + CompareOp.Always => MTLCompareFunction.Always, + _ => default + }; } } diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs index 0653c08f..3a7c92a5 100644 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -62,7 +62,7 @@ public static MTLTextureDescriptor Descriptor(TextureDesc desc) { return new() { - TextureType = MTLFormats.Metal(desc.Type), + TextureType = MTLFormats.Metal(desc.Type, desc.SampleCount), PixelFormat = MTLFormats.Metal(desc.Format), Width = desc.Width, Height = desc.Height, diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs index 18ab6dd0..551aa13d 100644 --- a/sources/Zenith.NET.Metal/MTLTextureView.cs +++ b/sources/Zenith.NET.Metal/MTLTextureView.cs @@ -7,7 +7,7 @@ internal class MTLTextureView : TextureView public MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : base(context, desc) { Texture = desc.Texture.Metal().Texture.MakeTextureView(MTLFormats.Metal(desc.Format), - MTLFormats.Metal(desc.Type), + MTLFormats.Metal(desc.Type, desc.Texture.Desc.SampleCount), new(desc.Range.BaseMipLevel, desc.Range.LevelCount), new(desc.Range.BaseArrayLayer, desc.Range.LayerCount)); From 2804b65c0ef7a7202c3db52d98e2d9ae1329c4a6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 13 Jun 2026 20:40:18 +0800 Subject: [PATCH 216/466] Add Metal shader support with MTLShader implementation Introduced the internal MTLShader class for Metal shader creation and management using Metal.NET. Updated MTLGraphicsContext to return MTLShader in CreateShaderImpl. Added a Metal() extension method to Shader for accessing the underlying MTLShader instance. --- sources/Zenith.NET.Metal/Extensions.cs | 4 ++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- sources/Zenith.NET.Metal/MTLShader.cs | 38 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 sources/Zenith.NET.Metal/MTLShader.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index c30708ee..19e130bc 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -97,6 +97,10 @@ internal MTLSampler Metal() extension(Shader shader) { + internal MTLShader Metal() + { + return (MTLShader)shader; + } } extension(SwapChain swapChain) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 4b746bc1..b5edbc8a 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -118,7 +118,7 @@ protected override Sampler CreateSamplerImpl(SamplerDesc desc) protected override Shader CreateShaderImpl(ShaderDesc desc) { - throw new NotImplementedException(); + return new MTLShader(this, desc); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLShader.cs b/sources/Zenith.NET.Metal/MTLShader.cs new file mode 100644 index 00000000..d617c4a5 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLShader.cs @@ -0,0 +1,38 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLShader : Shader +{ + public MTLLibrary Library; + + public MTL4LibraryFunctionDescriptor Descriptor; + + public MTLShader(MTLGraphicsContext context, ShaderDesc desc) : base(context, desc) + { + Library = context.Device.MakeLibrary(DispatchData.Create(desc.CodeBytes), out NSError error); + error.Success(); + + Descriptor = new() + { + Name = desc.Name, + Library = Library + }; + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + Library.Label = name; + } + + protected override void Destroy() + { + Descriptor.Dispose(); + Library.Dispose(); + } +} From 6968b15abb288f5598f16acff6dd4ce438d182a6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 19:16:43 +0800 Subject: [PATCH 217/466] Add Metal graphics pipeline implementation and refactor InputLayout Refactored InputLayout to rename InputElements to Elements and updated all references. Introduced MTLGraphicsPipeline for Metal backend, handling pipeline and state creation. Updated MTLGraphicsContext to instantiate MTLGraphicsPipeline. Added Metal() extension for GraphicsPipeline and stub conversion methods in MTLFormats. --- .../DXGraphicsPipeline.cs | 2 +- sources/Zenith.NET.Metal/Extensions.cs | 4 + sources/Zenith.NET.Metal/MTLFormats.cs | 30 +++++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 121 ++++++++++++++++++ sources/Zenith.NET/Structs/InputLayout.cs | 10 +- 6 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 2fc6876d..681d9c7b 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -28,7 +28,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) { InputLayout inputLayout = desc.InputLayouts[i]; - foreach (InputElement element in inputLayout.InputElements) + foreach (InputElement element in inputLayout.Elements) { inputElementDescs.Add(new() { diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 19e130bc..d86e45ab 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -73,6 +73,10 @@ internal MTLCommandQueue Metal() extension(GraphicsPipeline graphicsPipeline) { + internal MTLGraphicsPipeline Metal() + { + return (MTLGraphicsPipeline)graphicsPipeline; + } } extension(Heap heap) diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 87504fee..8ac1d477 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -219,4 +219,34 @@ internal static MTLCompareFunction Metal(CompareOp compareOp) _ => default }; } + + internal static MTLPrimitiveTopologyClass Metal(PrimitiveTopology primitiveTopology) + { + throw new NotImplementedException(); + } + + internal static MTLVertexFormat Metal(ElementFormat format) + { + throw new NotImplementedException(); + } + + internal static MTLBlendFactor Metal(BlendFactor srcRgbFactor) + { + throw new NotImplementedException(); + } + + internal static MTLBlendOperation Metal(BlendOp rgbOp) + { + throw new NotImplementedException(); + } + + internal static MTLColorWriteMask Metal(ColorWrites colorWrites) + { + throw new NotImplementedException(); + } + + internal static MTLStencilOperation Metal(StencilOp failOp) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index b5edbc8a..e1de35ab 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -123,7 +123,7 @@ protected override Shader CreateShaderImpl(ShaderDesc desc) protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) { - throw new NotImplementedException(); + return new MTLGraphicsPipeline(this, desc); } protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs new file mode 100644 index 00000000..28e38fd8 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -0,0 +1,121 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLGraphicsPipeline : GraphicsPipeline +{ + public MTLDepthStencilState DepthStencilState; + + public MTLRenderPipelineState RenderPipelineState; + + public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) + { + MTL4RenderPipelineDescriptor descriptor = new() + { + VertexFunctionDescriptor = desc.VertexShader.Metal().Descriptor, + FragmentFunctionDescriptor = desc.FragmentShader.Metal().Descriptor, + InputPrimitiveTopology = MTLFormats.Metal(desc.PrimitiveTopology) + }; + + // InputLayouts + { + uint attribute = 0; + for (int i = 0; i < desc.InputLayouts.Length; i++) + { + uint bufferIndex = (uint)(i + 1); + + InputLayout inputLayout = desc.InputLayouts[i]; + + descriptor.VertexDescriptor.Layouts[bufferIndex].Stride = inputLayout.StrideInBytes; + + foreach (InputElement element in inputLayout.Elements) + { + descriptor.VertexDescriptor.Attributes[attribute++] = new() + { + Format = MTLFormats.Metal(element.Format), + Offset = element.OffsetInBytes, + BufferIndex = bufferIndex + }; + } + } + } + + // AttachmentFormats and RenderState + { + ColorAttachmentBlendState[] states = + [ + desc.RenderState.BlendState.ColorAttachment0, + desc.RenderState.BlendState.ColorAttachment1, + desc.RenderState.BlendState.ColorAttachment2, + desc.RenderState.BlendState.ColorAttachment3, + desc.RenderState.BlendState.ColorAttachment4, + desc.RenderState.BlendState.ColorAttachment5, + desc.RenderState.BlendState.ColorAttachment6, + desc.RenderState.BlendState.ColorAttachment7 + ]; + + descriptor.RasterSampleCount = MTLFormats.Metal(desc.AttachmentFormats.SampleCount); + descriptor.AlphaToCoverageState = desc.RenderState.BlendState.IsAlphaToCoverageEnabled ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; + + for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) + { + ColorAttachmentBlendState state = desc.RenderState.BlendState.IsIndependentBlendEnabled ? states[i] : states[0]; + + descriptor.ColorAttachments[(uint)i] = new() + { + PixelFormat = MTLFormats.Metal(desc.AttachmentFormats.ColorFormats[i]), + BlendingState = state.IsBlendingEnabled ? MTL4BlendState.Enabled : MTL4BlendState.Disabled, + SourceRGBBlendFactor = MTLFormats.Metal(state.SrcRgbFactor), + DestinationRGBBlendFactor = MTLFormats.Metal(state.DstRgbFactor), + RgbBlendOperation = MTLFormats.Metal(state.RgbOp), + SourceAlphaBlendFactor = MTLFormats.Metal(state.SrcAlphaFactor), + DestinationAlphaBlendFactor = MTLFormats.Metal(state.DstAlphaFactor), + AlphaBlendOperation = MTLFormats.Metal(state.AlphaOp), + WriteMask = MTLFormats.Metal(state.ColorWrites) + }; + } + + DepthStencilState = context.Device.MakeDepthStencilState(new() + { + DepthCompareFunction = desc.RenderState.DepthStencilState.IsDepthEnabled ? MTLFormats.Metal(desc.RenderState.DepthStencilState.DepthCompareOp) : MTLCompareFunction.Always, + IsDepthWriteEnabled = desc.RenderState.DepthStencilState.IsDepthWriteEnabled, + FrontFaceStencil = desc.RenderState.DepthStencilState.IsStencilEnabled ? new() + { + StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.CompareOp), + StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.FailOp), + DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.DepthFailOp), + DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.PassOp), + ReadMask = desc.RenderState.DepthStencilState.StencilReadMask, + WriteMask = desc.RenderState.DepthStencilState.StencilWriteMask + } : MTLStencilDescriptor.Null, + BackFaceStencil = desc.RenderState.DepthStencilState.IsStencilEnabled ? new() + { + StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.CompareOp), + StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.FailOp), + DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.DepthFailOp), + DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.PassOp), + ReadMask = desc.RenderState.DepthStencilState.StencilReadMask, + WriteMask = desc.RenderState.DepthStencilState.StencilWriteMask + } : MTLStencilDescriptor.Null + }); + } + + RenderPipelineState = context.Compiler.MakeRenderPipelineState(descriptor, MTL4CompilerTaskOptions.Null, out NSError error); + error.Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + RenderPipelineState.Dispose(); + DepthStencilState.Dispose(); + } +} diff --git a/sources/Zenith.NET/Structs/InputLayout.cs b/sources/Zenith.NET/Structs/InputLayout.cs index 2eb4bc18..d72e79cd 100644 --- a/sources/Zenith.NET/Structs/InputLayout.cs +++ b/sources/Zenith.NET/Structs/InputLayout.cs @@ -2,7 +2,7 @@ public struct InputLayout { - public InputElement[] InputElements; + public InputElement[] Elements; public uint StrideInBytes; @@ -10,15 +10,15 @@ public void Add(InputElement element) { element.OffsetInBytes = StrideInBytes; - if (InputElements is null) + if (Elements is null) { - InputElements = [element]; + Elements = [element]; } else { - Array.Resize(ref InputElements, InputElements.Length + 1); + Array.Resize(ref Elements, Elements.Length + 1); - InputElements[^1] = element; + Elements[^1] = element; } StrideInBytes += ZenithHelper.SizeInBytes(element.Format); From 4a59e0696bb0dd08f111c91a3ba467d60c8469c3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 19:57:25 +0800 Subject: [PATCH 218/466] Refactor RenderState field names for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed RenderState fields: RasterizerState → Rasterizer, DepthStencilState → DepthStencil, and BlendState → Blend. Updated all usages across the codebase to match the new names for improved consistency and readability. --- .../Renderers/RasterizationRenderer.cs | 6 +- .../ImGuiRenderer.cs | 6 +- .../DXGraphicsPipeline.cs | 62 +++++++++---------- .../DXMeshShadingPipeline.cs | 62 +++++++++---------- .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 52 ++++++++-------- sources/Zenith.NET/Structs/RenderState.cs | 6 +- 6 files changed, 97 insertions(+), 97 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index a3bf0dfc..058e3ad7 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -86,9 +86,9 @@ public RasterizationRenderer() AttachmentFormats = AttachmentFormats, RenderState = new() { - RasterizerState = RasterizerState.CullNone(), - DepthStencilState = DepthStencilState.DepthReadWrite(), - BlendState = BlendState.Opaque() + Rasterizer = RasterizerState.CullNone(), + DepthStencil = DepthStencilState.DepthReadWrite(), + Blend = BlendState.Opaque() } }); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index 32ba5546..e37b87c2 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -121,9 +121,9 @@ public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormat AttachmentFormats = attachmentFormats, RenderState = new() { - RasterizerState = RasterizerState.CullNone(), - DepthStencilState = DepthStencilState.DepthNone(), - BlendState = BlendState.NonPremultiplied() + Rasterizer = RasterizerState.CullNone(), + DepthStencil = DepthStencilState.DepthNone(), + Blend = BlendState.NonPremultiplied() } }); diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 681d9c7b..85824836 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -66,56 +66,56 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) { ColorAttachmentBlendState[] colorAttachmentBlendStates = [ - desc.RenderState.BlendState.ColorAttachment0, - desc.RenderState.BlendState.ColorAttachment1, - desc.RenderState.BlendState.ColorAttachment2, - desc.RenderState.BlendState.ColorAttachment3, - desc.RenderState.BlendState.ColorAttachment4, - desc.RenderState.BlendState.ColorAttachment5, - desc.RenderState.BlendState.ColorAttachment6, - desc.RenderState.BlendState.ColorAttachment7 + desc.RenderState.Blend.ColorAttachment0, + desc.RenderState.Blend.ColorAttachment1, + desc.RenderState.Blend.ColorAttachment2, + desc.RenderState.Blend.ColorAttachment3, + desc.RenderState.Blend.ColorAttachment4, + desc.RenderState.Blend.ColorAttachment5, + desc.RenderState.Blend.ColorAttachment6, + desc.RenderState.Blend.ColorAttachment7 ]; pipelineStateStream.RasterizerState = new() { - FillMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.FillMode), - CullMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.CullMode), - FrontCounterClockwise = desc.RenderState.RasterizerState.FrontFace is FrontFace.CounterClockwise, - DepthBias = desc.RenderState.RasterizerState.DepthBias, - DepthBiasClamp = desc.RenderState.RasterizerState.DepthBiasClamp, - SlopeScaledDepthBias = desc.RenderState.RasterizerState.DepthBiasSlopeScale, - DepthClipEnable = desc.RenderState.RasterizerState.IsDepthClipEnabled, + FillMode = DXFormats.DirectX12(desc.RenderState.Rasterizer.FillMode), + CullMode = DXFormats.DirectX12(desc.RenderState.Rasterizer.CullMode), + FrontCounterClockwise = desc.RenderState.Rasterizer.FrontFace is FrontFace.CounterClockwise, + DepthBias = desc.RenderState.Rasterizer.DepthBias, + DepthBiasClamp = desc.RenderState.Rasterizer.DepthBiasClamp, + SlopeScaledDepthBias = desc.RenderState.Rasterizer.DepthBiasSlopeScale, + DepthClipEnable = desc.RenderState.Rasterizer.IsDepthClipEnabled, MultisampleEnable = desc.AttachmentFormats.SampleCount is not SampleCount.Count1 }; pipelineStateStream.DepthStencilState = new() { - DepthEnable = desc.RenderState.DepthStencilState.IsDepthEnabled, - DepthWriteMask = desc.RenderState.DepthStencilState.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, - DepthFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.DepthCompareOp), - StencilEnable = desc.RenderState.DepthStencilState.IsStencilEnabled, - StencilReadMask = desc.RenderState.DepthStencilState.StencilReadMask, - StencilWriteMask = desc.RenderState.DepthStencilState.StencilWriteMask, + DepthEnable = desc.RenderState.DepthStencil.IsDepthEnabled, + DepthWriteMask = desc.RenderState.DepthStencil.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, + DepthFunc = DXFormats.DirectX12(desc.RenderState.DepthStencil.DepthCompareOp), + StencilEnable = desc.RenderState.DepthStencil.IsStencilEnabled, + StencilReadMask = desc.RenderState.DepthStencil.StencilReadMask, + StencilWriteMask = desc.RenderState.DepthStencil.StencilWriteMask, FrontFace = new() { - StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.FailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.DepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.PassOp), - StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.CompareOp) + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.CompareOp) }, BackFace = new() { - StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.FailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.DepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.PassOp), - StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.CompareOp) + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.CompareOp) } }; pipelineStateStream.BlendState = new() { - AlphaToCoverageEnable = desc.RenderState.BlendState.IsAlphaToCoverageEnabled, - IndependentBlendEnable = desc.RenderState.BlendState.IsIndependentBlendEnabled + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled, + IndependentBlendEnable = desc.RenderState.Blend.IsIndependentBlendEnabled }; for (int i = 0; i < colorAttachmentBlendStates.Length; i++) diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index ffd62a88..db4c886c 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -40,56 +40,56 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc { ColorAttachmentBlendState[] colorAttachmentBlendStates = [ - desc.RenderState.BlendState.ColorAttachment0, - desc.RenderState.BlendState.ColorAttachment1, - desc.RenderState.BlendState.ColorAttachment2, - desc.RenderState.BlendState.ColorAttachment3, - desc.RenderState.BlendState.ColorAttachment4, - desc.RenderState.BlendState.ColorAttachment5, - desc.RenderState.BlendState.ColorAttachment6, - desc.RenderState.BlendState.ColorAttachment7 + desc.RenderState.Blend.ColorAttachment0, + desc.RenderState.Blend.ColorAttachment1, + desc.RenderState.Blend.ColorAttachment2, + desc.RenderState.Blend.ColorAttachment3, + desc.RenderState.Blend.ColorAttachment4, + desc.RenderState.Blend.ColorAttachment5, + desc.RenderState.Blend.ColorAttachment6, + desc.RenderState.Blend.ColorAttachment7 ]; pipelineStateStream.RasterizerState = new() { - FillMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.FillMode), - CullMode = DXFormats.DirectX12(desc.RenderState.RasterizerState.CullMode), - FrontCounterClockwise = desc.RenderState.RasterizerState.FrontFace is FrontFace.CounterClockwise, - DepthBias = desc.RenderState.RasterizerState.DepthBias, - DepthBiasClamp = desc.RenderState.RasterizerState.DepthBiasClamp, - SlopeScaledDepthBias = desc.RenderState.RasterizerState.DepthBiasSlopeScale, - DepthClipEnable = desc.RenderState.RasterizerState.IsDepthClipEnabled, + FillMode = DXFormats.DirectX12(desc.RenderState.Rasterizer.FillMode), + CullMode = DXFormats.DirectX12(desc.RenderState.Rasterizer.CullMode), + FrontCounterClockwise = desc.RenderState.Rasterizer.FrontFace is FrontFace.CounterClockwise, + DepthBias = desc.RenderState.Rasterizer.DepthBias, + DepthBiasClamp = desc.RenderState.Rasterizer.DepthBiasClamp, + SlopeScaledDepthBias = desc.RenderState.Rasterizer.DepthBiasSlopeScale, + DepthClipEnable = desc.RenderState.Rasterizer.IsDepthClipEnabled, MultisampleEnable = desc.AttachmentFormats.SampleCount is not SampleCount.Count1 }; pipelineStateStream.DepthStencilState = new() { - DepthEnable = desc.RenderState.DepthStencilState.IsDepthEnabled, - DepthWriteMask = desc.RenderState.DepthStencilState.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, - DepthFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.DepthCompareOp), - StencilEnable = desc.RenderState.DepthStencilState.IsStencilEnabled, - StencilReadMask = desc.RenderState.DepthStencilState.StencilReadMask, - StencilWriteMask = desc.RenderState.DepthStencilState.StencilWriteMask, + DepthEnable = desc.RenderState.DepthStencil.IsDepthEnabled, + DepthWriteMask = desc.RenderState.DepthStencil.IsDepthWriteEnabled ? DepthWriteMask.All : DepthWriteMask.Zero, + DepthFunc = DXFormats.DirectX12(desc.RenderState.DepthStencil.DepthCompareOp), + StencilEnable = desc.RenderState.DepthStencil.IsStencilEnabled, + StencilReadMask = desc.RenderState.DepthStencil.StencilReadMask, + StencilWriteMask = desc.RenderState.DepthStencil.StencilWriteMask, FrontFace = new() { - StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.FailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.DepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.PassOp), - StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.FrontFace.CompareOp) + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencil.FrontFace.CompareOp) }, BackFace = new() { - StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.FailOp), - StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.DepthFailOp), - StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.PassOp), - StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencilState.BackFace.CompareOp) + StencilFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.FailOp), + StencilDepthFailOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.DepthFailOp), + StencilPassOp = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.PassOp), + StencilFunc = DXFormats.DirectX12(desc.RenderState.DepthStencil.BackFace.CompareOp) } }; pipelineStateStream.BlendState = new() { - AlphaToCoverageEnable = desc.RenderState.BlendState.IsAlphaToCoverageEnabled, - IndependentBlendEnable = desc.RenderState.BlendState.IsIndependentBlendEnabled + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled, + IndependentBlendEnable = desc.RenderState.Blend.IsIndependentBlendEnabled }; for (int i = 0; i < colorAttachmentBlendStates.Length; i++) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs index 28e38fd8..25009ddb 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -44,22 +44,22 @@ public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc { ColorAttachmentBlendState[] states = [ - desc.RenderState.BlendState.ColorAttachment0, - desc.RenderState.BlendState.ColorAttachment1, - desc.RenderState.BlendState.ColorAttachment2, - desc.RenderState.BlendState.ColorAttachment3, - desc.RenderState.BlendState.ColorAttachment4, - desc.RenderState.BlendState.ColorAttachment5, - desc.RenderState.BlendState.ColorAttachment6, - desc.RenderState.BlendState.ColorAttachment7 + desc.RenderState.Blend.ColorAttachment0, + desc.RenderState.Blend.ColorAttachment1, + desc.RenderState.Blend.ColorAttachment2, + desc.RenderState.Blend.ColorAttachment3, + desc.RenderState.Blend.ColorAttachment4, + desc.RenderState.Blend.ColorAttachment5, + desc.RenderState.Blend.ColorAttachment6, + desc.RenderState.Blend.ColorAttachment7 ]; descriptor.RasterSampleCount = MTLFormats.Metal(desc.AttachmentFormats.SampleCount); - descriptor.AlphaToCoverageState = desc.RenderState.BlendState.IsAlphaToCoverageEnabled ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; + descriptor.AlphaToCoverageState = desc.RenderState.Blend.IsAlphaToCoverageEnabled ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) { - ColorAttachmentBlendState state = desc.RenderState.BlendState.IsIndependentBlendEnabled ? states[i] : states[0]; + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[0]; descriptor.ColorAttachments[(uint)i] = new() { @@ -77,25 +77,25 @@ public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc DepthStencilState = context.Device.MakeDepthStencilState(new() { - DepthCompareFunction = desc.RenderState.DepthStencilState.IsDepthEnabled ? MTLFormats.Metal(desc.RenderState.DepthStencilState.DepthCompareOp) : MTLCompareFunction.Always, - IsDepthWriteEnabled = desc.RenderState.DepthStencilState.IsDepthWriteEnabled, - FrontFaceStencil = desc.RenderState.DepthStencilState.IsStencilEnabled ? new() + DepthCompareFunction = desc.RenderState.DepthStencil.IsDepthEnabled ? MTLFormats.Metal(desc.RenderState.DepthStencil.DepthCompareOp) : MTLCompareFunction.Always, + IsDepthWriteEnabled = desc.RenderState.DepthStencil.IsDepthWriteEnabled, + FrontFaceStencil = desc.RenderState.DepthStencil.IsStencilEnabled ? new() { - StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.CompareOp), - StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.FailOp), - DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.DepthFailOp), - DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.FrontFace.PassOp), - ReadMask = desc.RenderState.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderState.DepthStencilState.StencilWriteMask + StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.CompareOp), + StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.FailOp), + DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.DepthFailOp), + DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.PassOp), + ReadMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask } : MTLStencilDescriptor.Null, - BackFaceStencil = desc.RenderState.DepthStencilState.IsStencilEnabled ? new() + BackFaceStencil = desc.RenderState.DepthStencil.IsStencilEnabled ? new() { - StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.CompareOp), - StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.FailOp), - DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.DepthFailOp), - DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencilState.BackFace.PassOp), - ReadMask = desc.RenderState.DepthStencilState.StencilReadMask, - WriteMask = desc.RenderState.DepthStencilState.StencilWriteMask + StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.CompareOp), + StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.FailOp), + DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.DepthFailOp), + DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.PassOp), + ReadMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask } : MTLStencilDescriptor.Null }); } diff --git a/sources/Zenith.NET/Structs/RenderState.cs b/sources/Zenith.NET/Structs/RenderState.cs index abc71741..2cd62590 100644 --- a/sources/Zenith.NET/Structs/RenderState.cs +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -2,9 +2,9 @@ public struct RenderState { - public RasterizerState RasterizerState; + public RasterizerState Rasterizer; - public DepthStencilState DepthStencilState; + public DepthStencilState DepthStencil; - public BlendState BlendState; + public BlendState Blend; } From 9cdd5ee87be0073f9a3c7a39ae8c2f1040749eb0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 19:58:19 +0800 Subject: [PATCH 219/466] Rename colorAttachmentBlendStates to states for clarity Refactored variable name colorAttachmentBlendStates to states in DXGraphicsPipeline and DXMeshShadingPipeline for improved naming consistency. No functional changes were made. --- sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs | 6 +++--- sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index 85824836..fc44cd3d 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -64,7 +64,7 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) // RenderState { - ColorAttachmentBlendState[] colorAttachmentBlendStates = + ColorAttachmentBlendState[] states = [ desc.RenderState.Blend.ColorAttachment0, desc.RenderState.Blend.ColorAttachment1, @@ -118,9 +118,9 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) IndependentBlendEnable = desc.RenderState.Blend.IsIndependentBlendEnabled }; - for (int i = 0; i < colorAttachmentBlendStates.Length; i++) + for (int i = 0; i < states.Length; i++) { - ColorAttachmentBlendState blend = colorAttachmentBlendStates[i]; + ColorAttachmentBlendState blend = states[i]; pipelineStateStream.BlendState.RenderTarget[i] = new() { diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index db4c886c..ef146a40 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -38,7 +38,7 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc // RenderState { - ColorAttachmentBlendState[] colorAttachmentBlendStates = + ColorAttachmentBlendState[] states = [ desc.RenderState.Blend.ColorAttachment0, desc.RenderState.Blend.ColorAttachment1, @@ -92,9 +92,9 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc IndependentBlendEnable = desc.RenderState.Blend.IsIndependentBlendEnabled }; - for (int i = 0; i < colorAttachmentBlendStates.Length; i++) + for (int i = 0; i < states.Length; i++) { - ColorAttachmentBlendState blend = colorAttachmentBlendStates[i]; + ColorAttachmentBlendState blend = states[i]; pipelineStateStream.BlendState.RenderTarget[i] = new() { From 58b2c3ff0b8117c78a8f3d33dd256f6a252e0d42 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 20:21:05 +0800 Subject: [PATCH 220/466] Remove Metal std and Xmetal args from compiler invocation The Metal backend in ZenithCompiler no longer passes the "-Xmetal" and "-std=metal4.0" arguments. Only "metallib" and "-capability metallib_latest" are now included for Metal compilation. --- sources/Zenith.NET/ZenithCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs index 2b1d9374..683f7119 100644 --- a/sources/Zenith.NET/ZenithCompiler.cs +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -53,7 +53,7 @@ private static string[] SlangCompilerArguments(GraphicsApi graphicsApi, string? break; case GraphicsApi.Metal: - arguments.AddRange(["metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]); + arguments.AddRange(["metallib", "-capability", "metallib_latest"]); break; case GraphicsApi.Vulkan: From bea64c14f9630c75fecbfdd76fe142c234e3192b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 21:02:33 +0800 Subject: [PATCH 221/466] Add ThreadGroupSize struct and field to ShaderDesc Introduced the ThreadGroupSize struct with X, Y, and Z fields in the Zenith.NET namespace. Updated ShaderDesc to include a ThreadGroupSize field for specifying thread group dimensions. --- sources/Zenith.NET/Structs/ShaderDesc.cs | 2 ++ sources/Zenith.NET/Structs/ThreadGroupSize.cs | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 sources/Zenith.NET/Structs/ThreadGroupSize.cs diff --git a/sources/Zenith.NET/Structs/ShaderDesc.cs b/sources/Zenith.NET/Structs/ShaderDesc.cs index d8d0dee6..3348e686 100644 --- a/sources/Zenith.NET/Structs/ShaderDesc.cs +++ b/sources/Zenith.NET/Structs/ShaderDesc.cs @@ -5,4 +5,6 @@ public struct ShaderDesc public string Name; public byte[] CodeBytes; + + public ThreadGroupSize ThreadGroupSize; } diff --git a/sources/Zenith.NET/Structs/ThreadGroupSize.cs b/sources/Zenith.NET/Structs/ThreadGroupSize.cs new file mode 100644 index 00000000..c0fb6782 --- /dev/null +++ b/sources/Zenith.NET/Structs/ThreadGroupSize.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public struct ThreadGroupSize +{ + public uint X; + + public uint Y; + + public uint Z; +} From 4ffa21dfad55c09ee0165b9e84d34c7869c475c1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 21:13:50 +0800 Subject: [PATCH 222/466] Refactor ZenithCompiler to return ShaderDesc with reflection Refactored CompileFromFile and CompileFromSource to return ShaderDesc instead of byte array, using SlangCompiler.CompileWithReflection to include reflection data. Renamed entry to name, added ThreadGroupSize extraction, and simplified argument construction by replacing SlangCompilerArguments with Arguments. --- sources/Zenith.NET/ZenithCompiler.cs | 59 +++++++++++++++++----------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs index 683f7119..1f0ff002 100644 --- a/sources/Zenith.NET/ZenithCompiler.cs +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -4,37 +4,33 @@ namespace Zenith.NET; public static class ZenithCompiler { - public static byte[] CompileFromFile(GraphicsApi graphicsApi, string file, string entry, string[]? searchPaths = null) + public static ShaderDesc CompileFromFile(GraphicsApi graphicsApi, string file, string name, string[]? searchPaths = null) { - return SlangCompiler.Compile(SlangCompilerArguments(graphicsApi, file, entry, searchPaths)); + return new() + { + Name = name, + CodeBytes = SlangCompiler.CompileWithReflection([file, .. Arguments(graphicsApi, name, searchPaths)], out SlangReflection reflection), + ThreadGroupSize = ThreadGroupSize(reflection, name) + }; } - public static byte[] CompileFromSource(GraphicsApi graphicsApi, string source, string entry, string[]? searchPaths = null) + public static ShaderDesc CompileFromSource(GraphicsApi graphicsApi, string source, string name, string[]? searchPaths = null) { - return SlangCompiler.Compile(source, SlangCompilerArguments(graphicsApi, null, entry, searchPaths)); + return new() + { + Name = name, + CodeBytes = SlangCompiler.CompileWithReflection(source, Arguments(graphicsApi, name, searchPaths), out SlangReflection reflection), + ThreadGroupSize = ThreadGroupSize(reflection, name) + }; } - private static string[] SlangCompilerArguments(GraphicsApi graphicsApi, string? file, string entry, string[]? searchPaths) + private static string[] Arguments(GraphicsApi graphicsApi, string name, string[]? searchPaths) { - List arguments; - - if (file is null) - { - arguments = - [ - "-entry", entry, - "-matrix-layout-row-major" - ]; - } - else - { - arguments = - [ - file, - "-entry", entry, - "-matrix-layout-row-major" - ]; - } + List arguments = + [ + "-entry", name, + "-matrix-layout-row-major" + ]; if (searchPaths is not null) { @@ -63,4 +59,19 @@ private static string[] SlangCompilerArguments(GraphicsApi graphicsApi, string? return [.. arguments]; } + + private static ThreadGroupSize ThreadGroupSize(SlangReflection reflection, string name) + { + if (reflection.EntryPoints.FirstOrDefault(p => p.Name == name) is SlangEntryPoint entryPoint && entryPoint.ThreadGroupSize.Length is 3) + { + return new() + { + X = entryPoint.ThreadGroupSize[0], + Y = entryPoint.ThreadGroupSize[1], + Z = entryPoint.ThreadGroupSize[2] + }; + } + + return new(); + } } From 1651079e82ddb7ef9ed022c10d4ecb9ae579904d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 21:15:37 +0800 Subject: [PATCH 223/466] Simplify shader creation by removing anonymous objects Refactored shader creation calls to pass compiled shader code directly to CreateShader, eliminating the use of anonymous objects with Name and CodeBytes properties. This reduces verbosity and streamlines shader instantiation in PathTracingRenderer, RasterizationRenderer, and ImGuiRenderer. --- .../CornellBox/Renderers/PathTracingRenderer.cs | 6 +----- .../CornellBox/Renderers/RasterizationRenderer.cs | 12 ++---------- .../Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs | 12 ++---------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index b13f840e..e05e9aa8 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -69,11 +69,7 @@ public PathTracingRenderer() Residency = MemoryResidency.CpuWriteOnly }); - using Shader computeShader = App.Context.CreateShader(new() - { - Name = "CSMain", - CodeBytes = ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("PathTracing.slang"), "CSMain") - }); + using Shader computeShader = App.Context.CreateShader(ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("PathTracing.slang"), "CSMain")); pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index 058e3ad7..9db3e061 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -66,16 +66,8 @@ public RasterizationRenderer() Semantic = ElementSemantic.Normal }); - using Shader vertexShader = App.Context.CreateShader(new() - { - Name = "VSMain", - CodeBytes = ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("Rasterization.slang"), "VSMain") - }); - using Shader fragmentShader = App.Context.CreateShader(new() - { - Name = "FSMain", - CodeBytes = ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("Rasterization.slang"), "FSMain") - }); + using Shader vertexShader = App.Context.CreateShader(ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("Rasterization.slang"), "VSMain")); + using Shader fragmentShader = App.Context.CreateShader(ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("Rasterization.slang"), "FSMain")); pipeline = App.Context.CreateGraphicsPipeline(new() { diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs index e37b87c2..e0dc108a 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiRenderer.cs @@ -82,16 +82,8 @@ public ImGuiRenderer(GraphicsContext context, AttachmentFormats attachmentFormat sampler = context.CreateSampler(SamplerDesc.PointClamp()); - using Shader vertex = context.CreateShader(new() - { - Name = "VSMain", - CodeBytes = ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "VSMain") - }); - using Shader fragment = context.CreateShader(new() - { - Name = "FSMain", - CodeBytes = ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "FSMain") - }); + using Shader vertex = context.CreateShader(ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "VSMain")); + using Shader fragment = context.CreateShader(ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "FSMain")); InputLayout inputLayout = new(); inputLayout.Add(new() From 831893a0d4cab25095a2f64864d80b6704d41e68 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 14 Jun 2026 21:25:24 +0800 Subject: [PATCH 224/466] Implement Metal compute pipeline support Added MTLComputePipeline class for Metal compute pipeline integration, updated MTLGraphicsContext to instantiate it, introduced extension methods for casting, and replaced unsafe ResourceHandle casting with bit manipulation. --- sources/Zenith.NET.Metal/Extensions.cs | 7 ++-- .../Zenith.NET.Metal/MTLComputePipeline.cs | 34 +++++++++++++++++++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLComputePipeline.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index d86e45ab..37d116fb 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Runtime.CompilerServices; using Metal.NET; namespace Zenith.NET.Metal; @@ -31,7 +30,7 @@ internal void Success() { internal ResourceHandle ToHandle() { - return Unsafe.As(ref value); + return new((uint)value, (uint)(value >> 32)); } } @@ -69,6 +68,10 @@ internal MTLCommandQueue Metal() extension(ComputePipeline computePipeline) { + internal MTLComputePipeline Metal() + { + return (MTLComputePipeline)computePipeline; + } } extension(GraphicsPipeline graphicsPipeline) diff --git a/sources/Zenith.NET.Metal/MTLComputePipeline.cs b/sources/Zenith.NET.Metal/MTLComputePipeline.cs new file mode 100644 index 00000000..2af36f6e --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLComputePipeline.cs @@ -0,0 +1,34 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLComputePipeline : ComputePipeline +{ + public MTLComputePipelineState ComputePipelineState; + + public MTLComputePipeline(MTLGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) + { + MTL4ComputePipelineDescriptor descriptor = new() + { + ComputeFunctionDescriptor = desc.ComputeShader.Metal().Descriptor, + RequiredThreadsPerThreadgroup = new(desc.ComputeShader.Desc.ThreadGroupSize.X, desc.ComputeShader.Desc.ThreadGroupSize.Y, desc.ComputeShader.Desc.ThreadGroupSize.Z) + }; + + ComputePipelineState = context.Compiler.MakeComputePipelineState(descriptor, MTL4CompilerTaskOptions.Null, out NSError error); + error.Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + ComputePipelineState.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index e1de35ab..6d474a53 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -128,7 +128,7 @@ protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineD protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) { - throw new NotImplementedException(); + return new MTLComputePipeline(this, desc); } protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) From c0dd22033a609801b63bb0a9d7c0695686515773 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 11:53:42 +0800 Subject: [PATCH 225/466] Implement Metal mesh shading pipeline support Added MTLMeshShadingPipeline for Metal backend, including pipeline and depth-stencil state creation, blend/attachment format handling, and resource cleanup. Introduced Metal() extension methods for MeshShadingPipeline and Heap. Updated MTLGraphicsContext to instantiate MTLMeshShadingPipeline. --- sources/Zenith.NET.Metal/Extensions.cs | 8 ++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- .../MTLMeshShadingPipeline.cs | 104 ++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 37d116fb..8b57ebae 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -84,10 +84,18 @@ internal MTLGraphicsPipeline Metal() extension(Heap heap) { + internal MTLHeap Metal() + { + return (MTLHeap)heap; + } } extension(MeshShadingPipeline meshShadingPipeline) { + internal MTLMeshShadingPipeline Metal() + { + return (MTLMeshShadingPipeline)meshShadingPipeline; + } } extension(QueryHeap queryHeap) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 6d474a53..766dec2a 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -133,7 +133,7 @@ protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) { - throw new NotImplementedException(); + return new MTLMeshShadingPipeline(this, desc); } protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs new file mode 100644 index 00000000..445ee120 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs @@ -0,0 +1,104 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLMeshShadingPipeline : MeshShadingPipeline +{ + public MTLDepthStencilState DepthStencilState; + + public MTLRenderPipelineState RenderPipelineState; + + public MTLMeshShadingPipeline(MTLGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) + { + MTL4MeshRenderPipelineDescriptor descriptor = new() + { + MeshFunctionDescriptor = desc.MeshShader.Metal().Descriptor, + FragmentFunctionDescriptor = desc.FragmentShader.Metal().Descriptor, + RequiredThreadsPerMeshThreadgroup = new(desc.MeshShader.Desc.ThreadGroupSize.X, desc.MeshShader.Desc.ThreadGroupSize.Y, desc.MeshShader.Desc.ThreadGroupSize.Z) + }; + + if (desc.TaskShader is not null) + { + descriptor.ObjectFunctionDescriptor = desc.TaskShader.Metal().Descriptor; + descriptor.RequiredThreadsPerObjectThreadgroup = new(desc.TaskShader.Desc.ThreadGroupSize.X, desc.TaskShader.Desc.ThreadGroupSize.Y, desc.TaskShader.Desc.ThreadGroupSize.Z); + } + + // AttachmentFormats and RenderState + { + ColorAttachmentBlendState[] states = + [ + desc.RenderState.Blend.ColorAttachment0, + desc.RenderState.Blend.ColorAttachment1, + desc.RenderState.Blend.ColorAttachment2, + desc.RenderState.Blend.ColorAttachment3, + desc.RenderState.Blend.ColorAttachment4, + desc.RenderState.Blend.ColorAttachment5, + desc.RenderState.Blend.ColorAttachment6, + desc.RenderState.Blend.ColorAttachment7 + ]; + + descriptor.RasterSampleCount = MTLFormats.Metal(desc.AttachmentFormats.SampleCount); + descriptor.AlphaToCoverageState = desc.RenderState.Blend.IsAlphaToCoverageEnabled ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; + + for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) + { + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[0]; + + descriptor.ColorAttachments[(uint)i] = new() + { + PixelFormat = MTLFormats.Metal(desc.AttachmentFormats.ColorFormats[i]), + BlendingState = state.IsBlendingEnabled ? MTL4BlendState.Enabled : MTL4BlendState.Disabled, + SourceRGBBlendFactor = MTLFormats.Metal(state.SrcRgbFactor), + DestinationRGBBlendFactor = MTLFormats.Metal(state.DstRgbFactor), + RgbBlendOperation = MTLFormats.Metal(state.RgbOp), + SourceAlphaBlendFactor = MTLFormats.Metal(state.SrcAlphaFactor), + DestinationAlphaBlendFactor = MTLFormats.Metal(state.DstAlphaFactor), + AlphaBlendOperation = MTLFormats.Metal(state.AlphaOp), + WriteMask = MTLFormats.Metal(state.ColorWrites) + }; + } + + DepthStencilState = context.Device.MakeDepthStencilState(new() + { + DepthCompareFunction = desc.RenderState.DepthStencil.IsDepthEnabled ? MTLFormats.Metal(desc.RenderState.DepthStencil.DepthCompareOp) : MTLCompareFunction.Always, + IsDepthWriteEnabled = desc.RenderState.DepthStencil.IsDepthWriteEnabled, + FrontFaceStencil = desc.RenderState.DepthStencil.IsStencilEnabled ? new() + { + StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.CompareOp), + StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.FailOp), + DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.DepthFailOp), + DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.FrontFace.PassOp), + ReadMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask + } : MTLStencilDescriptor.Null, + BackFaceStencil = desc.RenderState.DepthStencil.IsStencilEnabled ? new() + { + StencilCompareFunction = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.CompareOp), + StencilFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.FailOp), + DepthFailureOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.DepthFailOp), + DepthStencilPassOperation = MTLFormats.Metal(desc.RenderState.DepthStencil.BackFace.PassOp), + ReadMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask + } : MTLStencilDescriptor.Null + }); + } + + RenderPipelineState = context.Compiler.MakeRenderPipelineState(descriptor, MTL4CompilerTaskOptions.Null, out NSError error); + error.Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + RenderPipelineState.Dispose(); + DepthStencilState.Dispose(); + } +} From 5464e8f28d5d0f73fc4e39f4105487b406f97bbf Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 14:20:01 +0800 Subject: [PATCH 226/466] Implement Metal query heap support with MTLQueryHeap Added MTLQueryHeap class for Metal backend, managing Metal counter heaps and result buffers. Updated MTLGraphicsContext to instantiate MTLQueryHeap. Introduced extension for casting QueryHeap to MTLQueryHeap. Improved resource cleanup and added methods for result handling and resource naming. --- sources/Zenith.NET.DirectX12/DXQueryHeap.cs | 1 - sources/Zenith.NET.Metal/Extensions.cs | 4 ++ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- sources/Zenith.NET.Metal/MTLQueryHeap.cs | 53 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLQueryHeap.cs diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index b0613f09..da3b98d2 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -48,7 +48,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { Buffer.Dispose(); - QueryHeap.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 8b57ebae..2b06b8cb 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -100,6 +100,10 @@ internal MTLMeshShadingPipeline Metal() extension(QueryHeap queryHeap) { + internal MTLQueryHeap Metal() + { + return (MTLQueryHeap)queryHeap; + } } extension(Sampler sampler) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 766dec2a..14fac39f 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -138,7 +138,7 @@ protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShading protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { - throw new NotImplementedException(); + return new MTLQueryHeap(this, desc); } protected override void Destroy() diff --git a/sources/Zenith.NET.Metal/MTLQueryHeap.cs b/sources/Zenith.NET.Metal/MTLQueryHeap.cs new file mode 100644 index 00000000..c7e36e35 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLQueryHeap.cs @@ -0,0 +1,53 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal unsafe class MTLQueryHeap : QueryHeap +{ + public MTL4CounterHeap CounterHeap; + + public MTLQueryHeap(MTLGraphicsContext context, QueryHeapDesc desc) : base(context, desc) + { + MTL4CounterHeapDescriptor descriptor = new() + { + Type = MTL4CounterHeapType.Timestamp, + Count = desc.Count + }; + + CounterHeap = context.Device.MakeCounterHeap(descriptor, out NSError error); + error.Success(); + + Buffer = new(context, new() + { + SizeInBytes = sizeof(ulong) * desc.Count, + Residency = MemoryResidency.CpuReadOnly + }); + } + + public MTLBuffer Buffer { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void GetResultsImpl(Span results, uint startIndex) + { + nint pointer = Buffer.Map(); + + new Span((void*)(pointer + (sizeof(ulong) * startIndex)), results.Length).CopyTo(results); + + Buffer.Unmap(); + } + + protected override void SetResourceName(string name) + { + CounterHeap.Label = name; + } + + protected override void Destroy() + { + Buffer.Dispose(); + CounterHeap.Dispose(); + } +} From bc3d76f55a71f3939a89091e48347f8d745ad26c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 15:35:45 +0800 Subject: [PATCH 227/466] Implement Metal swap chain (MTLSwapChain) support Added MTLSwapChain to manage Metal-specific swap chain operations, including initialization, resizing, presentation, and cleanup. Replaced NotImplementedException in CreateSwapChainImpl. Introduced extension method for casting SwapChain to MTLSwapChain. --- sources/Zenith.NET.Metal/Extensions.cs | 4 + .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- sources/Zenith.NET.Metal/MTLSwapChain.cs | 108 ++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 sources/Zenith.NET.Metal/MTLSwapChain.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 2b06b8cb..6380453d 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -124,6 +124,10 @@ internal MTLShader Metal() extension(SwapChain swapChain) { + internal MTLSwapChain Metal() + { + return (MTLSwapChain)swapChain; + } } extension(Texture texture) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 14fac39f..0c37989d 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -69,7 +69,7 @@ protected override void Initialize(bool useValidationLayer, protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) { - throw new NotImplementedException(); + return new MTLSwapChain(this, desc); } protected override Heap CreateHeapImpl(HeapDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs new file mode 100644 index 00000000..4cef0f6f --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -0,0 +1,108 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLSwapChain : SwapChain +{ + public CAMetalLayer MetalLayer = CAMetalLayer.Null; + + public CAMetalDrawable MetalDrawable = CAMetalDrawable.Null; + + private MTLTexture? drawable; + + public MTLSwapChain(MTLGraphicsContext context, SwapChainDesc desc) : base(context, desc) + { + Initialize(); + } + + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public override Texture Drawable + { + get + { + if (drawable is null || drawable.Desc.Format != Desc.Format || drawable.Desc.Width != Desc.Surface.Width || drawable.Desc.Height != Desc.Surface.Height) + { + TextureDesc desc = new() + { + Type = TextureType.Texture2D, + Format = Desc.Format, + Width = Desc.Surface.Width, + Height = Desc.Surface.Height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment + }; + + drawable?.Dispose(); + drawable = new(Context, desc, MetalDrawable.Texture.Retain()); + } + + if (drawable.Texture.NativePtr != MetalDrawable.Texture.NativePtr) + { + drawable.Texture.Dispose(); + drawable.Texture = MetalDrawable.Texture.Retain(); + } + + return drawable; + } + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void PresentImpl() + { + MetalDrawable.Present(); + MetalDrawable.Dispose(); + MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); + } + + protected override void ResizeImpl() + { + MetalLayer.DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height); + + MetalDrawable.Dispose(); + MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); + } + + protected override void RefreshImpl() + { + drawable?.Dispose(); + drawable = null; + + MetalDrawable.Dispose(); + MetalLayer.Dispose(); + + Initialize(); + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + drawable?.Dispose(); + + MetalDrawable.Dispose(); + MetalLayer.Dispose(); + } + + private void Initialize() + { + MetalLayer = new(Desc.Surface.NativeHandles[0], NativeObjectOwnership.Borrowed) + { + Device = Context.Device, + PixelFormat = MTLFormats.Metal(Desc.Format), + FramebufferOnly = false, + DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height) + }; + + MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); + } +} From 9b5091a9d9e1696235fbfdb31365e5f2586a78c7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 15:36:56 +0800 Subject: [PATCH 228/466] Dispose MetalDrawable after presenting in MTLSwapChain Ensure MetalDrawable is disposed immediately after presenting in PresentImpl(), releasing resources before acquiring the next drawable from MetalLayer. --- sources/Zenith.NET.Metal/MTLSwapChain.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 4cef0f6f..b30d089f 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -58,6 +58,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override void PresentImpl() { MetalDrawable.Present(); + MetalDrawable.Dispose(); MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); } From 9a1ae27ae2e916baf586bc37b53b5325375c6613 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 18:05:19 +0800 Subject: [PATCH 229/466] Update subresource index calculation in DXTexture Changed SubresourceIndex to use arrayLayer-major order, calculating index as (arrayLayer * mipLevels) + mipLevel instead of (mipLevel * arrayLayers) + arrayLayer. This aligns subresource indexing with expected conventions. --- sources/Zenith.NET.DirectX12/DXTexture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index db617487..07a01548 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -60,7 +60,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr Date: Mon, 15 Jun 2026 20:03:31 +0800 Subject: [PATCH 230/466] Update subresource index calculation for consistency Changed linear index calculation for subresources in DXTexture and Texture classes from (MipLevel * ArrayLayers) + ArrayLayer to MipLevel + (ArrayLayer * MipLevels), ensuring consistent addressing of subresources in memory. --- sources/Zenith.NET.DirectX12/DXTexture.cs | 2 +- sources/Zenith.NET/Texture.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index 07a01548..ccb19acb 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -60,7 +60,7 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr layouts[(subresource.MipLevel * Desc.ArrayLayers) + subresource.ArrayLayer]; - set => layouts[(subresource.MipLevel * Desc.ArrayLayers) + subresource.ArrayLayer] = value; + get => layouts[subresource.MipLevel + (subresource.ArrayLayer * Desc.MipLevels)]; + set => layouts[subresource.MipLevel + (subresource.ArrayLayer * Desc.MipLevels)] = value; } public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) From 54da18592d0cb31ad89e428e886c73727b4155db Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 20:06:15 +0800 Subject: [PATCH 231/466] Remove TextureSubresourceLayers struct definition Deleted the TextureSubresourceLayers struct and its associated fields (MipLevel, BaseArrayLayer, LayerCount) from TextureSubresourceLayers.cs, including the namespace declaration. --- sources/Zenith.NET/Structs/TextureSubresourceLayers.cs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 sources/Zenith.NET/Structs/TextureSubresourceLayers.cs diff --git a/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs b/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs deleted file mode 100644 index 6a00da7b..00000000 --- a/sources/Zenith.NET/Structs/TextureSubresourceLayers.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Zenith.NET; - -public struct TextureSubresourceLayers -{ - public uint MipLevel; - - public uint BaseArrayLayer; - - public uint LayerCount; -} From a404c4be1c68ca53866b3b80cd311b365de41171 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 20:18:28 +0800 Subject: [PATCH 232/466] Add external texture import support to GraphicsContext Introduced ImportTextureType enum and ImportTextureDesc struct to enable importing external textures (D3D11, D3D12, Metal, etc.). Extended GraphicsContext with ImportTexture method and abstract implementation. Updated SurfaceType enum formatting and removed D3D11Interop surface creation in favor of the new import mechanism. --- sources/Zenith.NET/Enums/ImportTextureType.cs | 14 ++++++++++++++ sources/Zenith.NET/Enums/SurfaceType.cs | 4 +--- sources/Zenith.NET/GraphicsContext.cs | 7 +++++++ sources/Zenith.NET/Structs/ImportTextureDesc.cs | 10 ++++++++++ sources/Zenith.NET/Structs/Surface.cs | 11 ----------- 5 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 sources/Zenith.NET/Enums/ImportTextureType.cs create mode 100644 sources/Zenith.NET/Structs/ImportTextureDesc.cs diff --git a/sources/Zenith.NET/Enums/ImportTextureType.cs b/sources/Zenith.NET/Enums/ImportTextureType.cs new file mode 100644 index 00000000..18642eb7 --- /dev/null +++ b/sources/Zenith.NET/Enums/ImportTextureType.cs @@ -0,0 +1,14 @@ +namespace Zenith.NET; + +public enum ImportTextureType +{ + D3D11, + + D3D12, + + Metal, + + OpaqueWin32, + + OpaqueFd +} diff --git a/sources/Zenith.NET/Enums/SurfaceType.cs b/sources/Zenith.NET/Enums/SurfaceType.cs index f3569d98..19fb52cc 100644 --- a/sources/Zenith.NET/Enums/SurfaceType.cs +++ b/sources/Zenith.NET/Enums/SurfaceType.cs @@ -10,7 +10,5 @@ public enum SurfaceType Android, - Apple, - - D3D11Interop + Apple } diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 578c0a34..1a6ef1d5 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -76,6 +76,11 @@ public Texture CreateTexture(TextureDesc desc) return CreateTextureImpl(desc); } + public Texture ImportTexture(ImportTextureDesc desc) + { + return ImportTextureImpl(desc); + } + public TextureView CreateTextureView(TextureViewDesc desc) { return CreateTextureViewImpl(desc); @@ -145,6 +150,8 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract Texture CreateTextureImpl(TextureDesc desc); + protected abstract Texture ImportTextureImpl(ImportTextureDesc desc); + protected abstract TextureView CreateTextureViewImpl(TextureViewDesc desc); protected abstract Sampler CreateSamplerImpl(SamplerDesc desc); diff --git a/sources/Zenith.NET/Structs/ImportTextureDesc.cs b/sources/Zenith.NET/Structs/ImportTextureDesc.cs new file mode 100644 index 00000000..e64a6242 --- /dev/null +++ b/sources/Zenith.NET/Structs/ImportTextureDesc.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public struct ImportTextureDesc +{ + public ImportTextureType Type; + + public nint Texture; + + public TextureDesc TextureDesc; +} diff --git a/sources/Zenith.NET/Structs/Surface.cs b/sources/Zenith.NET/Structs/Surface.cs index 8bef4112..5ec34028 100644 --- a/sources/Zenith.NET/Structs/Surface.cs +++ b/sources/Zenith.NET/Structs/Surface.cs @@ -64,15 +64,4 @@ public static Surface Apple(nint layer, uint width, uint height) Height = height }; } - - public static Surface D3D11Interop(nint sharedHandle, uint width, uint height) - { - return new() - { - Type = SurfaceType.D3D11Interop, - NativeHandles = [sharedHandle], - Width = width, - Height = height - }; - } } From b7d4b48ba5ae67bb0f49ebb5c3f59eea8864c149 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 20:40:10 +0800 Subject: [PATCH 233/466] Update ImportTextureType enum for Vulkan support Replaced OpaqueWin32 with VulkanWin32 and OpaqueFd with VulkanLinux in the ImportTextureType enum. Added a new VulkanAndroid value to extend platform support. --- sources/Zenith.NET/Enums/ImportTextureType.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET/Enums/ImportTextureType.cs b/sources/Zenith.NET/Enums/ImportTextureType.cs index 18642eb7..e951f07f 100644 --- a/sources/Zenith.NET/Enums/ImportTextureType.cs +++ b/sources/Zenith.NET/Enums/ImportTextureType.cs @@ -8,7 +8,9 @@ public enum ImportTextureType Metal, - OpaqueWin32, + VulkanWin32, - OpaqueFd + VulkanLinux, + + VulkanAndroid } From e8eda9badee92dfa0d0e4020c62689621ab5bcc3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 22:05:04 +0800 Subject: [PATCH 234/466] Refactor Target to Drawable and update format property Renamed the main rendering texture from Target to Drawable across the codebase for clarity. Updated related property and parameter names in Surface, RenderEventArgs, and ZenithView. Renamed ZenithViewHelper.Format to DrawableFormat and updated all references to ensure consistent and correct usage for texture creation and swap chain setup. --- .../Zenith.NET.Views.Avalonia/Surface.cs | 21 ++++++++++++++----- .../Zenith.NET.Views.Avalonia/ZenithView.cs | 2 +- .../Platforms/Android/MauiZenithView.cs | 2 +- .../Platforms/MacCatalyst/MauiZenithView.cs | 2 +- .../Platforms/iOS/MauiZenithView.cs | 2 +- .../Views/Zenith.NET.Views/RenderEventArgs.cs | 4 ++-- .../Zenith.NET.Views/ZenithViewHelper.cs | 2 +- 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 18533bd8..6b99fcc3 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -6,7 +6,18 @@ namespace Zenith.NET.Views.Avalonia; internal class Surface(GraphicsContext graphicsContext, uint width, uint height) : DisposableObject { - public Texture Target { get; } = graphicsContext.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.Format, width, height, 1, SampleCount.Count1)); + public Texture Drawable { get; } = graphicsContext.CreateTexture(new() + { + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + }); public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); @@ -33,22 +44,22 @@ public void Present() SliceStrideInBytes = (uint)(lockedFramebuffer.RowBytes * Height) }; - Target.Download(default, default, extent, data); + Drawable.Download(default, default, extent, data); } protected override void Destroy() { Bitmap.Dispose(); - Target.Dispose(); + Drawable.Dispose(); } private static AvaloniaPixelFormat ColorFormat() { - return ZenithViewHelper.Format switch + return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => AvaloniaPixelFormat.Rgba8888, PixelFormat.B8G8R8A8UNorm => AvaloniaPixelFormat.Bgra8888, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") }; } } diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index 195ab007..11a7af34 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs @@ -110,7 +110,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Target)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Drawable)); } void IZenithView.Present() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs index 817117ae..b660722b 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Android/MauiZenithView.cs @@ -26,7 +26,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Android(ANativeWindowFromSurface(JniEnvironment.EnvironmentPointer, Holder!.Surface!.Handle), width, height), - Format = ZenithViewHelper.Format + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs index 8abe924c..549fc268 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/MacCatalyst/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - Format = ZenithViewHelper.Format + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs index 8f4b3fb8..529ffa58 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/iOS/MauiZenithView.cs @@ -27,7 +27,7 @@ public void EnsureResources() swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() { Surface = Surface.Apple(Layer.Handle, width, height), - Format = ZenithViewHelper.Format + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views/RenderEventArgs.cs b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs index d7b7fffc..0699d909 100644 --- a/sources/Views/Zenith.NET.Views/RenderEventArgs.cs +++ b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs @@ -1,10 +1,10 @@ namespace Zenith.NET.Views; -public class RenderEventArgs(double deltaSeconds, double totalSeconds, Texture target) : EventArgs +public class RenderEventArgs(double deltaSeconds, double totalSeconds, Texture drawable) : EventArgs { public double DeltaSeconds { get; } = deltaSeconds; public double TotalSeconds { get; } = totalSeconds; - public Texture Target { get; } = target; + public Texture Drawable { get; } = drawable; } diff --git a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs index 1fb22c85..095fd105 100644 --- a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs +++ b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs @@ -2,5 +2,5 @@ public static class ZenithViewHelper { - public static PixelFormat Format { get; } = OperatingSystem.IsAndroid() ? PixelFormat.R8G8B8A8UNorm : PixelFormat.B8G8R8A8UNorm; + public static PixelFormat DrawableFormat { get; } = OperatingSystem.IsAndroid() ? PixelFormat.R8G8B8A8UNorm : PixelFormat.B8G8R8A8UNorm; } From 07af40434be86875091c7478e1f05acf6d165fe8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 22:06:09 +0800 Subject: [PATCH 235/466] Refactor D3DTexture to Surface and unify resource handling Replaces platform-specific D3DTexture classes with unified Surface abstractions for WinUI, MAUI, and WPF. Updates all usages to leverage Surface, centralizing Direct3D interop, keyed mutex, and shared handle logic. Switches to ZenithViewHelper.DrawableFormat for texture/swapchain formats. Adds ImportTextureImpl to GraphicsContext for D3D11 texture import. Refactors DXSwapChain for consistent backbuffer management. Improves maintainability and cross-platform rendering consistency. --- .../Platforms/Windows/MauiZenithView.cs | 33 +++----- .../Windows/{D3DTexture.cs => Surface.cs} | 36 +++++--- .../Views/Zenith.NET.Views.Maui/ZenithView.cs | 4 +- .../{D3DTexture.cs => Surface.cs} | 44 ++++++---- .../Views/Zenith.NET.Views.WPF/ZenithView.cs | 31 +++---- .../Zenith.NET.Views.WinForms/ZenithView.cs | 2 +- .../Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 21 +++-- .../ZenithView.WinUI.cs | 70 ++++++++-------- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 8 ++ sources/Zenith.NET.DirectX12/DXSwapChain.cs | 83 +++++++------------ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 5 ++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 5 ++ 12 files changed, 176 insertions(+), 166 deletions(-) rename sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/{D3DTexture.cs => Surface.cs} (76%) rename sources/Views/Zenith.NET.Views.WPF/{D3DTexture.cs => Surface.cs} (75%) diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs index e354210c..560a91de 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/MauiZenithView.cs @@ -5,8 +5,7 @@ namespace Zenith.NET.Views.Maui.Platforms.Windows; internal unsafe partial class MauiZenithView(ZenithViewHandler handler) : SwapChainPanel { - private D3DTexture? texture; - private SwapChain? swapChain; + private Surface? surface; public void EnsureResources() { @@ -18,49 +17,39 @@ public void EnsureResources() uint width = Math.Clamp((uint)Math.Ceiling(ActualWidth), 1, uint.MaxValue); uint height = Math.Clamp((uint)Math.Ceiling(ActualHeight), 1, uint.MaxValue); - if (texture is null || texture.Width != width || texture.Height != height || swapChain is null) + if (surface is null || surface.Width != width || surface.Height != height) { ReleaseResources(); - texture = new(width, height); + surface = new(handler.VirtualView.GraphicsContext, width, height); - swapChain = handler.VirtualView.GraphicsContext.CreateSwapChain(new() - { - Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - Format = ZenithViewHelper.Format - }); - - this.As().SetSwapChain(texture.SwapChain); + this.As().SetSwapChain(surface.SwapChain); } } public void Tick() { - if (texture is null || swapChain is null) + if (surface is null) { return; } - texture.AcquireSync(); + surface.AcquireSync(); handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.Drawable); + handler.VirtualView.OnRenderRequested(surface.Drawable); - texture.ReleaseSync(); + surface.ReleaseSync(); } public void Present() { - swapChain?.Present(); - texture?.Present(); + surface?.Present(); } public void ReleaseResources() { - swapChain?.Dispose(); - swapChain = null; - - texture?.Dispose(); - texture = null; + surface?.Dispose(); + surface = null; } } diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs similarity index 76% rename from sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs rename to sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs index 8a733f82..0ca01a0f 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -6,7 +6,7 @@ namespace Zenith.NET.Views.Maui.Platforms.Windows; -internal unsafe partial class D3DTexture : DisposableObject +internal unsafe partial class Surface : DisposableObject { [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); @@ -17,13 +17,11 @@ internal unsafe partial class D3DTexture : DisposableObject public ComPtr Mutex = new(); - public nint Handle; - public nint SharedHandle; private ulong key; - public D3DTexture(uint width, uint height) + public Surface(GraphicsContext graphicsContext, uint width, uint height) { SwapChainDesc1 swapChainDesc = new() { @@ -61,17 +59,31 @@ public D3DTexture(uint width, uint height) void* sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); - Handle = (nint)Texture.Handle; - SharedHandle = (nint)sharedHandle; - - Width = width; - Height = height; + Drawable = graphicsContext.ImportTexture(new() + { + Type = ImportTextureType.D3D11, + Texture = SharedHandle = (nint)sharedHandle, + TextureDesc = new() + { + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = Width = width, + Height = Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + } + }); } public uint Width { get; } public uint Height { get; } + public Texture Drawable { get; } + public void AcquireSync() { D3D.Success(Mutex.AcquireSync(key++, uint.MaxValue)); @@ -99,6 +111,8 @@ public void Present() protected override void Destroy() { + Drawable.Dispose(); + if (CloseHandle(SharedHandle) is 0) { Debug.WriteLine("Failed to close shared handle."); @@ -111,11 +125,11 @@ protected override void Destroy() private static Format ColorFormat() { - return ZenithViewHelper.Format switch + return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") }; } } \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs index 8e9aa35c..4a86d77c 100644 --- a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs @@ -86,9 +86,9 @@ internal void OnUpdateRequested() UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); } - internal void OnRenderRequested(Texture target) + internal void OnRenderRequested(Texture drawable) { - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, target)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, drawable)); } void IZenithView.UI(Action action) diff --git a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs similarity index 75% rename from sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs rename to sources/Views/Zenith.NET.Views.WPF/Surface.cs index 841670a9..7bfd8d0e 100644 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -10,7 +10,7 @@ namespace Zenith.NET.Views.WPF; -internal unsafe partial class D3DTexture : DisposableObject +internal unsafe partial class Surface : DisposableObject { [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); @@ -29,7 +29,7 @@ internal unsafe partial class D3DTexture : DisposableObject private ulong key; - public D3DTexture(uint width, uint height, D3DImage image) + public Surface(GraphicsContext graphicsContext, uint width, uint height) { void* sharedHandle = null; D3D.Success(D3D.D3D9DeviceEx.CreateTexture(width, @@ -66,18 +66,30 @@ public D3DTexture(uint width, uint height, D3DImage image) sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); - SharedHandle = (nint)sharedHandle; - - Width = width; - Height = height; - Image = image; + Drawable = graphicsContext.ImportTexture(new() + { + Type = ImportTextureType.D3D11, + Texture = SharedHandle = (nint)sharedHandle, + TextureDesc = new() + { + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = Width = width, + Height = Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + } + }); } public uint Width { get; } public uint Height { get; } - public D3DImage Image { get; } + public Texture Drawable { get; } public void AcquireSync() { @@ -89,10 +101,10 @@ public void ReleaseSync() D3D.Success(D3D11Mutex.ReleaseSync(key)); } - public void Present() + public void Present(D3DImage image) { - Image.Lock(); - Image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (nint)D3D9RenderSurface.Handle); + image.Lock(); + image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (nint)D3D9RenderSurface.Handle); AcquireSync(); @@ -101,12 +113,14 @@ public void Present() ReleaseSync(); - Image.AddDirtyRect(new(0, 0, (int)Width, (int)Height)); - Image.Unlock(); + image.AddDirtyRect(new(0, 0, (int)Width, (int)Height)); + image.Unlock(); } protected override void Destroy() { + Drawable.Dispose(); + if (CloseHandle(SharedHandle) is 0) { Debug.WriteLine("Failed to close shared handle."); @@ -121,11 +135,11 @@ protected override void Destroy() private static DXGIFormat ColorFormat() { - return ZenithViewHelper.Format switch + return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => DXGIFormat.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => DXGIFormat.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") }; } } \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index 294d4e7f..d2a71c11 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -17,8 +17,7 @@ public class ZenithView : Control, IZenithView private readonly D3DImage image; private readonly FrameScheduler scheduler; - private D3DTexture? texture; - private SwapChain? swapChain; + private Surface? surface; public ZenithView() { @@ -102,49 +101,39 @@ void IZenithView.EnsureResources() uint width = Math.Clamp((uint)Math.Ceiling(ActualWidth), 1, uint.MaxValue); uint height = Math.Clamp((uint)Math.Ceiling(ActualHeight), 1, uint.MaxValue); - if (texture is null || texture.Width != width || texture.Height != height || swapChain is null) + if (surface is null || surface.Width != width || surface.Height != height) { ((IZenithView)this).ReleaseResources(); - texture = new(width, height, image); - - swapChain = GraphicsContext.CreateSwapChain(new() - { - Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - Format = ZenithViewHelper.Format - }); + surface = new(GraphicsContext, width, height); } } void IZenithView.Tick() { - if (texture is null || swapChain is null) + if (surface is null) { return; } - texture.AcquireSync(); + surface.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.Drawable)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Drawable)); - texture.ReleaseSync(); + surface.ReleaseSync(); } void IZenithView.Present() { - swapChain?.Present(); - texture?.Present(); + surface?.Present(image); InvalidateVisual(); } void IZenithView.ReleaseResources() { - swapChain?.Dispose(); - swapChain = null; - - texture?.Dispose(); - texture = null; + surface?.Dispose(); + surface = null; } } diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs index 6255fab7..90ca0ce5 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs @@ -81,7 +81,7 @@ void IZenithView.EnsureResources() swapChain = GraphicsContext.CreateSwapChain(new() { Surface = Surface.Win32(Handle, width, height), - Format = ZenithViewHelper.Format + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index c3eaaae8..f9fd9593 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -35,7 +35,7 @@ void IZenithView.Tick() } UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Target)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Drawable)); } void IZenithView.Present() @@ -54,7 +54,18 @@ internal unsafe class Surface(GraphicsContext context, uint width, uint height) { private readonly byte[] pixels = new byte[width * height * 4]; - public Texture Target { get; } = context.CreateTexture(TextureDesc.Texture2D(ZenithViewHelper.Format, width, height, 1, SampleCount.Count1)); + public Texture Drawable { get; } = context.CreateTexture(new() + { + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + }); public WriteableBitmap Bitmap { get; } = new((int)width, (int)height); @@ -81,10 +92,10 @@ public void Present() SliceStrideInBytes = (uint)pixels.Length }; - Target.Download(default, default, extent, data); + Drawable.Download(default, default, extent, data); } - if (ZenithViewHelper.Format is PixelFormat.R8G8B8A8UNorm) + if (ZenithViewHelper.DrawableFormat is PixelFormat.R8G8B8A8UNorm) { for (int i = 0; i < pixels.Length; i += 4) { @@ -101,7 +112,7 @@ public void Present() protected override void Destroy() { Bitmap.Dispose(); - Target.Dispose(); + Drawable.Dispose(); } } #endif \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 53a61e15..20b88274 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -11,8 +11,7 @@ namespace Zenith.NET.Views.WinUI; public unsafe partial class ZenithView { - private D3DTexture? texture; - private SwapChain? swapChain; + private Surface? surface; void IZenithView.EnsureResources() { @@ -24,50 +23,40 @@ void IZenithView.EnsureResources() uint width = Math.Clamp((uint)Math.Ceiling(ActualWidth), 1, uint.MaxValue); uint height = Math.Clamp((uint)Math.Ceiling(ActualHeight), 1, uint.MaxValue); - if (texture is null || texture.Width != width || texture.Height != height || swapChain is null) + if (surface is null || surface.Width != width || surface.Height != height) { ((IZenithView)this).ReleaseResources(); - texture = new(width, height); + surface = new(GraphicsContext, width, height); - swapChain = GraphicsContext.CreateSwapChain(new() - { - Surface = Surface.D3D11Interop(texture.SharedHandle, width, height), - Format = ZenithViewHelper.Format - }); - - this.As().SetSwapChain(texture.SwapChain); + this.As().SetSwapChain(surface.SwapChain); } } void IZenithView.Tick() { - if (texture is null || swapChain is null) + if (surface is null) { return; } - texture.AcquireSync(); + surface.AcquireSync(); UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, swapChain.Drawable)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, surface.Drawable)); - texture.ReleaseSync(); + surface.ReleaseSync(); } void IZenithView.Present() { - swapChain?.Present(); - texture?.Present(); + surface?.Present(); } void IZenithView.ReleaseResources() { - swapChain?.Dispose(); - swapChain = null; - - texture?.Dispose(); - texture = null; + surface?.Dispose(); + surface = null; } } @@ -110,8 +99,7 @@ public static void Success(int result) } } } - -internal unsafe partial class D3DTexture : DisposableObject +internal unsafe partial class Surface : DisposableObject { [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); @@ -122,13 +110,11 @@ internal unsafe partial class D3DTexture : DisposableObject public ComPtr Mutex = new(); - public nint Handle; - public nint SharedHandle; private ulong key; - public D3DTexture(uint width, uint height) + public Surface(GraphicsContext graphicsContext, uint width, uint height) { SwapChainDesc1 swapChainDesc = new() { @@ -166,17 +152,31 @@ public D3DTexture(uint width, uint height) void* sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); - Handle = (nint)Texture.Handle; - SharedHandle = (nint)sharedHandle; - - Width = width; - Height = height; + Drawable = graphicsContext.ImportTexture(new() + { + Type = ImportTextureType.D3D11, + Texture = SharedHandle = (nint)sharedHandle, + TextureDesc = new() + { + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = Width = width, + Height = Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + } + }); } public uint Width { get; } public uint Height { get; } + public Texture Drawable { get; } + public void AcquireSync() { D3D.Success(Mutex.AcquireSync(key++, uint.MaxValue)); @@ -204,6 +204,8 @@ public void Present() protected override void Destroy() { + Drawable.Dispose(); + if (CloseHandle(SharedHandle) is 0) { Debug.WriteLine("Failed to close shared handle."); @@ -216,11 +218,11 @@ protected override void Destroy() private static Format ColorFormat() { - return ZenithViewHelper.Format switch + return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.Format} is not supported.") + _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") }; } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 808e4ff4..219f11d7 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -199,6 +199,14 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new DXTexture(this, desc); } + protected override Texture ImportTextureImpl(ImportTextureDesc desc) + { + ComPtr resource = new(); + Device.OpenSharedHandle((void*)desc.Texture, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); + + return new DXTexture(this, desc.TextureDesc, resource); + } + protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { return new DXTextureView(this, desc); diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 4e1ff128..00922031 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -6,9 +6,10 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXSwapChain : SwapChain { + private readonly DXTexture[] textures = new DXTexture[3]; + public ComPtr SwapChain; - private DXTexture[] textures = []; private uint index; public DXSwapChain(DXGraphicsContext context, SwapChainDesc desc) : base(context, desc) @@ -28,22 +29,16 @@ public override nint GetNativeObject(NativeObjectType type) protected override void PresentImpl() { - if (SwapChain.Handle is not null) - { - SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); + SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); - index = SwapChain.GetCurrentBackBufferIndex(); - } + index = SwapChain.GetCurrentBackBufferIndex(); } protected override void ResizeImpl() { DestroyTextures(); - if (SwapChain.Handle is not null) - { - SwapChain.ResizeBuffers((uint)textures.Length, Desc.Surface.Width, Desc.Surface.Height, DXFormats.DirectX12(Desc.Format), (uint)SwapChainFlag.AllowTearing).Success(); - } + SwapChain.ResizeBuffers((uint)textures.Length, Desc.Surface.Width, Desc.Surface.Height, DXFormats.DirectX12(Desc.Format), (uint)SwapChainFlag.AllowTearing).Success(); CreateTextures(); } @@ -69,42 +64,30 @@ protected override void Destroy() private void CreateSwapChain() { - if (Desc.Surface.Type is not SurfaceType.D3D11Interop) + SwapChainDesc1 swapChainDesc = new() { - const uint BufferCount = 3; - - SwapChainDesc1 swapChainDesc = new() - { - Width = Desc.Surface.Width, - Height = Desc.Surface.Height, - Format = DXFormats.DirectX12(Desc.Format), - SampleDesc = new() { Count = 1 }, - BufferUsage = DXGI.UsageRenderTargetOutput, - BufferCount = BufferCount, - SwapEffect = SwapEffect.FlipDiscard, - AlphaMode = AlphaMode.Ignore, - Flags = (uint)SwapChainFlag.AllowTearing - }; - - Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsQueue.DirectX12().CommandQueue.Handle, - Desc.Surface.NativeHandles[0], - &swapChainDesc, - default(SwapChainFullscreenDesc*), - default(IDXGIOutput*), - (IDXGISwapChain1**)SwapChain.GetAddressOf()).Success(); - - textures = new DXTexture[BufferCount]; - } - else - { - textures = new DXTexture[1]; - } + Width = Desc.Surface.Width, + Height = Desc.Surface.Height, + Format = DXFormats.DirectX12(Desc.Format), + SampleDesc = new() { Count = 1 }, + BufferUsage = DXGI.UsageRenderTargetOutput, + BufferCount = (uint)textures.Length, + SwapEffect = SwapEffect.FlipDiscard, + AlphaMode = AlphaMode.Ignore, + Flags = (uint)SwapChainFlag.AllowTearing + }; + + Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsQueue.DirectX12().CommandQueue.Handle, + Desc.Surface.NativeHandles[0], + &swapChainDesc, + default(SwapChainFullscreenDesc*), + default(IDXGIOutput*), + (IDXGISwapChain1**)SwapChain.GetAddressOf()).Success(); } private void DestroySwapChain() { SwapChain.Dispose(); - SwapChain = default; index = 0; } @@ -124,25 +107,15 @@ private void CreateTextures() Usages = TextureUsages.ColorAttachment }; - if (SwapChain.Handle is not null) - { - for (int i = 0; i < textures.Length; i++) - { - ComPtr resource = new(); - SwapChain.GetBuffer((uint)i, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); - - textures[i] = new(Context, desc, resource); - } - - index = SwapChain.GetCurrentBackBufferIndex(); - } - else + for (int i = 0; i < textures.Length; i++) { ComPtr resource = new(); - Context.Device.OpenSharedHandle((void*)Desc.Surface.NativeHandles[0], SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); + SwapChain.GetBuffer((uint)i, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); - textures[0] = new(Context, desc, resource); + textures[i] = new(Context, desc, resource); } + + index = SwapChain.GetCurrentBackBufferIndex(); } private void DestroyTextures() diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 0c37989d..9835dbc5 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -106,6 +106,11 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new MTLTexture(this, desc); } + protected override Texture ImportTextureImpl(ImportTextureDesc desc) + { + throw new NotImplementedException(); + } + protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { return new MTLTextureView(this, desc); diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index dc2f4c76..4c35e134 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -56,6 +56,11 @@ protected override Texture CreateTextureImpl(TextureDesc desc) throw new NotImplementedException(); } + protected override Texture ImportTextureImpl(ImportTextureDesc desc) + { + throw new NotImplementedException(); + } + protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { throw new NotImplementedException(); From 60d61cf957cc88848699a7cbf488556bcaa3d6f9 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 22:08:39 +0800 Subject: [PATCH 236/466] Refactor to use DrawableFormat and handle unknown formats Replaces ColorFormat with DrawableFormat in Surface and related code. DrawableFormat now returns a default value for unsupported pixel formats instead of throwing exceptions, improving robustness when handling unknown formats. Affects bitmap creation, swap chain, and texture descriptors. --- sources/Views/Zenith.NET.Views.Avalonia/Surface.cs | 6 +++--- .../Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs | 8 ++++---- sources/Views/Zenith.NET.Views.WPF/Surface.cs | 6 +++--- sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 6b99fcc3..aee27462 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -19,7 +19,7 @@ internal class Surface(GraphicsContext graphicsContext, uint width, uint height) Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst }); - public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), ColorFormat(), AlphaFormat.Premul); + public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), DrawableFormat(), AlphaFormat.Premul); public uint Width { get; } = width; @@ -53,13 +53,13 @@ protected override void Destroy() Drawable.Dispose(); } - private static AvaloniaPixelFormat ColorFormat() + private static AvaloniaPixelFormat DrawableFormat() { return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => AvaloniaPixelFormat.Rgba8888, PixelFormat.B8G8R8A8UNorm => AvaloniaPixelFormat.Bgra8888, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") + _ => default }; } } 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 0ca01a0f..68d7709f 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -27,7 +27,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) { Width = width, Height = height, - Format = ColorFormat(), + Format = DrawableFormat(), SampleDesc = new() { Count = 1 }, BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = 3, @@ -43,7 +43,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) Height = height, MipLevels = 1, ArraySize = 1, - Format = ColorFormat(), + Format = DrawableFormat(), SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) @@ -123,13 +123,13 @@ protected override void Destroy() SwapChain.Dispose(); } - private static Format ColorFormat() + private static Format DrawableFormat() { return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") + _ => default }; } } \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.WPF/Surface.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs index 7bfd8d0e..a7006fe4 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Surface.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -50,7 +50,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) Height = height, MipLevels = 1, ArraySize = 1, - Format = ColorFormat(), + Format = DrawableFormat(), SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) @@ -133,13 +133,13 @@ protected override void Destroy() D3D9RenderTarget.Dispose(); } - private static DXGIFormat ColorFormat() + private static DXGIFormat DrawableFormat() { return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => DXGIFormat.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => DXGIFormat.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") + _ => default }; } } \ No newline at end of file diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 20b88274..0185d7d2 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -120,7 +120,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) { Width = width, Height = height, - Format = ColorFormat(), + Format = DrawableFormat(), SampleDesc = new() { Count = 1 }, BufferUsage = DXGI.UsageRenderTargetOutput, BufferCount = 3, @@ -136,7 +136,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) Height = height, MipLevels = 1, ArraySize = 1, - Format = ColorFormat(), + Format = DrawableFormat(), SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) @@ -216,13 +216,13 @@ protected override void Destroy() SwapChain.Dispose(); } - private static Format ColorFormat() + private static Format DrawableFormat() { return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.DrawableFormat} is not supported.") + _ => default }; } } From ccee8e7590f30698c80ae7eb77868b3091d03115 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 22:23:08 +0800 Subject: [PATCH 237/466] Expand swapchain texture usages and improve Metal import Expanded texture usages for swapchain textures to include Sampled, Storage, CopySrc, and CopyDst in both DX and Metal backends. Simplified Metal swapchain drawable update logic and implemented shared texture import in MTLGraphicsContext. --- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 2 +- sources/Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- sources/Zenith.NET.Metal/MTLSwapChain.cs | 10 ++-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 00922031..972105f0 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -104,7 +104,7 @@ private void CreateTextures() MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst }; for (int i = 0; i < textures.Length; i++) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 9835dbc5..f5a2da10 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -108,7 +108,7 @@ protected override Texture CreateTextureImpl(TextureDesc desc) protected override Texture ImportTextureImpl(ImportTextureDesc desc) { - throw new NotImplementedException(); + return new MTLTexture(this, desc.TextureDesc, Device.MakeSharedTexture(new MTLSharedTextureHandle(desc.Texture, NativeObjectOwnership.Borrowed))); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index b30d089f..0cdc9eb1 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -21,7 +21,7 @@ public override Texture Drawable { get { - if (drawable is null || drawable.Desc.Format != Desc.Format || drawable.Desc.Width != Desc.Surface.Width || drawable.Desc.Height != Desc.Surface.Height) + if (drawable is null || drawable.Texture.NativePtr != MetalDrawable.Texture.NativePtr) { TextureDesc desc = new() { @@ -33,19 +33,13 @@ public override Texture Drawable MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment + Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst }; drawable?.Dispose(); drawable = new(Context, desc, MetalDrawable.Texture.Retain()); } - if (drawable.Texture.NativePtr != MetalDrawable.Texture.NativePtr) - { - drawable.Texture.Dispose(); - drawable.Texture = MetalDrawable.Texture.Retain(); - } - return drawable; } } From 1fddcfcd0ec256e6605f48c2331efcb6c906bfb7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 22:30:03 +0800 Subject: [PATCH 238/466] Update texture usages and simplify MTLHeap creation Removed Sampled, Storage, and CopySrc usages from texture creation, retaining only ColorAttachment and CopyDst. Also removed redundant context.Register call in MTLHeap for cleaner heap initialization. --- sources/Views/Zenith.NET.Views.Avalonia/Surface.cs | 2 +- .../Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs | 2 +- sources/Views/Zenith.NET.Views.WPF/Surface.cs | 2 +- sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 2 +- sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 2 +- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 2 +- sources/Zenith.NET.Metal/MTLHeap.cs | 4 ++-- sources/Zenith.NET.Metal/MTLSwapChain.cs | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index aee27462..6be17788 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -16,7 +16,7 @@ internal class Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst }); public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), DrawableFormat(), AlphaFormat.Premul); 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 68d7709f..2f5f05ad 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -73,7 +73,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst } }); } diff --git a/sources/Views/Zenith.NET.Views.WPF/Surface.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs index a7006fe4..4a814bb8 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Surface.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -80,7 +80,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst } }); } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index f9fd9593..6266b47f 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -64,7 +64,7 @@ internal unsafe class Surface(GraphicsContext context, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst }); public WriteableBitmap Bitmap { get; } = new((int)width, (int)height); diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 0185d7d2..6142e705 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -166,7 +166,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst } }); } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 972105f0..ec0b7bb0 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -104,7 +104,7 @@ private void CreateTextures() MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst }; for (int i = 0; i < textures.Length; i++) diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs index 5af72b51..2009f5a8 100644 --- a/sources/Zenith.NET.Metal/MTLHeap.cs +++ b/sources/Zenith.NET.Metal/MTLHeap.cs @@ -8,12 +8,12 @@ internal class MTLHeap : Heap public MTLHeap(MTLGraphicsContext context, HeapDesc desc) : base(context, desc) { - context.Register(Heap = context.Device.MakeHeap(new() + Heap = context.Device.MakeHeap(new() { Size = (nuint)desc.SizeInBytes, ResourceOptions = MTLFormats.Metal(desc.Residency), Type = MTLHeapType.Placement - })); + }); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 0cdc9eb1..56f8ba29 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -33,7 +33,7 @@ public override Texture Drawable MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment | TextureUsages.CopySrc | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst }; drawable?.Dispose(); From f1518b5a50de91ca8dca8b325e7b89ae1005168a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 15 Jun 2026 22:43:18 +0800 Subject: [PATCH 239/466] Update swap chain queue usage and add drawable signaling Refactor DXSwapChain to use GraphicsCommandQueue for swap chain creation. In MTLSwapChain, signal the drawable before presenting to improve synchronization. --- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 2 +- sources/Zenith.NET.Metal/MTLSwapChain.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index ec0b7bb0..6367c3e5 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -77,7 +77,7 @@ private void CreateSwapChain() Flags = (uint)SwapChainFlag.AllowTearing }; - Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsQueue.DirectX12().CommandQueue.Handle, + Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsCommandQueue.Handle, Desc.Surface.NativeHandles[0], &swapChainDesc, default(SwapChainFullscreenDesc*), diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 56f8ba29..4baba1c6 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -51,6 +51,8 @@ public override nint GetNativeObject(NativeObjectType type) protected override void PresentImpl() { + Context.GraphicsCommandQueue.SignalDrawable(MetalDrawable); + MetalDrawable.Present(); MetalDrawable.Dispose(); From d3b79bb338e28e5d5c80e3eaee47dcc901d4e30b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 12:58:42 +0800 Subject: [PATCH 240/466] Refactor native texture import API in graphics context Replaces ImportTexture/ImportTextureDesc with an overloaded CreateTexture method that accepts TextureDesc, NativeTextureType, and a native handle. Removes ImportTextureType and ImportTextureDesc. Updates all platform-specific and abstract implementations to use the new API. Introduces NativeTextureType enum for clarity. --- .../Platforms/Windows/Surface.cs | 27 ++++++++----------- sources/Views/Zenith.NET.Views.WPF/Surface.cs | 27 ++++++++----------- .../ZenithView.WinUI.cs | 27 ++++++++----------- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 6 ++--- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 4 +-- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 +- ...ortTextureType.cs => NativeTextureType.cs} | 2 +- sources/Zenith.NET/GraphicsContext.cs | 6 ++--- .../Zenith.NET/Structs/ImportTextureDesc.cs | 10 ------- 9 files changed, 43 insertions(+), 68 deletions(-) rename sources/Zenith.NET/Enums/{ImportTextureType.cs => NativeTextureType.cs} (80%) delete mode 100644 sources/Zenith.NET/Structs/ImportTextureDesc.cs 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 2f5f05ad..00158203 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -59,23 +59,18 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) void* sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); - Drawable = graphicsContext.ImportTexture(new() + Drawable = graphicsContext.CreateTexture(new() { - Type = ImportTextureType.D3D11, - Texture = SharedHandle = (nint)sharedHandle, - TextureDesc = new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.DrawableFormat, - Width = Width = width, - Height = Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst - } - }); + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = Width = width, + Height = Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + }, NativeTextureType.D3D11, SharedHandle = (nint)sharedHandle); } public uint Width { get; } diff --git a/sources/Views/Zenith.NET.Views.WPF/Surface.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs index 4a814bb8..2f215294 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Surface.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -66,23 +66,18 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); - Drawable = graphicsContext.ImportTexture(new() + Drawable = graphicsContext.CreateTexture(new() { - Type = ImportTextureType.D3D11, - Texture = SharedHandle = (nint)sharedHandle, - TextureDesc = new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.DrawableFormat, - Width = Width = width, - Height = Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst - } - }); + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = Width = width, + Height = Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + }, NativeTextureType.D3D11, SharedHandle = (nint)sharedHandle); } public uint Width { get; } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 6142e705..a2b9917f 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -152,23 +152,18 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) void* sharedHandle = null; D3D.Success(resource.CreateSharedHandle(default(SecurityAttributes*), DXGI.SharedResourceRead | DXGI.SharedResourceWrite, default(char*), &sharedHandle)); - Drawable = graphicsContext.ImportTexture(new() + Drawable = graphicsContext.CreateTexture(new() { - Type = ImportTextureType.D3D11, - Texture = SharedHandle = (nint)sharedHandle, - TextureDesc = new() - { - Type = TextureType.Texture2D, - Format = ZenithViewHelper.DrawableFormat, - Width = Width = width, - Height = Height = height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst - } - }); + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = Width = width, + Height = Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + }, NativeTextureType.D3D11, SharedHandle = (nint)sharedHandle); } public uint Width { get; } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 219f11d7..9d1f9ce8 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -199,12 +199,12 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new DXTexture(this, desc); } - protected override Texture ImportTextureImpl(ImportTextureDesc desc) + protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { ComPtr resource = new(); - Device.OpenSharedHandle((void*)desc.Texture, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); + Device.OpenSharedHandle((void*)nativeTexture, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); - return new DXTexture(this, desc.TextureDesc, resource); + return new DXTexture(this, desc, resource); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index f5a2da10..338d80ee 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -106,9 +106,9 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new MTLTexture(this, desc); } - protected override Texture ImportTextureImpl(ImportTextureDesc desc) + protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { - return new MTLTexture(this, desc.TextureDesc, Device.MakeSharedTexture(new MTLSharedTextureHandle(desc.Texture, NativeObjectOwnership.Borrowed))); + return new MTLTexture(this, desc, Device.MakeSharedTexture(new MTLSharedTextureHandle(nativeTexture, NativeObjectOwnership.Borrowed))); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 4c35e134..1e9a52eb 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -56,7 +56,7 @@ protected override Texture CreateTextureImpl(TextureDesc desc) throw new NotImplementedException(); } - protected override Texture ImportTextureImpl(ImportTextureDesc desc) + protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { throw new NotImplementedException(); } diff --git a/sources/Zenith.NET/Enums/ImportTextureType.cs b/sources/Zenith.NET/Enums/NativeTextureType.cs similarity index 80% rename from sources/Zenith.NET/Enums/ImportTextureType.cs rename to sources/Zenith.NET/Enums/NativeTextureType.cs index e951f07f..c70d595f 100644 --- a/sources/Zenith.NET/Enums/ImportTextureType.cs +++ b/sources/Zenith.NET/Enums/NativeTextureType.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum ImportTextureType +public enum NativeTextureType { D3D11, diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 1a6ef1d5..31ef7663 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -76,9 +76,9 @@ public Texture CreateTexture(TextureDesc desc) return CreateTextureImpl(desc); } - public Texture ImportTexture(ImportTextureDesc desc) + public Texture CreateTexture(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { - return ImportTextureImpl(desc); + return CreateTextureImpl(desc, nativeTextureType, nativeTexture); } public TextureView CreateTextureView(TextureViewDesc desc) @@ -150,7 +150,7 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract Texture CreateTextureImpl(TextureDesc desc); - protected abstract Texture ImportTextureImpl(ImportTextureDesc desc); + protected abstract Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture); protected abstract TextureView CreateTextureViewImpl(TextureViewDesc desc); diff --git a/sources/Zenith.NET/Structs/ImportTextureDesc.cs b/sources/Zenith.NET/Structs/ImportTextureDesc.cs deleted file mode 100644 index e64a6242..00000000 --- a/sources/Zenith.NET/Structs/ImportTextureDesc.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Zenith.NET; - -public struct ImportTextureDesc -{ - public ImportTextureType Type; - - public nint Texture; - - public TextureDesc TextureDesc; -} From 5048f25125dc7a5babcc9f2e0931d8988e16877f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 13:39:39 +0800 Subject: [PATCH 241/466] Handle nullable CommandQueue in CommandSubmission Allow CommandSubmission.Queue to be nullable and update all usages to check for null before waiting. Refactor App.cs to track and wait for the last submission for proper synchronization. Improve ImGui UI code clarity. Update DXCommandQueue and MTLCommandQueue to skip null queues in WaitImpl, preventing potential null reference errors. --- sources/Experiments/CornellBox/App.cs | 72 ++++++++++--------- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 5 ++ sources/Zenith.NET.Metal/MTLCommandQueue.cs | 5 ++ .../Zenith.NET/Structs/CommandSubmission.cs | 6 +- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index c57d5b0c..54b7ef4e 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -114,6 +114,8 @@ static App() public static void Run() { + CommandSubmission lastSubmission = default; + window.Update += delta => { if (Width is 0 || Height is 0) @@ -127,52 +129,47 @@ public static void Run() imGui.Update(delta, width, height); camera.Update(delta, width, height); - // ImGui - { - ImGui.GetBackgroundDrawList().AddImage(imGui.Binding(activeRenderer.Color), new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y)); + ImGui.GetBackgroundDrawList().AddImage(imGui.Binding(activeRenderer.Color), new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y)); - ImGui.SetNextWindowPos(new(10, 10), ImGuiCond.FirstUseEver); - if (ImGui.Begin("Cornell Box", ImGuiWindowFlags.AlwaysAutoResize)) - { - ImGui.Text($"GraphicsApi: {Context.GraphicsApi}"); - ImGui.Text(Context.Capabilities.DeviceName); + ImGui.SetNextWindowPos(new(10, 10), ImGuiCond.FirstUseEver); + if (ImGui.Begin("Cornell Box", ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.Text($"GraphicsApi: {Context.GraphicsApi}"); + ImGui.Text(Context.Capabilities.DeviceName); - ImGui.Separator(); + ImGui.Separator(); - ImGui.Text("Render Mode:"); + ImGui.Text("Render Mode:"); - if (Context.Capabilities.RayTracingSupported) + if (Context.Capabilities.RayTracingSupported) + { + if (ImGui.RadioButton("Path Tracing", currentMode is 0) && currentMode is not 0) { - if (ImGui.RadioButton("Path Tracing", currentMode is 0) && currentMode is not 0) - { - pathTracer!.FrameCount = 0; - - currentMode = 0; - activeRenderer = pathTracer; - } + pathTracer!.FrameCount = 0; - ImGui.SameLine(); + currentMode = 0; + activeRenderer = pathTracer; } - if (ImGui.RadioButton("Rasterization", currentMode is 1) && currentMode is not 1) - { - currentMode = 1; - activeRenderer = rasterizer; - } + ImGui.SameLine(); + } - ImGui.Separator(); + if (ImGui.RadioButton("Rasterization", currentMode is 1) && currentMode is not 1) + { + currentMode = 1; + activeRenderer = rasterizer; + } - if (currentMode is 0 && pathTracer is not null) - { - ImGui.Text($"SPP: {pathTracer.FrameCount}"); - } + ImGui.Separator(); - ImGui.Text($"FPS: {ImGui.GetIO().Framerate:F1}"); + if (currentMode is 0 && pathTracer is not null) + { + ImGui.Text($"SPP: {pathTracer.FrameCount}"); } - ImGui.End(); - } - activeRenderer.Update(camera); + ImGui.Text($"FPS: {ImGui.GetIO().Framerate:F1}"); + } + ImGui.End(); }; window.Render += _ => @@ -182,8 +179,11 @@ public static void Run() return; } + lastSubmission.Wait(); + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); + activeRenderer.Update(camera); activeRenderer.Render(commandBuffer); commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment); @@ -192,7 +192,7 @@ public static void Run() commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Present); - swapChain.Present(commandBuffer.Submit()).Wait(); + lastSubmission = swapChain.Present(commandBuffer.Submit()); }; window.Resize += _ => @@ -202,6 +202,8 @@ public static void Run() return; } + lastSubmission.Wait(); + pathTracer?.Resize(Width, Height); rasterizer.Resize(Width, Height); swapChain.Resize(Width, Height); @@ -209,6 +211,8 @@ public static void Run() window.Run(); + lastSubmission.Wait(); + pathTracer?.Dispose(); rasterizer.Dispose(); imGui.Dispose(); diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 5097bd47..9655f6d8 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -57,6 +57,11 @@ protected override void WaitImpl(ReadOnlySpan submissions) { foreach (CommandSubmission submission in submissions) { + if (submission.Queue is null) + { + continue; + } + CommandQueue.Wait(submission.Queue.DirectX12().Fence, submission.Value).Success(); } } diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index 6395ef5b..7dd9020a 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -42,6 +42,11 @@ protected override void WaitImpl(ReadOnlySpan submissions) { foreach (CommandSubmission submission in submissions) { + if (submission.Queue is null) + { + continue; + } + CommandQueue.WaitForEvent(submission.Queue.Metal().Event, submission.Value); } } diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs index 9311fc73..e2b60e27 100644 --- a/sources/Zenith.NET/Structs/CommandSubmission.cs +++ b/sources/Zenith.NET/Structs/CommandSubmission.cs @@ -1,13 +1,13 @@ namespace Zenith.NET; -public readonly struct CommandSubmission(CommandQueue queue, ulong value) +public readonly struct CommandSubmission(CommandQueue? queue, ulong value) { - public readonly CommandQueue Queue = queue; + public readonly CommandQueue? Queue = queue; public readonly ulong Value = value; public void Wait() { - Queue.Wait(Value); + Queue?.Wait(Value); } } From bf1f58269efa01c2d98613feca2175f7f5c69c72 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 13:43:48 +0800 Subject: [PATCH 242/466] Remove SignalDrawable call from PresentImpl in MTLSwapChain Removed the call to Context.GraphicsCommandQueue.SignalDrawable(MetalDrawable) in the PresentImpl() method of MTLSwapChain. Now, only MetalDrawable.Present() is invoked before disposing and acquiring the next drawable. This simplifies the present logic and may address unnecessary signaling. --- sources/Zenith.NET.Metal/MTLSwapChain.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 4baba1c6..56f8ba29 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -51,8 +51,6 @@ public override nint GetNativeObject(NativeObjectType type) protected override void PresentImpl() { - Context.GraphicsCommandQueue.SignalDrawable(MetalDrawable); - MetalDrawable.Present(); MetalDrawable.Dispose(); From b016adba65f265586da7c055ed8592f0d452188a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 14:20:27 +0800 Subject: [PATCH 243/466] Refactor NativeTextureType enum and texture handling Refactor NativeTextureType to use explicit, platform-specific names and add new types for cross-platform support. Update texture creation logic in Surface, ZenithView.WinUI, and MTLGraphicsContext to use the new enum values and improve platform handling. Enhances clarity and maintainability. --- .../Platforms/Windows/Surface.cs | 2 +- sources/Views/Zenith.NET.Views.WPF/Surface.cs | 2 +- .../Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 2 +- sources/Zenith.NET.Metal/MTLGraphicsContext.cs | 7 ++++++- sources/Zenith.NET/Enums/NativeTextureType.cs | 14 ++++++++------ 5 files changed, 17 insertions(+), 10 deletions(-) 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 00158203..10184344 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -70,7 +70,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) ArrayLayers = 1, SampleCount = SampleCount.Count1, Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst - }, NativeTextureType.D3D11, SharedHandle = (nint)sharedHandle); + }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } public uint Width { get; } diff --git a/sources/Views/Zenith.NET.Views.WPF/Surface.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs index 2f215294..ddebb675 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Surface.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -77,7 +77,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) ArrayLayers = 1, SampleCount = SampleCount.Count1, Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst - }, NativeTextureType.D3D11, SharedHandle = (nint)sharedHandle); + }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } public uint Width { get; } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index a2b9917f..ff46b8ad 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -163,7 +163,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) ArrayLayers = 1, SampleCount = SampleCount.Count1, Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst - }, NativeTextureType.D3D11, SharedHandle = (nint)sharedHandle); + }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } public uint Width { get; } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 338d80ee..a9ba83dc 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -108,7 +108,12 @@ protected override Texture CreateTextureImpl(TextureDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { - return new MTLTexture(this, desc, Device.MakeSharedTexture(new MTLSharedTextureHandle(nativeTexture, NativeObjectOwnership.Borrowed))); + return new MTLTexture(this, desc, nativeTextureType switch + { + NativeTextureType.MTLSharedTextureHandle => Device.MakeSharedTexture(new MTLSharedTextureHandle(nativeTexture, NativeObjectOwnership.Borrowed)), + NativeTextureType.IOSurfaceRef => Device.MakeTexture(MTLTexture.Descriptor(desc), nativeTexture, 0), + _ => MtlTexture.Null + }); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET/Enums/NativeTextureType.cs b/sources/Zenith.NET/Enums/NativeTextureType.cs index c70d595f..307ca4cb 100644 --- a/sources/Zenith.NET/Enums/NativeTextureType.cs +++ b/sources/Zenith.NET/Enums/NativeTextureType.cs @@ -2,15 +2,17 @@ public enum NativeTextureType { - D3D11, + D3D11TextureNtHandle, - D3D12, + D3D12ResourceNtHandle, - Metal, + MTLSharedTextureHandle, - VulkanWin32, + IOSurfaceRef, - VulkanLinux, + VulkanOpaqueNtHandle, - VulkanAndroid + VulkanOpaquePosixFileDescriptor, + + VulkanAndroidHardwareBuffer } From d680e42665b07c39a3c4f342b692656303d7a6e6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 16:06:52 +0800 Subject: [PATCH 244/466] Update DXCommandBuffer to use DXCommandQueue type Changed DXCommandBuffer constructor to accept DXCommandQueue instead of the base CommandQueue type, and updated the base constructor call to match. This ensures type consistency and may enable DX-specific functionality. --- sources/Zenith.NET.DirectX12/DXCommandBuffer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index b18aef35..f0ab8efd 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -11,7 +11,7 @@ internal unsafe class DXCommandBuffer : CommandBuffer public ComPtr CommandList; - public DXCommandBuffer(DXGraphicsContext context, CommandQueue queue) : base(context, queue) + public DXCommandBuffer(DXGraphicsContext context, DXCommandQueue queue) : base(context, queue) { context.Device.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), SilkMarshal.GuidPtrOf(), (void**)CommandAllocator.GetAddressOf()).Success(); context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, default(ID3D12PipelineState*), SilkMarshal.GuidPtrOf(), (void**)CommandList.GetAddressOf()).Success(); From 1d94d5775fd7e446a98e94d0ef184283ea4a78f6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 17:32:21 +0800 Subject: [PATCH 245/466] Add MTLCommandBuffer and refactor buffer GPU address usage Introduced MTLCommandBuffer with method stubs and partial timestamp support. Refactored MTLBuffer to remove GpuAddress field, updating MTLBufferView to access GPU address via Buffer property. Updated MTLCommandQueue to create MTLCommandBuffer and expose Context. --- sources/Zenith.NET.Metal/MTLBuffer.cs | 6 - sources/Zenith.NET.Metal/MTLBufferView.cs | 2 +- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 232 +++++++++++++++++++ sources/Zenith.NET.Metal/MTLCommandQueue.cs | 4 +- 4 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLCommandBuffer.cs diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs index c92927a7..6a7ac36d 100644 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -4,14 +4,10 @@ internal class MTLBuffer : Buffer { public MtlBuffer Buffer; - public ulong GpuAddress; - public MTLBuffer(MTLGraphicsContext context, BufferDesc desc) : base(context, desc) { context.Register(Buffer = context.Device.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency))); - GpuAddress = Buffer.GpuAddress; - View = new(context, new() { Buffer = this, @@ -24,8 +20,6 @@ public MTLBuffer(MTLGraphicsContext context, BufferDesc desc, MtlBuffer buffer) { context.Register(Buffer = buffer); - GpuAddress = Buffer.GpuAddress; - View = new(context, new() { Buffer = this, diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs index aca5a1bf..3623787a 100644 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -2,7 +2,7 @@ internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) { - public ResourceHandle Handle = (desc.Buffer.Metal().GpuAddress + desc.OffsetInBytes).ToHandle(); + public ResourceHandle Handle = (desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + desc.OffsetInBytes).ToHandle(); public override ResourceHandle ConstantHandle => Handle; diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs new file mode 100644 index 00000000..900a7ec0 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -0,0 +1,232 @@ +using System.Numerics; +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : CommandBuffer(context, queue) +{ + public MTL4CommandAllocator CommandAllocator = context.Device.MakeCommandAllocator(); + + public MTL4CommandBuffer CommandBuffer = NSAutorelease.Own(context.Device.MakeCommandBuffer); + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void BarrierImpl(BarrierStages before, BarrierStages after) + { + throw new NotImplementedException(); + } + + protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) + { + throw new NotImplementedException(); + } + + protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) + { + throw new NotImplementedException(); + } + + protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + throw new NotImplementedException(); + } + + protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) + { + throw new NotImplementedException(); + } + + protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) + { + throw new NotImplementedException(); + } + + protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + throw new NotImplementedException(); + } + + protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); + } + + protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); + } + + protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) + { + throw new NotImplementedException(); + } + + protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) + { + throw new NotImplementedException(); + } + + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + throw new NotImplementedException(); + } + + protected override void EndRenderPassImpl() + { + throw new NotImplementedException(); + } + + protected override void SetScissorsImpl(ReadOnlySpan scissors) + { + throw new NotImplementedException(); + } + + protected override void SetViewportsImpl(ReadOnlySpan viewports) + { + throw new NotImplementedException(); + } + + protected override void SetPipelineImpl(GraphicsPipeline pipeline) + { + throw new NotImplementedException(); + } + + protected override void SetPipelineImpl(ComputePipeline pipeline) + { + throw new NotImplementedException(); + } + + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + { + throw new NotImplementedException(); + } + + protected override void SetStencilReferenceImpl(uint stencilReference) + { + throw new NotImplementedException(); + } + + protected override void SetBlendConstantImpl(Vector4 blendConstant) + { + throw new NotImplementedException(); + } + + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) + { + throw new NotImplementedException(); + } + + protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) + { + throw new NotImplementedException(); + } + + protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) + { + throw new NotImplementedException(); + } + + protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) + { + throw new NotImplementedException(); + } + + protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + throw new NotImplementedException(); + } + + protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) + { + throw new NotImplementedException(); + } + + protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + throw new NotImplementedException(); + } + + protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) + { + throw new NotImplementedException(); + } + + protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) + { + throw new NotImplementedException(); + } + + protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) + { + throw new NotImplementedException(); + } + + protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) + { + throw new NotImplementedException(); + } + + protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) + { + throw new NotImplementedException(); + } + + protected override void EndQueryImpl(QueryHeap queryHeap, uint index) + { + throw new NotImplementedException(); + } + + protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) + { + MTLQueryHeap mtlQueryHeap = queryHeap.Metal(); + + CommandBuffer.WriteTimestamp(mtlQueryHeap.CounterHeap, index); + CommandBuffer.ResolveCounterHeap(mtlQueryHeap.CounterHeap, new(index, 1), new(mtlQueryHeap.Buffer.Buffer.GpuAddress + (sizeof(ulong) * index), sizeof(ulong)), MTLFence.Null, MTLFence.Null); + } + + protected override void BeginDebugEventImpl(string label) + { + throw new NotImplementedException(); + } + + protected override void EndDebugEventImpl() + { + throw new NotImplementedException(); + } + + protected override void InsertDebugMarkerImpl(string label) + { + throw new NotImplementedException(); + } + + protected override void BeginImpl() + { + CommandBuffer.BeginCommandBuffer(CommandAllocator); + } + + protected override void EndImpl() + { + CommandBuffer.EndCommandBuffer(); + } + + protected override void ResetImpl() + { + CommandAllocator.Reset(); + } + + protected override void SetResourceName(string name) + { + CommandBuffer.Label = name; + } + + protected override void Destroy() + { + base.Destroy(); + + CommandBuffer.Dispose(); + CommandAllocator.Dispose(); + } +} diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index 7dd9020a..d41f8ac4 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -8,6 +8,8 @@ internal class MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type public MTLSharedEvent Event = context.Device.MakeSharedEvent(); + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -20,7 +22,7 @@ protected override ulong GetCompletedValue() protected override CommandBuffer CreateCommandBuffer() { - throw new NotImplementedException(); + return new MTLCommandBuffer(Context, this); } protected override void SignalImpl(ulong signalValue) From 9c433b5978f5a310259ebc2314aa4964f1e0c87d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 17:39:13 +0800 Subject: [PATCH 246/466] Refactor MTLBufferView to use explicit constructor Replaced primary constructor with explicit one. Removed ResourceHandle field and introduced auto-properties for ConstantHandle, StorageReadOnlyHandle, and StorageReadWriteHandle, initializing them in the constructor. Updated property overrides accordingly. --- sources/Zenith.NET.Metal/MTLBufferView.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs index 3623787a..4342fdc7 100644 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -1,14 +1,19 @@ namespace Zenith.NET.Metal; -internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) +internal class MTLBufferView : BufferView { - public ResourceHandle Handle = (desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + desc.OffsetInBytes).ToHandle(); + public MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : base(context, desc) + { + ConstantHandle = (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToHandle(); + StorageReadOnlyHandle = (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToHandle(); + StorageReadWriteHandle = (Desc.Buffer.Metal().Buffer.GpuAddress.ToUInt64() + Desc.OffsetInBytes).ToHandle(); + } - public override ResourceHandle ConstantHandle => Handle; + public override ResourceHandle ConstantHandle { get; } - public override ResourceHandle StorageReadOnlyHandle => Handle; + public override ResourceHandle StorageReadOnlyHandle { get; } - public override ResourceHandle StorageReadWriteHandle => Handle; + public override ResourceHandle StorageReadWriteHandle { get; } public override nint GetNativeObject(NativeObjectType type) { From 4e68f8c80539739db5d250aae8870ef6502d7bae Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 21:24:38 +0800 Subject: [PATCH 247/466] Implement Metal command buffer ops and DX refactors Add implementations for buffer/texture copy, debug markers, render pass, and barrier methods in MTLCommandBuffer. Manage Metal command encoders and defer scissor/viewport state as needed. Refactor DXCommandBuffer to use local variables for scissor/viewport assignment. Add stub for BarrierStages conversion in MTLFormats and improve resource cleanup. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 24 +-- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 167 ++++++++++++++++-- sources/Zenith.NET.Metal/MTLFormats.cs | 5 + 3 files changed, 169 insertions(+), 27 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index f0ab8efd..dba80c71 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -299,17 +299,19 @@ protected override void SetScissorsImpl(ReadOnlySpan scissors) Box2D* pRects = stackalloc Box2D[scissors.Length]; for (int i = 0; i < scissors.Length; i++) { + Scissor scissor = scissors[i]; + pRects[i] = new() { Min = new() { - X = scissors[i].X, - Y = scissors[i].Y + X = scissor.X, + Y = scissor.Y }, Max = new() { - X = scissors[i].X + (int)scissors[i].Width, - Y = scissors[i].Y + (int)scissors[i].Height + X = (int)(scissor.X + scissor.Width), + Y = (int)(scissor.Y + scissor.Height) } }; } @@ -322,14 +324,16 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) DxViewport* pViewports = stackalloc DxViewport[viewports.Length]; for (int i = 0; i < viewports.Length; i++) { + Viewport viewport = viewports[i]; + pViewports[i] = new() { - TopLeftX = viewports[i].X, - TopLeftY = viewports[i].Y, - Width = viewports[i].Width, - Height = viewports[i].Height, - MinDepth = viewports[i].MinDepth, - MaxDepth = viewports[i].MaxDepth + TopLeftX = viewport.X, + TopLeftY = viewport.Y, + Width = viewport.Width, + Height = viewport.Height, + MinDepth = viewport.MinDepth, + MaxDepth = viewport.MaxDepth }; } diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 900a7ec0..255687a7 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -3,11 +3,34 @@ namespace Zenith.NET.Metal; -internal class MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : CommandBuffer(context, queue) +internal class MTLCommandBuffer : CommandBuffer { - public MTL4CommandAllocator CommandAllocator = context.Device.MakeCommandAllocator(); + public MTL4CommandAllocator CommandAllocator; - public MTL4CommandBuffer CommandBuffer = NSAutorelease.Own(context.Device.MakeCommandBuffer); + public MTL4CommandBuffer CommandBuffer; + + public MTL4ArgumentTable ArgumentTable; + + private MTL4RenderCommandEncoder? render; + private MTL4ComputeCommandEncoder? compute; + + private Scissor[]? todoScissors; + private Viewport[]? todoViewports; + + public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) + { + CommandAllocator = context.Device.MakeCommandAllocator(); + CommandBuffer = NSAutorelease.Own(context.Device.MakeCommandBuffer); + + MTL4ArgumentTableDescriptor descriptor = new() + { + MaxBufferBindCount = 16, + SupportAttributeStrides = true + }; + + ArgumentTable = context.Device.MakeArgumentTable(descriptor, out NSError error); + error.Success(); + } public override nint GetNativeObject(NativeObjectType type) { @@ -16,37 +39,68 @@ public override nint GetNativeObject(NativeObjectType type) protected override void BarrierImpl(BarrierStages before, BarrierStages after) { - throw new NotImplementedException(); + render?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); + compute?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); } protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) { - throw new NotImplementedException(); } protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { - throw new NotImplementedException(); + compute?.Copy(src.Metal().Buffer, srcOffsetInBytes, dst.Metal().Buffer, dstOffsetInBytes, sizeInBytes); } protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - throw new NotImplementedException(); + compute?.Copy(src.Metal().Buffer, + srcOffsetInBytes, + srcRowStrideInBytes, + srcSliceStrideInBytes, + new(dstExtent.Width, dstExtent.Height, dstExtent.Depth), + dst.Metal().Texture, + dstSubresource.ArrayLayer, + dstSubresource.MipLevel, + new(dstOffset.X, dstOffset.Y, dstOffset.Z)); } protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - throw new NotImplementedException(); + compute?.Copy(src.Metal().Texture, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, + new(srcOffset.X, srcOffset.Y, srcOffset.Z), + new(extent.Width, extent.Height, extent.Depth), + dst.Metal().Texture, + dstSubresource.ArrayLayer, + dstSubresource.MipLevel, + new(dstOffset.X, dstOffset.Y, dstOffset.Z)); } protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { - throw new NotImplementedException(); + compute?.Copy(src.Metal().Texture, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, + new(srcOffset.X, srcOffset.Y, srcOffset.Z), + new(srcExtent.Width, srcExtent.Height, srcExtent.Depth), + dst.Metal().Buffer, + dstOffsetInBytes, + dstRowStrideInBytes, + dstSliceStrideInBytes); } protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { - throw new NotImplementedException(); + compute?.Copy(src.Metal().Texture, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, + src.Metal().Texture, + dstSubresource.ArrayLayer, + dstSubresource.MipLevel, + 1, + 1); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) @@ -71,22 +125,54 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { - throw new NotImplementedException(); + EndComputeEncoding(); + BeginRenderEncoding(MTL4RenderPassDescriptor.Null); } protected override void EndRenderPassImpl() { - throw new NotImplementedException(); + EndRenderEncoding(); + BeginComputeEncoding(); } protected override void SetScissorsImpl(ReadOnlySpan scissors) { - throw new NotImplementedException(); + if (render is null) + { + todoScissors = [.. scissors]; + } + else + { + MTLScissorRect[] mtlScissors = new MTLScissorRect[scissors.Length]; + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; + + mtlScissors[i] = new((uint)scissor.X, (uint)scissor.Y, scissor.Width, scissor.Height); + } + + render.SetScissorRects(mtlScissors); + } } protected override void SetViewportsImpl(ReadOnlySpan viewports) { - throw new NotImplementedException(); + if (render is null) + { + todoViewports = [.. viewports]; + } + else + { + MTLViewport[] mtlViewports = new MTLViewport[viewports.Length]; + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; + + mtlViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); + } + + render.SetViewports(mtlViewports); + } } protected override void SetPipelineImpl(GraphicsPipeline pipeline) @@ -189,26 +275,34 @@ protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) protected override void BeginDebugEventImpl(string label) { - throw new NotImplementedException(); + render?.PushDebugGroup(label); + compute?.PushDebugGroup(label); } protected override void EndDebugEventImpl() { - throw new NotImplementedException(); + render?.PopDebugGroup(); + compute?.PopDebugGroup(); } protected override void InsertDebugMarkerImpl(string label) { - throw new NotImplementedException(); + render?.InsertDebugSignpost(label); + compute?.InsertDebugSignpost(label); } protected override void BeginImpl() { CommandBuffer.BeginCommandBuffer(CommandAllocator); + + BeginComputeEncoding(); } protected override void EndImpl() { + EndRenderEncoding(); + EndComputeEncoding(); + CommandBuffer.EndCommandBuffer(); } @@ -226,7 +320,46 @@ protected override void Destroy() { base.Destroy(); + ArgumentTable.Dispose(); CommandBuffer.Dispose(); CommandAllocator.Dispose(); } + + private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) + { + render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); + + if (todoScissors is not null) + { + SetScissors(todoScissors); + + todoScissors = null; + } + + if (todoViewports is not null) + { + SetViewports(todoViewports); + + todoViewports = null; + } + } + + private void EndRenderEncoding() + { + render?.EndEncoding(); + render?.Dispose(); + render = null; + } + + private void BeginComputeEncoding() + { + compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); + } + + private void EndComputeEncoding() + { + compute?.EndEncoding(); + compute?.Dispose(); + compute = null; + } } diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 8ac1d477..488c1e23 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -249,4 +249,9 @@ internal static MTLStencilOperation Metal(StencilOp failOp) { throw new NotImplementedException(); } + + internal static MTLStages Metal(BarrierStages after) + { + throw new NotImplementedException(); + } } From 969f963b4e546faad37a6e776ad0571a84dcdffb Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 16 Jun 2026 23:24:47 +0800 Subject: [PATCH 248/466] Refactor BarrierStages enum and update DirectX12 barrier handling --- sources/Zenith.NET.DirectX12/DXFormats.cs | 19 +++++++++++++------ sources/Zenith.NET/Enums/BarrierStages.cs | 10 ++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 89375b51..c5f3b0a0 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -57,19 +57,19 @@ public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages b BarrierSync sync = BarrierSync.None; BarrierAccess access = BarrierAccess.Common; - if (barrierStages.HasFlag(BarrierStages.Vertex)) + if (barrierStages.HasFlag(BarrierStages.VertexShading)) { sync |= BarrierSync.VertexShading; access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; } - if (barrierStages.HasFlag(BarrierStages.Fragment)) + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) { - sync |= BarrierSync.PixelShading; - access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; + sync |= BarrierSync.PixelShading | BarrierSync.DepthStencil | BarrierSync.RenderTarget; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.DepthStencilRead | BarrierAccess.DepthStencilWrite | BarrierAccess.RenderTarget; } - if (barrierStages.HasFlag(BarrierStages.Compute)) + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) { sync |= BarrierSync.ComputeShading; access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.UnorderedAccess; @@ -81,9 +81,16 @@ public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages b access |= BarrierAccess.CopySource | BarrierAccess.CopyDest; } + if (barrierStages.HasFlag(BarrierStages.Resolve)) + { + sync |= BarrierSync.Resolve; + access |= BarrierAccess.ResolveSource | BarrierAccess.ResolveDest; + } + if (barrierStages.HasFlag(BarrierStages.All)) { - sync |= BarrierSync.All; + sync = BarrierSync.All; + access = BarrierAccess.Common; } return (sync, access); diff --git a/sources/Zenith.NET/Enums/BarrierStages.cs b/sources/Zenith.NET/Enums/BarrierStages.cs index 2b22120a..6745175e 100644 --- a/sources/Zenith.NET/Enums/BarrierStages.cs +++ b/sources/Zenith.NET/Enums/BarrierStages.cs @@ -5,13 +5,15 @@ public enum BarrierStages { None = 0, - Vertex = 1 << 0, + VertexShading = 1 << 0, - Fragment = 1 << 1, + FragmentShading = 1 << 1, - Compute = 1 << 2, + ComputeShading = 1 << 2, Copy = 1 << 3, - All = 1 << 4 + Resolve = 1 << 4, + + All = 1 << 5 } From 050739ae525b9a1412a1ab3314a6872925f825e0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 17 Jun 2026 15:32:50 +0800 Subject: [PATCH 249/466] Update MTLFormats: public API and full enum mappings Changed methods in MTLFormats from internal to public. Implemented full mappings from graphics API enums to Metal equivalents, replacing NotImplementedException. Improved ColorWrites and BarrierStages handling to compute correct Metal flags. --- sources/Zenith.NET.Metal/MTLFormats.cs | 181 ++++++++++++++++++++++--- 1 file changed, 163 insertions(+), 18 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 488c1e23..3cb6ca80 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -171,7 +171,7 @@ public static MTLTextureUsage Metal(TextureUsages textureUsages) return result; } - internal static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode filterMode) + public static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode filterMode) { return filterMode switch { @@ -181,7 +181,7 @@ internal static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFil }; } - internal static MTLSamplerAddressMode Metal(AddressMode addressMode) + public static MTLSamplerAddressMode Metal(AddressMode addressMode) { return addressMode switch { @@ -193,7 +193,7 @@ internal static MTLSamplerAddressMode Metal(AddressMode addressMode) }; } - internal static MTLSamplerBorderColor Metal(BorderColor borderColor) + public static MTLSamplerBorderColor Metal(BorderColor borderColor) { return borderColor switch { @@ -204,7 +204,7 @@ internal static MTLSamplerBorderColor Metal(BorderColor borderColor) }; } - internal static MTLCompareFunction Metal(CompareOp compareOp) + public static MTLCompareFunction Metal(CompareOp compareOp) { return compareOp switch { @@ -220,38 +220,183 @@ internal static MTLCompareFunction Metal(CompareOp compareOp) }; } - internal static MTLPrimitiveTopologyClass Metal(PrimitiveTopology primitiveTopology) + public static MTLPrimitiveTopologyClass Metal(PrimitiveTopology primitiveTopology) { - throw new NotImplementedException(); + return primitiveTopology switch + { + PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, + PrimitiveTopology.LineList => MTLPrimitiveTopologyClass.Line, + PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, + PrimitiveTopology.TriangleList => MTLPrimitiveTopologyClass.Triangle, + PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, + _ => default + }; } - internal static MTLVertexFormat Metal(ElementFormat format) + public static MTLVertexFormat Metal(ElementFormat elementFormat) { - throw new NotImplementedException(); + return elementFormat switch + { + ElementFormat.UByte1 => MTLVertexFormat.UChar, + ElementFormat.UByte2 => MTLVertexFormat.UChar2, + ElementFormat.UByte4 => MTLVertexFormat.UChar4, + + ElementFormat.Byte1 => MTLVertexFormat.Char, + ElementFormat.Byte2 => MTLVertexFormat.Char2, + ElementFormat.Byte4 => MTLVertexFormat.Char4, + + ElementFormat.UByte1UNorm => MTLVertexFormat.UCharNormalized, + ElementFormat.UByte2UNorm => MTLVertexFormat.UChar2Normalized, + ElementFormat.UByte4UNorm => MTLVertexFormat.UChar4Normalized, + + ElementFormat.Byte1SNorm => MTLVertexFormat.CharNormalized, + ElementFormat.Byte2SNorm => MTLVertexFormat.Char2Normalized, + ElementFormat.Byte4SNorm => MTLVertexFormat.Char4Normalized, + + ElementFormat.UShort1 => MTLVertexFormat.UShort, + ElementFormat.UShort2 => MTLVertexFormat.UShort2, + ElementFormat.UShort4 => MTLVertexFormat.UShort4, + + ElementFormat.Short1 => MTLVertexFormat.Short, + ElementFormat.Short2 => MTLVertexFormat.Short2, + ElementFormat.Short4 => MTLVertexFormat.Short4, + + ElementFormat.UShort1UNorm => MTLVertexFormat.UShortNormalized, + ElementFormat.UShort2UNorm => MTLVertexFormat.UShort2Normalized, + ElementFormat.UShort4UNorm => MTLVertexFormat.UShort4Normalized, + + ElementFormat.Short1SNorm => MTLVertexFormat.ShortNormalized, + ElementFormat.Short2SNorm => MTLVertexFormat.Short2Normalized, + ElementFormat.Short4SNorm => MTLVertexFormat.Short4Normalized, + + ElementFormat.Half1 => MTLVertexFormat.Half, + ElementFormat.Half2 => MTLVertexFormat.Half2, + ElementFormat.Half4 => MTLVertexFormat.Half4, + + ElementFormat.Float1 => MTLVertexFormat.Float, + ElementFormat.Float2 => MTLVertexFormat.Float2, + ElementFormat.Float3 => MTLVertexFormat.Float3, + ElementFormat.Float4 => MTLVertexFormat.Float4, + + ElementFormat.UInt1 => MTLVertexFormat.UInt, + ElementFormat.UInt2 => MTLVertexFormat.UInt2, + ElementFormat.UInt3 => MTLVertexFormat.UInt3, + ElementFormat.UInt4 => MTLVertexFormat.UInt4, + + ElementFormat.Int1 => MTLVertexFormat.Int, + ElementFormat.Int2 => MTLVertexFormat.Int2, + ElementFormat.Int3 => MTLVertexFormat.Int3, + ElementFormat.Int4 => MTLVertexFormat.Int4, + + _ => default + }; } - internal static MTLBlendFactor Metal(BlendFactor srcRgbFactor) + public static MTLBlendFactor Metal(BlendFactor blendFactor) { - throw new NotImplementedException(); + return blendFactor switch + { + BlendFactor.Zero => MTLBlendFactor.Zero, + BlendFactor.One => MTLBlendFactor.One, + BlendFactor.SrcColor => MTLBlendFactor.SourceColor, + BlendFactor.OneMinusSrcColor => MTLBlendFactor.OneMinusSourceColor, + BlendFactor.DstColor => MTLBlendFactor.DestinationColor, + BlendFactor.OneMinusDstColor => MTLBlendFactor.OneMinusDestinationColor, + BlendFactor.SrcAlpha => MTLBlendFactor.SourceAlpha, + BlendFactor.OneMinusSrcAlpha => MTLBlendFactor.OneMinusSourceAlpha, + BlendFactor.DstAlpha => MTLBlendFactor.DestinationAlpha, + BlendFactor.OneMinusDstAlpha => MTLBlendFactor.OneMinusDestinationAlpha, + BlendFactor.Constant => MTLBlendFactor.BlendColor, + BlendFactor.OneMinusConstant => MTLBlendFactor.OneMinusBlendColor, + _ => default + }; } - internal static MTLBlendOperation Metal(BlendOp rgbOp) + public static MTLBlendOperation Metal(BlendOp blendOp) { - throw new NotImplementedException(); + return blendOp switch + { + BlendOp.Add => MTLBlendOperation.Add, + BlendOp.Subtract => MTLBlendOperation.Subtract, + BlendOp.ReverseSubtract => MTLBlendOperation.ReverseSubtract, + BlendOp.Min => MTLBlendOperation.Min, + BlendOp.Max => MTLBlendOperation.Max, + _ => default + }; } - internal static MTLColorWriteMask Metal(ColorWrites colorWrites) + public static MTLColorWriteMask Metal(ColorWrites colorWrites) { - throw new NotImplementedException(); + MTLColorWriteMask result = MTLColorWriteMask.None; + + if (colorWrites.HasFlag(ColorWrites.Red)) + { + result |= MTLColorWriteMask.Red; + } + + if (colorWrites.HasFlag(ColorWrites.Green)) + { + result |= MTLColorWriteMask.Green; + } + + if (colorWrites.HasFlag(ColorWrites.Blue)) + { + result |= MTLColorWriteMask.Blue; + } + + if (colorWrites.HasFlag(ColorWrites.Alpha)) + { + result |= MTLColorWriteMask.Alpha; + } + + return result; } - internal static MTLStencilOperation Metal(StencilOp failOp) + public static MTLStencilOperation Metal(StencilOp stencilOp) { - throw new NotImplementedException(); + return stencilOp switch + { + StencilOp.Keep => MTLStencilOperation.Keep, + StencilOp.Zero => MTLStencilOperation.Zero, + StencilOp.Replace => MTLStencilOperation.Replace, + StencilOp.IncrementAndClamp => MTLStencilOperation.IncrementClamp, + StencilOp.DecrementAndClamp => MTLStencilOperation.DecrementClamp, + StencilOp.Invert => MTLStencilOperation.Invert, + StencilOp.IncrementAndWrap => MTLStencilOperation.IncrementWrap, + StencilOp.DecrementAndWrap => MTLStencilOperation.DecrementWrap, + _ => default + }; } - internal static MTLStages Metal(BarrierStages after) + public static MTLStages Metal(BarrierStages barrierStages) { - throw new NotImplementedException(); + MTLStages result = default; + + if (barrierStages.HasFlag(BarrierStages.VertexShading)) + { + result |= MTLStages.Vertex; + } + + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) + { + result |= MTLStages.Fragment; + } + + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) + { + result |= MTLStages.Dispatch; + } + + if (barrierStages.HasFlag(BarrierStages.Copy) || barrierStages.HasFlag(BarrierStages.Resolve)) + { + result |= MTLStages.Blit; + } + + if (barrierStages.HasFlag(BarrierStages.All)) + { + result = MTLStages.All; + } + + return result; } } From 58499a539e3e3f24c364e5874e85c851a83c2987 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 17 Jun 2026 15:34:36 +0800 Subject: [PATCH 250/466] Fix BarrierImpl to use correct 'before' stage in barriers Updated BarrierImpl to pass MTLFormats.Metal(before) as the second argument to BarrierAfterStages for both render and compute objects, ensuring correct barrier transitions between stages. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 255687a7..f91c5fdc 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -39,8 +39,8 @@ public override nint GetNativeObject(NativeObjectType type) protected override void BarrierImpl(BarrierStages before, BarrierStages after) { - render?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); - compute?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); + render?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(before), MTL4VisibilityOptions.Device); + compute?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(before), MTL4VisibilityOptions.Device); } protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) From 4098185e8c26be7244aef19b7343260145e0255a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 18 Jun 2026 20:13:37 +0800 Subject: [PATCH 251/466] Add barriers before ending render and compute encoders Insert BarrierAfterEncoderStages in EndRenderEncoding and EndComputeEncoding to ensure proper synchronization and memory visibility before encoders are finalized in MTLCommandBuffer. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index f91c5fdc..dd092ff3 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -346,6 +346,7 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) private void EndRenderEncoding() { + render?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); render?.EndEncoding(); render?.Dispose(); render = null; @@ -358,6 +359,7 @@ private void BeginComputeEncoding() private void EndComputeEncoding() { + compute?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); compute?.EndEncoding(); compute?.Dispose(); compute = null; From def66811d39e0e7fdc959990ec9d454d39589b4d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 10:55:01 +0800 Subject: [PATCH 252/466] Add locking for thread-safe descriptor heap allocation Added a private Lock to DXDescriptorHeap and wrapped Allocate and Free methods in lock scopes to ensure thread safety during descriptor token management. --- sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs index c5b0cb7f..4bd49407 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -5,6 +5,7 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXDescriptorHeap : DisposableObject { + private readonly Lock @lock = new(); private readonly Stack recycled = []; public ComPtr Heap; @@ -35,6 +36,8 @@ public DXDescriptorHeap(DXGraphicsContext context, DescriptorHeapType type, uint public DXDescriptorToken Allocate() { + using Lock.Scope _ = @lock.EnterScope(); + if (!recycled.TryPop(out uint slot)) { slot = head++; @@ -45,6 +48,8 @@ public DXDescriptorToken Allocate() public void Free(DXDescriptorToken token) { + using Lock.Scope _ = @lock.EnterScope(); + recycled.Push(token.Slot); } From 1ae8c5767c774493ca6e7ebec298fe29f89acec3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 11:01:30 +0800 Subject: [PATCH 253/466] Add locking to MTLGraphicsContext for thread safety Introduced a private Lock to MTLGraphicsContext and wrapped ResidencySet modifications in Register and Unregister methods with Lock.Scope to ensure thread-safe access. --- sources/Zenith.NET.Metal/MTLGraphicsContext.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index a9ba83dc..0919de2f 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -4,6 +4,8 @@ namespace Zenith.NET.Metal; internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Metal, useValidationLayer) { + private readonly Lock @lock = new(); + public MTLDevice Device = MTLDevice.CreateSystemDefaultDevice(); public MTL4Compiler Compiler = MTL4Compiler.Null; @@ -18,12 +20,16 @@ internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(Gra public void Register(MTLAllocation allocation) { + using Lock.Scope _ = @lock.EnterScope(); + ResidencySet.AddAllocation(allocation); ResidencySet.Commit(); } public void Unregister(MTLAllocation allocation) { + using Lock.Scope _ = @lock.EnterScope(); + ResidencySet.RemoveAllocation(allocation); ResidencySet.Commit(); } From eed0855a9e1a399ed71cfea870ceb3507fa779e6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 11:04:21 +0800 Subject: [PATCH 254/466] Remove Context.Unregister call from MTLHeap.Destroy The Destroy() method in MTLHeap no longer calls Context.Unregister(Heap); it now only disposes of the heap. This simplifies resource cleanup and may affect heap lifecycle management. --- sources/Zenith.NET.Metal/MTLHeap.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs index 2009f5a8..27a4a38f 100644 --- a/sources/Zenith.NET.Metal/MTLHeap.cs +++ b/sources/Zenith.NET.Metal/MTLHeap.cs @@ -40,8 +40,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.Unregister(Heap); - Heap.Dispose(); } } From c02830b54b567be8464b915ff9cd9007d266bba5 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 11:13:32 +0800 Subject: [PATCH 255/466] Refactor command queue management in Metal backend Move creation and disposal of MTL4CommandQueue instances from MTLGraphicsContext to MTLCommandQueue. Each MTLCommandQueue now manages its own queue and residency set association, improving encapsulation and simplifying resource management. --- sources/Zenith.NET.Metal/MTLCommandQueue.cs | 17 ++++++++++--- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 25 +++---------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index d41f8ac4..d74ca36d 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -2,11 +2,19 @@ namespace Zenith.NET.Metal; -internal class MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type, MTL4CommandQueue commandQueue) : CommandQueue(context, type) +internal class MTLCommandQueue : CommandQueue { - public MTL4CommandQueue CommandQueue = commandQueue; + public MTL4CommandQueue CommandQueue; - public MTLSharedEvent Event = context.Device.MakeSharedEvent(); + public MTLSharedEvent Event; + + public MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type) : base(context, type) + { + CommandQueue = context.Device.MakeMTL4CommandQueue(); + Event = context.Device.MakeSharedEvent(); + + CommandQueue.AddResidencySet(context.ResidencySet); + } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; @@ -61,6 +69,9 @@ protected override void Destroy() { base.Destroy(); + CommandQueue.RemoveResidencySet(Context.ResidencySet); + Event.Dispose(); + CommandQueue.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 0919de2f..b9366769 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -12,12 +12,6 @@ internal class MTLGraphicsContext(bool useValidationLayer) : GraphicsContext(Gra public MTLResidencySet ResidencySet = MTLResidencySet.Null; - public MTL4CommandQueue GraphicsCommandQueue = MTL4CommandQueue.Null; - - public MTL4CommandQueue ComputeCommandQueue = MTL4CommandQueue.Null; - - public MTL4CommandQueue CopyCommandQueue = MTL4CommandQueue.Null; - public void Register(MTLAllocation allocation) { using Lock.Scope _ = @lock.EnterScope(); @@ -57,19 +51,10 @@ protected override void Initialize(bool useValidationLayer, ResidencySet = Device.MakeResidencySet(new(), out error); error.Success(); - GraphicsCommandQueue = Device.MakeMTL4CommandQueue(); - GraphicsCommandQueue.AddResidencySet(ResidencySet); - - ComputeCommandQueue = Device.MakeMTL4CommandQueue(); - ComputeCommandQueue.AddResidencySet(ResidencySet); - - CopyCommandQueue = Device.MakeMTL4CommandQueue(); - CopyCommandQueue.AddResidencySet(ResidencySet); - capabilities = new MTLCapabilities(this); - graphicsQueue = new MTLCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); - computeQueue = new MTLCommandQueue(this, CommandQueueType.Compute, ComputeCommandQueue); - copyQueue = new MTLCommandQueue(this, CommandQueueType.Copy, CopyCommandQueue); + graphicsQueue = new MTLCommandQueue(this, CommandQueueType.Graphics); + computeQueue = new MTLCommandQueue(this, CommandQueueType.Compute); + copyQueue = new MTLCommandQueue(this, CommandQueueType.Copy); validationLayer = null; } @@ -161,10 +146,6 @@ protected override void Destroy() { base.Destroy(); - CopyCommandQueue.Dispose(); - ComputeCommandQueue.Dispose(); - GraphicsCommandQueue.Dispose(); - ResidencySet.Dispose(); Compiler.Dispose(); Device.Dispose(); From c740517e62cccaa144b4419c6c448bbce6dea173 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 11:17:53 +0800 Subject: [PATCH 256/466] Refactor DX12 command queue management DXCommandQueue now creates and owns its own command queue based on type, removing the need for separate command queue pointers in DXGraphicsContext. The context now instantiates DXCommandQueue objects directly for graphics, compute, and copy queues. Resource cleanup and swap chain creation are updated to use the new structure. --- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 7 ++++-- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 25 +++---------------- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 2 +- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 9655f6d8..817d1602 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -11,10 +11,11 @@ internal unsafe class DXCommandQueue : CommandQueue public ComPtr Fence; - public DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr commandQueue) : base(context, type) + public DXCommandQueue(DXGraphicsContext context, CommandQueueType type) : base(context, type) { - CommandQueue = commandQueue; + CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; + context.Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CommandQueue.GetAddressOf()).Success(); context.Device.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence.GetAddressOf()).Success(); } @@ -76,6 +77,8 @@ protected override void Destroy() base.Destroy(); Fence.Dispose(); + CommandQueue.Dispose(); + @event.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 9d1f9ce8..5a04fc1d 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -27,12 +27,6 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public ComPtr DispatchMeshSignature; - public ComPtr GraphicsCommandQueue; - - public ComPtr ComputeCommandQueue; - - public ComPtr CopyCommandQueue; - public DXGI DXGI { get; } = DXGI.GetApi(null); public D3D12 D3D12 { get; } = D3D12.GetApi(); @@ -140,19 +134,10 @@ protected override void Initialize(bool useValidationLayer, commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).Success(); - CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; - Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)GraphicsCommandQueue.GetAddressOf()).Success(); - - commandQueueDesc.Type = CommandListType.Compute; - Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)ComputeCommandQueue.GetAddressOf()).Success(); - - commandQueueDesc.Type = CommandListType.Copy; - Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CopyCommandQueue.GetAddressOf()).Success(); - capabilities = new DXCapabilities(this); - graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics, GraphicsCommandQueue); - computeQueue = new DXCommandQueue(this, CommandQueueType.Compute, ComputeCommandQueue); - copyQueue = new DXCommandQueue(this, CommandQueueType.Copy, CopyCommandQueue); + graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics); + computeQueue = new DXCommandQueue(this, CommandQueueType.Compute); + copyQueue = new DXCommandQueue(this, CommandQueueType.Copy); validationLayer = useValidationLayer ? new DXValidationLayer(this) : null; } @@ -251,10 +236,6 @@ protected override void Destroy() DsvHeap.Dispose(); RtvHeap.Dispose(); - CopyCommandQueue.Dispose(); - ComputeCommandQueue.Dispose(); - GraphicsCommandQueue.Dispose(); - DispatchMeshSignature.Dispose(); DispatchSignature.Dispose(); DrawIndexedSignature.Dispose(); diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 6367c3e5..ec0b7bb0 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -77,7 +77,7 @@ private void CreateSwapChain() Flags = (uint)SwapChainFlag.AllowTearing }; - Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsCommandQueue.Handle, + Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsQueue.DirectX12().CommandQueue.Handle, Desc.Surface.NativeHandles[0], &swapChainDesc, default(SwapChainFullscreenDesc*), From 8c187c9d27f35225caa99dedf8f0b6230315b6a7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 11:18:57 +0800 Subject: [PATCH 257/466] Update command queue type to use DXFormats.DirectX12 Replaced hardcoded CommandListType.Direct with DXFormats.DirectX12(type) in CommandQueueDesc, enabling dynamic selection of the command queue type based on the provided CommandQueueType. --- sources/Zenith.NET.DirectX12/DXCommandQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 817d1602..af53d0d7 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -13,7 +13,7 @@ internal unsafe class DXCommandQueue : CommandQueue public DXCommandQueue(DXGraphicsContext context, CommandQueueType type) : base(context, type) { - CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; + CommandQueueDesc commandQueueDesc = new() { Type = DXFormats.DirectX12(type) }; context.Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CommandQueue.GetAddressOf()).Success(); context.Device.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence.GetAddressOf()).Success(); From 61a16d9397f9e1d1f4e403a5757a117e812f5533 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 18:45:32 +0800 Subject: [PATCH 258/466] Implement deferred pipeline state setting for Metal SetPipelineImpl now defers pipeline state if encoder is inactive, applying it when the encoder is created. Added stubs for Metal enum conversions in MTLFormats.cs. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 73 +++++++++++++++++++- sources/Zenith.NET.Metal/MTLFormats.cs | 15 ++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index dd092ff3..d6a5eab6 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -16,6 +16,9 @@ internal class MTLCommandBuffer : CommandBuffer private Scissor[]? todoScissors; private Viewport[]? todoViewports; + private GraphicsPipeline? todoGraphicsPipeline; + private ComputePipeline? todoComputePipeline; + private MeshShadingPipeline? todoMeshShadingPipeline; public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { @@ -177,17 +180,60 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) protected override void SetPipelineImpl(GraphicsPipeline pipeline) { - throw new NotImplementedException(); + if (render is null) + { + todoGraphicsPipeline = pipeline; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + } + else + { + MTLGraphicsPipeline mtlPipeline = pipeline.Metal(); + + render.SetDepthStencilState(mtlPipeline.DepthStencilState); + render.SetRenderPipelineState(mtlPipeline.RenderPipelineState); + render.SetCullMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.CullMode)); + render.SetFrontFacing(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FrontFace)); + render.SetTriangleFillMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FillMode)); + render.SetDepthClipMode(mtlPipeline.Desc.RenderState.Rasterizer.IsDepthClipEnabled ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); + render.SetDepthBias(mtlPipeline.Desc.RenderState.Rasterizer.DepthBias, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasSlopeScale, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasClamp); + } } protected override void SetPipelineImpl(ComputePipeline pipeline) { - throw new NotImplementedException(); + if (compute is null) + { + todoGraphicsPipeline = null; + todoComputePipeline = pipeline; + todoMeshShadingPipeline = null; + } + else + { + compute.SetComputePipelineState(pipeline.Metal().ComputePipelineState); + } } protected override void SetPipelineImpl(MeshShadingPipeline pipeline) { - throw new NotImplementedException(); + if (render is null) + { + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = pipeline; + } + else + { + MTLMeshShadingPipeline mtlPipeline = pipeline.Metal(); + + render.SetDepthStencilState(mtlPipeline.DepthStencilState); + render.SetRenderPipelineState(mtlPipeline.RenderPipelineState); + render.SetCullMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.CullMode)); + render.SetFrontFacing(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FrontFace)); + render.SetTriangleFillMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FillMode)); + render.SetDepthClipMode(mtlPipeline.Desc.RenderState.Rasterizer.IsDepthClipEnabled ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); + render.SetDepthBias(mtlPipeline.Desc.RenderState.Rasterizer.DepthBias, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasSlopeScale, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasClamp); + } } protected override void SetStencilReferenceImpl(uint stencilReference) @@ -342,6 +388,20 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) todoViewports = null; } + + if (todoGraphicsPipeline is not null) + { + SetPipeline(todoGraphicsPipeline); + + todoGraphicsPipeline = null; + } + + if (todoMeshShadingPipeline is not null) + { + SetPipeline(todoMeshShadingPipeline); + + todoMeshShadingPipeline = null; + } } private void EndRenderEncoding() @@ -355,6 +415,13 @@ private void EndRenderEncoding() private void BeginComputeEncoding() { compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); + + if (todoComputePipeline is not null) + { + SetPipeline(todoComputePipeline); + + todoComputePipeline = null; + } } private void EndComputeEncoding() diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 3cb6ca80..4e7b2295 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -399,4 +399,19 @@ public static MTLStages Metal(BarrierStages barrierStages) return result; } + + internal static MTLCullMode Metal(CullMode cullMode) + { + throw new NotImplementedException(); + } + + internal static MTLWinding Metal(FrontFace frontFace) + { + throw new NotImplementedException(); + } + + internal static MTLTriangleFillMode Metal(FillMode fillMode) + { + throw new NotImplementedException(); + } } From 3cd0ff87a50bdd05c5fb1d0bbcefa0f936729f91 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 19 Jun 2026 22:16:30 +0800 Subject: [PATCH 259/466] Defer stencil/blend state in MTLCommandBuffer Add support for deferring stencil reference and blend constant settings when the render encoder is inactive. These values are now stored and applied automatically when the encoder is started, ensuring correct state application. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 34 ++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index d6a5eab6..8b947486 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -19,6 +19,8 @@ internal class MTLCommandBuffer : CommandBuffer private GraphicsPipeline? todoGraphicsPipeline; private ComputePipeline? todoComputePipeline; private MeshShadingPipeline? todoMeshShadingPipeline; + private uint? todoStencilReference; + private Vector4? todoBlendConstant; public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { @@ -238,12 +240,26 @@ protected override void SetPipelineImpl(MeshShadingPipeline pipeline) protected override void SetStencilReferenceImpl(uint stencilReference) { - throw new NotImplementedException(); + if (render is null) + { + todoStencilReference = stencilReference; + } + else + { + render.SetStencilReferenceValue(stencilReference); + } } protected override void SetBlendConstantImpl(Vector4 blendConstant) { - throw new NotImplementedException(); + if (render is null) + { + todoBlendConstant = blendConstant; + } + else + { + render.SetBlendColor(blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W); + } } protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) @@ -402,6 +418,20 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) todoMeshShadingPipeline = null; } + + if (todoStencilReference is not null) + { + SetStencilReference(todoStencilReference.Value); + + todoStencilReference = null; + } + + if (todoBlendConstant is not null) + { + SetBlendConstant(todoBlendConstant.Value); + + todoBlendConstant = null; + } } private void EndRenderEncoding() From 92c3ef2d34fb4e524246eeff155f9f2de4de0371 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 20 Jun 2026 13:41:32 +0800 Subject: [PATCH 260/466] Implement Metal draw/dispatch methods in MTLCommandBuffer Implemented draw, dispatch, and buffer binding methods in MTLCommandBuffer using Metal API calls. Added IndexBuffer struct for index buffer management. Updated MTLFormats.Metal to return both topology class and type as a tuple, and adjusted MTLGraphicsPipeline accordingly. Reset internal state in ResetImpl and ensured ArgumentTable is set for resource binding. Added placeholder for Metal(IndexFormat) conversion. Minor code cleanup and marked MTLCommandBuffer as unsafe. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 85 ++++++++++++++++--- sources/Zenith.NET.Metal/MTLFormats.cs | 40 ++++++--- .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 2 +- 3 files changed, 103 insertions(+), 24 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 8b947486..ce371928 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -3,7 +3,7 @@ namespace Zenith.NET.Metal; -internal class MTLCommandBuffer : CommandBuffer +internal unsafe class MTLCommandBuffer : CommandBuffer { public MTL4CommandAllocator CommandAllocator; @@ -22,6 +22,8 @@ internal class MTLCommandBuffer : CommandBuffer private uint? todoStencilReference; private Vector4? todoBlendConstant; + private IndexBuffer indexBuffer; + public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { CommandAllocator = context.Device.MakeCommandAllocator(); @@ -264,57 +266,93 @@ protected override void SetBlendConstantImpl(Vector4 blendConstant) protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { - throw new NotImplementedException(); + ArgumentTable.SetAddress(buffer.Metal().Buffer.GpuAddress + offsetInBytes, pipeline.Desc.InputLayouts[slot].StrideInBytes, 1 + slot); } protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - throw new NotImplementedException(); + indexBuffer = new(MTLFormats.Metal(indexFormat), + buffer.Metal().Buffer.GpuAddress + offsetInBytes, + indexFormat is IndexFormat.UInt16 ? 2u : 4u, + buffer.Desc.SizeInBytes - offsetInBytes); } protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) { - throw new NotImplementedException(); + ArgumentTable.SetAddress(buffer.Metal().Buffer.GpuAddress + offsetInBytes, 0); + + render?.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); + compute?.SetArgumentTable(ArgumentTable); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { - throw new NotImplementedException(); + render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, firstVertex, vertexCount, instanceCount, firstInstance); } protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - throw new NotImplementedException(); + nuint address = indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes; + + for (uint i = 0; i < drawCount; i++) + { + render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, address + (uint)(sizeof(IndirectDrawArgs) * i)); + } } protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) { - throw new NotImplementedException(); + render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, + indexCount, + indexBuffer.Type, + indexBuffer.Buffer + (indexBuffer.SizeInBytes * firstIndex), + indexBuffer.Length, + instanceCount, + vertexOffset, + firstInstance); } protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - throw new NotImplementedException(); + nuint address = indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes; + + for (uint i = 0; i < drawCount; i++) + { + render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, + indexBuffer.Type, + indexBuffer.Buffer, + indexBuffer.Length, + address + (uint)(sizeof(IndirectDrawIndexedArgs) * i)); + } } protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - throw new NotImplementedException(); + compute?.DispatchThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); } protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) { - throw new NotImplementedException(); + compute?.DispatchThreadgroups(indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes, pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); } protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - throw new NotImplementedException(); + render?.DrawMeshThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), + pipeline.Metal().RenderPipelineState.RequiredThreadsPerObjectThreadgroup, + pipeline.Metal().RenderPipelineState.RequiredThreadsPerMeshThreadgroup); } protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) { - throw new NotImplementedException(); + nuint address = indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes; + + for (uint i = 0; i < dispatchCount; i++) + { + render?.DrawMeshThreadgroups(address + (uint)(sizeof(IndirectDispatchMeshArgs) * i), + pipeline.Metal().RenderPipelineState.RequiredThreadsPerObjectThreadgroup, + pipeline.Metal().RenderPipelineState.RequiredThreadsPerMeshThreadgroup); + } } protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) @@ -370,6 +408,14 @@ protected override void EndImpl() protected override void ResetImpl() { + todoScissors = null; + todoViewports = null; + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + todoStencilReference = null; + todoBlendConstant = null; + CommandAllocator.Reset(); } @@ -390,6 +436,7 @@ protected override void Destroy() private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) { render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); + render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); if (todoScissors is not null) { @@ -445,6 +492,7 @@ private void EndRenderEncoding() private void BeginComputeEncoding() { compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); + compute.SetArgumentTable(ArgumentTable); if (todoComputePipeline is not null) { @@ -461,4 +509,15 @@ private void EndComputeEncoding() compute?.Dispose(); compute = null; } -} + + private struct IndexBuffer(MTLIndexType type, nuint buffer, uint sizeInBytes, uint length) + { + public MTLIndexType Type = type; + + public nuint Buffer = buffer; + + public uint SizeInBytes = sizeInBytes; + + public uint Length = length; + } +} \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 4e7b2295..ac063a92 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -220,17 +220,32 @@ public static MTLCompareFunction Metal(CompareOp compareOp) }; } - public static MTLPrimitiveTopologyClass Metal(PrimitiveTopology primitiveTopology) + public static (MTLPrimitiveTopologyClass TopologyClass, MTLPrimitiveType Type) Metal(PrimitiveTopology primitiveTopology) { - return primitiveTopology switch - { - PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, - PrimitiveTopology.LineList => MTLPrimitiveTopologyClass.Line, - PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, - PrimitiveTopology.TriangleList => MTLPrimitiveTopologyClass.Triangle, - PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, - _ => default - }; + return + ( + primitiveTopology switch + { + PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, + + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, + + PrimitiveTopology.TriangleList or + PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, + + _ => MTLPrimitiveTopologyClass.Unspecified + }, + primitiveTopology switch + { + PrimitiveTopology.PointList => MTLPrimitiveType.Point, + PrimitiveTopology.LineList => MTLPrimitiveType.Line, + PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip, + PrimitiveTopology.TriangleList => MTLPrimitiveType.Triangle, + PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip, + _ => MTLPrimitiveType.Point + } + ); } public static MTLVertexFormat Metal(ElementFormat elementFormat) @@ -414,4 +429,9 @@ internal static MTLTriangleFillMode Metal(FillMode fillMode) { throw new NotImplementedException(); } + + internal static MTLIndexType Metal(IndexFormat indexFormat) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs index 25009ddb..05c45a4a 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -14,7 +14,7 @@ public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc { VertexFunctionDescriptor = desc.VertexShader.Metal().Descriptor, FragmentFunctionDescriptor = desc.FragmentShader.Metal().Descriptor, - InputPrimitiveTopology = MTLFormats.Metal(desc.PrimitiveTopology) + InputPrimitiveTopology = MTLFormats.Metal(desc.PrimitiveTopology).TopologyClass }; // InputLayouts From 5e4fbe292ea5b29aaac3a30d6c0d2921e04161df Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 20 Jun 2026 14:23:40 +0800 Subject: [PATCH 261/466] Refactor index buffer handling and Metal mappings Replaces IndexBuffer with IndexBinding in MTLCommandBuffer for clearer field naming and usage. Updates all related method calls. Implements and exposes Metal mapping methods in MTLFormats using switch expressions, replacing previous stubs. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 30 ++++++++-------- sources/Zenith.NET.Metal/MTLFormats.cs | 37 +++++++++++++++----- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index ce371928..c95a2ba6 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -22,7 +22,7 @@ internal unsafe class MTLCommandBuffer : CommandBuffer private uint? todoStencilReference; private Vector4? todoBlendConstant; - private IndexBuffer indexBuffer; + private IndexBinding indexBinding; public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { @@ -103,7 +103,7 @@ protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSu compute?.Copy(src.Metal().Texture, srcSubresource.ArrayLayer, srcSubresource.MipLevel, - src.Metal().Texture, + dst.Metal().Texture, dstSubresource.ArrayLayer, dstSubresource.MipLevel, 1, @@ -271,10 +271,10 @@ protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer bu protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - indexBuffer = new(MTLFormats.Metal(indexFormat), - buffer.Metal().Buffer.GpuAddress + offsetInBytes, - indexFormat is IndexFormat.UInt16 ? 2u : 4u, - buffer.Desc.SizeInBytes - offsetInBytes); + indexBinding = new(MTLFormats.Metal(indexFormat), + buffer.Metal().Buffer.GpuAddress + offsetInBytes, + indexFormat is IndexFormat.UInt16 ? 2u : 4u, + buffer.Desc.SizeInBytes - offsetInBytes); } protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) @@ -304,9 +304,9 @@ protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCou { render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, indexCount, - indexBuffer.Type, - indexBuffer.Buffer + (indexBuffer.SizeInBytes * firstIndex), - indexBuffer.Length, + indexBinding.Type, + indexBinding.Address + (indexBinding.SizeInBytes * firstIndex), + indexBinding.LengthInBytes, instanceCount, vertexOffset, firstInstance); @@ -319,9 +319,9 @@ protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffe for (uint i = 0; i < drawCount; i++) { render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, - indexBuffer.Type, - indexBuffer.Buffer, - indexBuffer.Length, + indexBinding.Type, + indexBinding.Address, + indexBinding.LengthInBytes, address + (uint)(sizeof(IndirectDrawIndexedArgs) * i)); } } @@ -510,14 +510,14 @@ private void EndComputeEncoding() compute = null; } - private struct IndexBuffer(MTLIndexType type, nuint buffer, uint sizeInBytes, uint length) + private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) { public MTLIndexType Type = type; - public nuint Buffer = buffer; + public nuint Address = address; public uint SizeInBytes = sizeInBytes; - public uint Length = length; + public uint LengthInBytes = lengthInBytes; } } \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index ac063a92..7a1112a1 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -415,23 +415,44 @@ public static MTLStages Metal(BarrierStages barrierStages) return result; } - internal static MTLCullMode Metal(CullMode cullMode) + public static MTLCullMode Metal(CullMode cullMode) { - throw new NotImplementedException(); + return cullMode switch + { + CullMode.None => MTLCullMode.None, + CullMode.Front => MTLCullMode.Front, + CullMode.Back => MTLCullMode.Back, + _ => default + }; } - internal static MTLWinding Metal(FrontFace frontFace) + public static MTLWinding Metal(FrontFace frontFace) { - throw new NotImplementedException(); + return frontFace switch + { + FrontFace.CounterClockwise => MTLWinding.CounterClockwise, + FrontFace.Clockwise => MTLWinding.Clockwise, + _ => default + }; } - internal static MTLTriangleFillMode Metal(FillMode fillMode) + public static MTLTriangleFillMode Metal(FillMode fillMode) { - throw new NotImplementedException(); + return fillMode switch + { + FillMode.Solid => MTLTriangleFillMode.Fill, + FillMode.Wireframe => MTLTriangleFillMode.Lines, + _ => default + }; } - internal static MTLIndexType Metal(IndexFormat indexFormat) + public static MTLIndexType Metal(IndexFormat indexFormat) { - throw new NotImplementedException(); + return indexFormat switch + { + IndexFormat.UInt16 => MTLIndexType.UInt16, + IndexFormat.UInt32 => MTLIndexType.UInt32, + _ => default + }; } } From afb9c8840e2c6670cbbd6688f2cc3ed0cf64b04e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 15:38:58 +0800 Subject: [PATCH 262/466] Add Metal acceleration structure support and extensions Introduce MTLBottomLevelAccelerationStructure and MTLTopLevelAccelerationStructure for Metal-specific acceleration structure handling. Add Metal() extension methods for safe casting. Implement build and update methods in MTLCommandBuffer. Add Context property to MTLCommandBuffer. --- sources/Zenith.NET.Metal/Extensions.cs | 12 +++++++++ .../MTLBottomLevelAccelerationStructure.cs | 25 +++++++++++++++++ sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 10 ++++--- .../MTLTopLevelAccelerationStructure.cs | 27 +++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs create mode 100644 sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 6380453d..30678c07 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -36,6 +36,10 @@ internal ResourceHandle ToHandle() extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { + internal MTLBottomLevelAccelerationStructure Metal() + { + return (MTLBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + } } extension(Buffer buffer) @@ -56,6 +60,10 @@ internal MTLBufferView Metal() extension(CommandBuffer commandBuffer) { + internal MTLCommandBuffer Metal() + { + return (MTLCommandBuffer)commandBuffer; + } } extension(CommandQueue commandQueue) @@ -148,5 +156,9 @@ internal MTLTextureView Metal() extension(TopLevelAccelerationStructure topLevelAccelerationStructure) { + internal MTLTopLevelAccelerationStructure Metal() + { + return (MTLTopLevelAccelerationStructure)topLevelAccelerationStructure; + } } } \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs new file mode 100644 index 00000000..d9919a36 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -0,0 +1,25 @@ +namespace Zenith.NET.Metal; + +internal class MTLBottomLevelAccelerationStructure : BottomLevelAccelerationStructure +{ + public MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) + { + } + + public void Update(MTLCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) + { + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + } +} diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index c95a2ba6..a713a46f 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -39,6 +39,8 @@ public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : bas error.Success(); } + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -112,22 +114,22 @@ protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSu protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + return new MTLBottomLevelAccelerationStructure(Context, this, desc); } protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + return new MTLTopLevelAccelerationStructure(Context, this, desc); } protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) { - throw new NotImplementedException(); + accelerationStructure.Metal().Update(this, newDesc); } protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { - throw new NotImplementedException(); + accelerationStructure.Metal().Update(this, newDesc); } protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs new file mode 100644 index 00000000..c835fa2f --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs @@ -0,0 +1,27 @@ +namespace Zenith.NET.Metal; + +internal class MTLTopLevelAccelerationStructure : TopLevelAccelerationStructure +{ + public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) + { + } + + public override ResourceHandle Handle { get; } + + public void Update(MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) + { + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + } +} From c786a490f1ca1dc0ae3f56d35a08908f3fbee420 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 15:57:27 +0800 Subject: [PATCH 263/466] Refactor: replace FillInputs with Inputs returning struct Refactored DXBottomLevelAccelerationStructure and DXTopLevelAccelerationStructure to use Inputs methods that return BuildRaytracingAccelerationStructureInputs directly, replacing the previous FillInputs methods with out parameters. This improves code clarity and simplifies method signatures. --- .../DXBottomLevelAccelerationStructure.cs | 8 ++++---- .../DXTopLevelAccelerationStructure.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index dd9d9807..7b581f42 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -16,7 +16,7 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu Residency = MemoryResidency.CpuWriteOnly }); - FillInputs(scope, desc, out BuildRaytracingAccelerationStructureInputs inputs); + BuildRaytracingAccelerationStructureInputs inputs = Inputs(scope, desc); RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); @@ -71,7 +71,7 @@ public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructu { using ZenithMarshal.Scope scope = new(); - FillInputs(scope, newDesc, out BuildRaytracingAccelerationStructureInputs inputs); + BuildRaytracingAccelerationStructureInputs inputs = Inputs(scope, newDesc); inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; BuildRaytracingAccelerationStructureDesc buildDesc = new() @@ -119,7 +119,7 @@ protected override void Destroy() Transform.Dispose(); } - private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) + private BuildRaytracingAccelerationStructureInputs Inputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc) { uint geometryCount = (uint)desc.Geometries.Length; @@ -167,7 +167,7 @@ private void FillInputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStruct Transform.Unmap(); - inputs = new() + return new() { Type = RaytracingAccelerationStructureType.BottomLevel, Flags = DXFormats.DirectX12(desc.BuildFlags), diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 5b95643a..2eb8c7ad 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -15,7 +15,7 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe Residency = MemoryResidency.CpuWriteOnly }); - FillInputs(desc, out BuildRaytracingAccelerationStructureInputs inputs); + BuildRaytracingAccelerationStructureInputs inputs = Inputs(desc); RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); @@ -79,7 +79,7 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) { - FillInputs(newDesc, out BuildRaytracingAccelerationStructureInputs inputs); + BuildRaytracingAccelerationStructureInputs inputs = Inputs(newDesc); inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; BuildRaytracingAccelerationStructureDesc buildDesc = new() @@ -129,7 +129,7 @@ protected override void Destroy() Instance.Dispose(); } - private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) + private BuildRaytracingAccelerationStructureInputs Inputs(TopLevelAccelerationStructureDesc desc) { uint instanceCount = (uint)desc.Instances.Length; @@ -153,7 +153,7 @@ private void FillInputs(TopLevelAccelerationStructureDesc desc, out BuildRaytrac Instance.Unmap(); - inputs = new() + return new() { Type = RaytracingAccelerationStructureType.TopLevel, Flags = DXFormats.DirectX12(desc.BuildFlags), From 63b476feac67299868bd7e0300b313d4f580e344 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 16:28:49 +0800 Subject: [PATCH 264/466] Refactor encoder fields; add BLAS management class Refactor MTLCommandBuffer to use public Render/Compute properties instead of private fields. Replace all usages accordingly. Introduce MTLBottomLevelAccelerationStructure for Metal BLAS creation, update, and cleanup, with resource management and a stub for descriptor creation. --- .../MTLBottomLevelAccelerationStructure.cs | 51 ++++++- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 139 ++++++++++-------- 2 files changed, 125 insertions(+), 65 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs index d9919a36..41e7688d 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -1,13 +1,49 @@ -namespace Zenith.NET.Metal; +using Metal.NET; -internal class MTLBottomLevelAccelerationStructure : BottomLevelAccelerationStructure +namespace Zenith.NET.Metal; + +internal unsafe class MTLBottomLevelAccelerationStructure : BottomLevelAccelerationStructure { + public MTLAccelerationStructure AccelerationStructure; + public MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) { + Transform = new(context, new() + { + SizeInBytes = (uint)(sizeof(MTLPackedFloat4x3) * desc.Geometries.Length), + Residency = MemoryResidency.CpuWriteOnly + }); + + MTL4PrimitiveAccelerationStructureDescriptor descriptor = Descriptor(desc); + + MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); + + context.Register(AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize)); + + Scratch = new(context, new() + { + SizeInBytes = (uint)sizes.BuildScratchBufferSize, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly + }); + + commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); } + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public MTLBuffer Transform { get; } + + public MTLBuffer Scratch { get; } + public void Update(MTLCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) { + MTL4PrimitiveAccelerationStructureDescriptor descriptor = Descriptor(newDesc); + descriptor.Usage |= MTLAccelerationStructureUsage.Refit; + + commandBuffer.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); } public override nint GetNativeObject(NativeObjectType type) @@ -17,9 +53,20 @@ public override nint GetNativeObject(NativeObjectType type) protected override void SetResourceName(string name) { + AccelerationStructure.Label = name; } protected override void Destroy() { + Context.Unregister(AccelerationStructure); + + Scratch.Dispose(); + Transform.Dispose(); + AccelerationStructure.Dispose(); + } + + private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); } } diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index a713a46f..24f0ff77 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -11,8 +11,9 @@ internal unsafe class MTLCommandBuffer : CommandBuffer public MTL4ArgumentTable ArgumentTable; - private MTL4RenderCommandEncoder? render; - private MTL4ComputeCommandEncoder? compute; + public MTL4RenderCommandEncoder? Render; + + public MTL4ComputeCommandEncoder? Compute; private Scissor[]? todoScissors; private Viewport[]? todoViewports; @@ -48,8 +49,8 @@ public override nint GetNativeObject(NativeObjectType type) protected override void BarrierImpl(BarrierStages before, BarrierStages after) { - render?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(before), MTL4VisibilityOptions.Device); - compute?.BarrierAfterStages(MTLFormats.Metal(after), MTLFormats.Metal(before), MTL4VisibilityOptions.Device); + Render?.BarrierAfterStages(MTLFormats.Metal(before), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); + Compute?.BarrierAfterStages(MTLFormats.Metal(before), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); } protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) @@ -58,12 +59,12 @@ protected override void TransitionImpl(Texture texture, TextureSubresource subre protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { - compute?.Copy(src.Metal().Buffer, srcOffsetInBytes, dst.Metal().Buffer, dstOffsetInBytes, sizeInBytes); + Compute?.Copy(src.Metal().Buffer, srcOffsetInBytes, dst.Metal().Buffer, dstOffsetInBytes, sizeInBytes); } protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - compute?.Copy(src.Metal().Buffer, + Compute?.Copy(src.Metal().Buffer, srcOffsetInBytes, srcRowStrideInBytes, srcSliceStrideInBytes, @@ -76,7 +77,7 @@ protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInByte protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - compute?.Copy(src.Metal().Texture, + Compute?.Copy(src.Metal().Texture, srcSubresource.ArrayLayer, srcSubresource.MipLevel, new(srcOffset.X, srcOffset.Y, srcOffset.Z), @@ -89,7 +90,7 @@ protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubre protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { - compute?.Copy(src.Metal().Texture, + Compute?.Copy(src.Metal().Texture, srcSubresource.ArrayLayer, srcSubresource.MipLevel, new(srcOffset.X, srcOffset.Y, srcOffset.Z), @@ -102,7 +103,7 @@ protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { - compute?.Copy(src.Metal().Texture, + Compute?.Copy(src.Metal().Texture, srcSubresource.ArrayLayer, srcSubresource.MipLevel, dst.Metal().Texture, @@ -146,7 +147,7 @@ protected override void EndRenderPassImpl() protected override void SetScissorsImpl(ReadOnlySpan scissors) { - if (render is null) + if (Render is null) { todoScissors = [.. scissors]; } @@ -160,13 +161,13 @@ protected override void SetScissorsImpl(ReadOnlySpan scissors) mtlScissors[i] = new((uint)scissor.X, (uint)scissor.Y, scissor.Width, scissor.Height); } - render.SetScissorRects(mtlScissors); + Render.SetScissorRects(mtlScissors); } } protected override void SetViewportsImpl(ReadOnlySpan viewports) { - if (render is null) + if (Render is null) { todoViewports = [.. viewports]; } @@ -180,13 +181,13 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) mtlViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); } - render.SetViewports(mtlViewports); + Render.SetViewports(mtlViewports); } } protected override void SetPipelineImpl(GraphicsPipeline pipeline) { - if (render is null) + if (Render is null) { todoGraphicsPipeline = pipeline; todoComputePipeline = null; @@ -194,21 +195,25 @@ protected override void SetPipelineImpl(GraphicsPipeline pipeline) } else { + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + MTLGraphicsPipeline mtlPipeline = pipeline.Metal(); - render.SetDepthStencilState(mtlPipeline.DepthStencilState); - render.SetRenderPipelineState(mtlPipeline.RenderPipelineState); - render.SetCullMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.CullMode)); - render.SetFrontFacing(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FrontFace)); - render.SetTriangleFillMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FillMode)); - render.SetDepthClipMode(mtlPipeline.Desc.RenderState.Rasterizer.IsDepthClipEnabled ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); - render.SetDepthBias(mtlPipeline.Desc.RenderState.Rasterizer.DepthBias, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasSlopeScale, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasClamp); + Render.SetDepthStencilState(mtlPipeline.DepthStencilState); + Render.SetRenderPipelineState(mtlPipeline.RenderPipelineState); + Render.SetCullMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.CullMode)); + Render.SetFrontFacing(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FrontFace)); + Render.SetTriangleFillMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FillMode)); + Render.SetDepthClipMode(mtlPipeline.Desc.RenderState.Rasterizer.IsDepthClipEnabled ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); + Render.SetDepthBias(mtlPipeline.Desc.RenderState.Rasterizer.DepthBias, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasSlopeScale, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasClamp); } } protected override void SetPipelineImpl(ComputePipeline pipeline) { - if (compute is null) + if (Compute is null) { todoGraphicsPipeline = null; todoComputePipeline = pipeline; @@ -216,13 +221,17 @@ protected override void SetPipelineImpl(ComputePipeline pipeline) } else { - compute.SetComputePipelineState(pipeline.Metal().ComputePipelineState); + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + + Compute.SetComputePipelineState(pipeline.Metal().ComputePipelineState); } } protected override void SetPipelineImpl(MeshShadingPipeline pipeline) { - if (render is null) + if (Render is null) { todoGraphicsPipeline = null; todoComputePipeline = null; @@ -230,39 +239,43 @@ protected override void SetPipelineImpl(MeshShadingPipeline pipeline) } else { + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + MTLMeshShadingPipeline mtlPipeline = pipeline.Metal(); - render.SetDepthStencilState(mtlPipeline.DepthStencilState); - render.SetRenderPipelineState(mtlPipeline.RenderPipelineState); - render.SetCullMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.CullMode)); - render.SetFrontFacing(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FrontFace)); - render.SetTriangleFillMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FillMode)); - render.SetDepthClipMode(mtlPipeline.Desc.RenderState.Rasterizer.IsDepthClipEnabled ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); - render.SetDepthBias(mtlPipeline.Desc.RenderState.Rasterizer.DepthBias, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasSlopeScale, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasClamp); + Render.SetDepthStencilState(mtlPipeline.DepthStencilState); + Render.SetRenderPipelineState(mtlPipeline.RenderPipelineState); + Render.SetCullMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.CullMode)); + Render.SetFrontFacing(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FrontFace)); + Render.SetTriangleFillMode(MTLFormats.Metal(mtlPipeline.Desc.RenderState.Rasterizer.FillMode)); + Render.SetDepthClipMode(mtlPipeline.Desc.RenderState.Rasterizer.IsDepthClipEnabled ? MTLDepthClipMode.Clip : MTLDepthClipMode.Clamp); + Render.SetDepthBias(mtlPipeline.Desc.RenderState.Rasterizer.DepthBias, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasSlopeScale, mtlPipeline.Desc.RenderState.Rasterizer.DepthBiasClamp); } } protected override void SetStencilReferenceImpl(uint stencilReference) { - if (render is null) + if (Render is null) { todoStencilReference = stencilReference; } else { - render.SetStencilReferenceValue(stencilReference); + Render.SetStencilReferenceValue(stencilReference); } } protected override void SetBlendConstantImpl(Vector4 blendConstant) { - if (render is null) + if (Render is null) { todoBlendConstant = blendConstant; } else { - render.SetBlendColor(blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W); + Render.SetBlendColor(blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W); } } @@ -283,13 +296,13 @@ protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, { ArgumentTable.SetAddress(buffer.Metal().Buffer.GpuAddress + offsetInBytes, 0); - render?.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); - compute?.SetArgumentTable(ArgumentTable); + Render?.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); + Compute?.SetArgumentTable(ArgumentTable); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { - render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, firstVertex, vertexCount, instanceCount, firstInstance); + Render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, firstVertex, vertexCount, instanceCount, firstInstance); } protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) @@ -298,13 +311,13 @@ protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indir for (uint i = 0; i < drawCount; i++) { - render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, address + (uint)(sizeof(IndirectDrawArgs) * i)); + Render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, address + (uint)(sizeof(IndirectDrawArgs) * i)); } } protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) { - render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, + Render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, indexCount, indexBinding.Type, indexBinding.Address + (indexBinding.SizeInBytes * firstIndex), @@ -320,7 +333,7 @@ protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffe for (uint i = 0; i < drawCount; i++) { - render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, + Render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, indexBinding.Type, indexBinding.Address, indexBinding.LengthInBytes, @@ -330,17 +343,17 @@ protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffe protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - compute?.DispatchThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); + Compute?.DispatchThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); } protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) { - compute?.DispatchThreadgroups(indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes, pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); + Compute?.DispatchThreadgroups(indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes, pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); } protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - render?.DrawMeshThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), + Render?.DrawMeshThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), pipeline.Metal().RenderPipelineState.RequiredThreadsPerObjectThreadgroup, pipeline.Metal().RenderPipelineState.RequiredThreadsPerMeshThreadgroup); } @@ -351,7 +364,7 @@ protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, B for (uint i = 0; i < dispatchCount; i++) { - render?.DrawMeshThreadgroups(address + (uint)(sizeof(IndirectDispatchMeshArgs) * i), + Render?.DrawMeshThreadgroups(address + (uint)(sizeof(IndirectDispatchMeshArgs) * i), pipeline.Metal().RenderPipelineState.RequiredThreadsPerObjectThreadgroup, pipeline.Metal().RenderPipelineState.RequiredThreadsPerMeshThreadgroup); } @@ -377,20 +390,20 @@ protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) protected override void BeginDebugEventImpl(string label) { - render?.PushDebugGroup(label); - compute?.PushDebugGroup(label); + Render?.PushDebugGroup(label); + Compute?.PushDebugGroup(label); } protected override void EndDebugEventImpl() { - render?.PopDebugGroup(); - compute?.PopDebugGroup(); + Render?.PopDebugGroup(); + Compute?.PopDebugGroup(); } protected override void InsertDebugMarkerImpl(string label) { - render?.InsertDebugSignpost(label); - compute?.InsertDebugSignpost(label); + Render?.InsertDebugSignpost(label); + Compute?.InsertDebugSignpost(label); } protected override void BeginImpl() @@ -437,8 +450,8 @@ protected override void Destroy() private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) { - render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); - render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); + Render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); + Render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); if (todoScissors is not null) { @@ -485,16 +498,16 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) private void EndRenderEncoding() { - render?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); - render?.EndEncoding(); - render?.Dispose(); - render = null; + Render?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); + Render?.EndEncoding(); + Render?.Dispose(); + Render = null; } private void BeginComputeEncoding() { - compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); - compute.SetArgumentTable(ArgumentTable); + Compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); + Compute.SetArgumentTable(ArgumentTable); if (todoComputePipeline is not null) { @@ -506,10 +519,10 @@ private void BeginComputeEncoding() private void EndComputeEncoding() { - compute?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); - compute?.EndEncoding(); - compute?.Dispose(); - compute = null; + Compute?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); + Compute?.EndEncoding(); + Compute?.Dispose(); + Compute = null; } private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) From 49879425a7d7bd771764be86a5511041b537d94f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 16:54:42 +0800 Subject: [PATCH 265/466] Refactor Metal pixel format handling and ray tracing desc Refactor MTLFormats.Metal(PixelFormat) to return a tuple of pixel and attribute formats, updating all usages to extract PixelFormat as needed. Implement MTLBottomLevelAccelerationStructure.Descriptor for Metal ray tracing geometry descriptors. Add stub conversions for Matrix4x4 and AccelerationStructureBuildFlags. --- .../MTLBottomLevelAccelerationStructure.cs | 65 ++++- sources/Zenith.NET.Metal/MTLFormats.cs | 228 ++++++++++-------- .../Zenith.NET.Metal/MTLGraphicsPipeline.cs | 2 +- .../MTLMeshShadingPipeline.cs | 2 +- sources/Zenith.NET.Metal/MTLSwapChain.cs | 2 +- sources/Zenith.NET.Metal/MTLTexture.cs | 2 +- sources/Zenith.NET.Metal/MTLTextureView.cs | 2 +- 7 files changed, 192 insertions(+), 111 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs index 41e7688d..2f5e25d6 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -67,6 +67,69 @@ protected override void Destroy() private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + uint geometryCount = (uint)desc.Geometries.Length; + + nint pointer = Transform.Map(); + + MTLPackedFloat4x3* transforms = (MTLPackedFloat4x3*)pointer; + MTL4AccelerationStructureGeometryDescriptor[] geometryDescriptors = new MTL4AccelerationStructureGeometryDescriptor[geometryCount]; + for (uint i = 0; i < geometryCount; i++) + { + RayTracingGeometry geometry = desc.Geometries[i]; + + transforms[i] = MTLFormats.Metal(geometry.TriangleGeometry.Transform); + + switch (geometry.Type) + { + case RayTracingGeometryType.Triangle: + geometryDescriptors[i] = new MTL4AccelerationStructureTriangleGeometryDescriptor() + { + VertexBuffer = new() + { + BufferAddress = geometry.TriangleGeometry.VertexBuffer.Metal().Buffer.GpuAddress + geometry.TriangleGeometry.VertexOffsetInBytes, + Length = geometry.TriangleGeometry.VertexStrideInBytes * geometry.TriangleGeometry.VertexCount + }, + VertexFormat = MTLFormats.Metal(geometry.TriangleGeometry.VertexFormat).AttributeFormat, + VertexStride = geometry.TriangleGeometry.VertexStrideInBytes, + IndexBuffer = geometry.TriangleGeometry.IndexBuffer is not null ? new() + { + BufferAddress = geometry.TriangleGeometry.IndexBuffer.Metal().Buffer.GpuAddress + geometry.TriangleGeometry.IndexOffsetInBytes, + Length = (geometry.TriangleGeometry.IndexFormat is IndexFormat.UInt16 ? 2u : 4u) * geometry.TriangleGeometry.IndexCount + } : default, + IndexType = MTLFormats.Metal(geometry.TriangleGeometry.IndexFormat), + TriangleCount = geometry.TriangleGeometry.IndexBuffer is not null ? geometry.TriangleGeometry.IndexCount / 3 : geometry.TriangleGeometry.VertexCount / 3, + TransformationMatrixBuffer = new() + { + BufferAddress = Transform.Buffer.GpuAddress + (uint)(sizeof(MTLPackedFloat4x3) * i), + Length = (uint)sizeof(MTLPackedFloat4x3) + }, + TransformationMatrixLayout = MTLMatrixLayout.RowMajor + }; + break; + + case RayTracingGeometryType.Aabb: + geometryDescriptors[i] = new MTL4AccelerationStructureBoundingBoxGeometryDescriptor() + { + BoundingBoxBuffer = new() + { + BufferAddress = geometry.AabbGeometry.Buffer.Metal().Buffer.GpuAddress + geometry.AabbGeometry.OffsetInBytes, + Length = geometry.AabbGeometry.StrideInBytes * geometry.AabbGeometry.Count + }, + BoundingBoxStride = geometry.AabbGeometry.StrideInBytes, + BoundingBoxCount = geometry.AabbGeometry.Count + }; + break; + } + + geometryDescriptors[i].Opaque = geometry.IsOpaque; + } + + Transform.Unmap(); + + return new() + { + GeometryDescriptors = geometryDescriptors, + Usage = MTLFormats.Metal(desc.BuildFlags) + }; } } diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 7a1112a1..209e27cc 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -1,4 +1,5 @@ -using Metal.NET; +using System.Numerics; +using Metal.NET; namespace Zenith.NET.Metal; @@ -30,109 +31,116 @@ public static MTLTextureType Metal(TextureType textureType, SampleCount sampleCo }; } - public static MTLPixelFormat Metal(PixelFormat pixelFormat) + public static (MTLPixelFormat PixelFormat, MTLAttributeFormat AttributeFormat) Metal(PixelFormat pixelFormat) { - return pixelFormat switch - { - PixelFormat.R8UNorm => MTLPixelFormat.R8Unorm, - PixelFormat.R8SNorm => MTLPixelFormat.R8Snorm, - PixelFormat.R8UInt => MTLPixelFormat.R8Uint, - PixelFormat.R8SInt => MTLPixelFormat.R8Sint, - - PixelFormat.R16UNorm => MTLPixelFormat.R16Unorm, - PixelFormat.R16SNorm => MTLPixelFormat.R16Snorm, - PixelFormat.R16UInt => MTLPixelFormat.R16Uint, - PixelFormat.R16SInt => MTLPixelFormat.R16Sint, - PixelFormat.R16Float => MTLPixelFormat.R16Float, - - PixelFormat.R32UInt => MTLPixelFormat.R32Uint, - PixelFormat.R32SInt => MTLPixelFormat.R32Sint, - PixelFormat.R32Float => MTLPixelFormat.R32Float, - - PixelFormat.R8G8UNorm => MTLPixelFormat.RG8Unorm, - PixelFormat.R8G8SNorm => MTLPixelFormat.RG8Snorm, - PixelFormat.R8G8UInt => MTLPixelFormat.RG8Uint, - PixelFormat.R8G8SInt => MTLPixelFormat.RG8Sint, - - PixelFormat.R16G16UNorm => MTLPixelFormat.RG16Unorm, - PixelFormat.R16G16SNorm => MTLPixelFormat.RG16Snorm, - PixelFormat.R16G16UInt => MTLPixelFormat.RG16Uint, - PixelFormat.R16G16SInt => MTLPixelFormat.RG16Sint, - PixelFormat.R16G16Float => MTLPixelFormat.RG16Float, - - PixelFormat.R32G32UInt => MTLPixelFormat.RG32Uint, - PixelFormat.R32G32SInt => MTLPixelFormat.RG32Sint, - PixelFormat.R32G32Float => MTLPixelFormat.RG32Float, - - PixelFormat.R8G8B8A8UNorm => MTLPixelFormat.RGBA8Unorm, - PixelFormat.R8G8B8A8SNorm => MTLPixelFormat.RGBA8Snorm, - PixelFormat.R8G8B8A8UInt => MTLPixelFormat.RGBA8Uint, - PixelFormat.R8G8B8A8SInt => MTLPixelFormat.RGBA8Sint, - PixelFormat.R8G8B8A8SRgb => MTLPixelFormat.RGBA8Unorm_sRGB, - - PixelFormat.R16G16B16A16UNorm => MTLPixelFormat.RGBA16Unorm, - PixelFormat.R16G16B16A16SNorm => MTLPixelFormat.RGBA16Snorm, - PixelFormat.R16G16B16A16UInt => MTLPixelFormat.RGBA16Uint, - PixelFormat.R16G16B16A16SInt => MTLPixelFormat.RGBA16Sint, - PixelFormat.R16G16B16A16Float => MTLPixelFormat.RGBA16Float, - - PixelFormat.R32G32B32A32UInt => MTLPixelFormat.RGBA32Uint, - PixelFormat.R32G32B32A32SInt => MTLPixelFormat.RGBA32Sint, - PixelFormat.R32G32B32A32Float => MTLPixelFormat.RGBA32Float, - - PixelFormat.B8G8R8A8UNorm => MTLPixelFormat.BGRA8Unorm, - PixelFormat.B8G8R8A8SRgb => MTLPixelFormat.BGRA8Unorm_sRGB, - - PixelFormat.D16UNorm => MTLPixelFormat.Depth16Unorm, - PixelFormat.D24UNormS8UInt => MTLPixelFormat.Depth24Unorm_Stencil8, - PixelFormat.D32Float => MTLPixelFormat.Depth32Float, - PixelFormat.D32FloatS8UInt => MTLPixelFormat.Depth32Float_Stencil8, - - PixelFormat.BC4UNorm => MTLPixelFormat.BC4_RUnorm, - PixelFormat.BC4SNorm => MTLPixelFormat.BC4_RSnorm, - - PixelFormat.BC5UNorm => MTLPixelFormat.BC5_RGUnorm, - PixelFormat.BC5SNorm => MTLPixelFormat.BC5_RGSnorm, - - PixelFormat.BC6HUFloat => MTLPixelFormat.BC6H_RGBUfloat, - PixelFormat.BC6HSFloat => MTLPixelFormat.BC6H_RGBFloat, - - PixelFormat.BC7UNorm => MTLPixelFormat.BC7_RGBAUnorm, - PixelFormat.BC7SRgb => MTLPixelFormat.BC7_RGBAUnorm_sRGB, - - PixelFormat.ETC2UNorm => MTLPixelFormat.ETC2_RGB8, - PixelFormat.ETC2SRgb => MTLPixelFormat.ETC2_RGB8_sRGB, - PixelFormat.ETC2A1UNorm => MTLPixelFormat.ETC2_RGB8A1, - PixelFormat.ETC2A1SRgb => MTLPixelFormat.ETC2_RGB8A1_sRGB, - PixelFormat.ETC2A8UNorm => MTLPixelFormat.EAC_RGBA8, - PixelFormat.ETC2A8SRgb => MTLPixelFormat.EAC_RGBA8_sRGB, - - PixelFormat.ASTC4x4UNorm => MTLPixelFormat.ASTC_4x4_LDR, - PixelFormat.ASTC4x4SRgb => MTLPixelFormat.ASTC_4x4_sRGB, - PixelFormat.ASTC4x4Float => MTLPixelFormat.ASTC_4x4_HDR, - - PixelFormat.ASTC5x5UNorm => MTLPixelFormat.ASTC_5x5_LDR, - PixelFormat.ASTC5x5SRgb => MTLPixelFormat.ASTC_5x5_sRGB, - PixelFormat.ASTC5x5Float => MTLPixelFormat.ASTC_5x5_HDR, - - PixelFormat.ASTC6x6UNorm => MTLPixelFormat.ASTC_6x6_LDR, - PixelFormat.ASTC6x6SRgb => MTLPixelFormat.ASTC_6x6_sRGB, - PixelFormat.ASTC6x6Float => MTLPixelFormat.ASTC_6x6_HDR, - - PixelFormat.ASTC8x8UNorm => MTLPixelFormat.ASTC_8x8_LDR, - PixelFormat.ASTC8x8SRgb => MTLPixelFormat.ASTC_8x8_sRGB, - PixelFormat.ASTC8x8Float => MTLPixelFormat.ASTC_8x8_HDR, - - PixelFormat.ASTC10x10UNorm => MTLPixelFormat.ASTC_10x10_LDR, - PixelFormat.ASTC10x10SRgb => MTLPixelFormat.ASTC_10x10_sRGB, - PixelFormat.ASTC10x10Float => MTLPixelFormat.ASTC_10x10_HDR, - - PixelFormat.ASTC12x12UNorm => MTLPixelFormat.ASTC_12x12_LDR, - PixelFormat.ASTC12x12SRgb => MTLPixelFormat.ASTC_12x12_sRGB, - PixelFormat.ASTC12x12Float => MTLPixelFormat.ASTC_12x12_HDR, - - _ => default - }; + return + ( + pixelFormat switch + { + PixelFormat.R8UNorm => MTLPixelFormat.R8Unorm, + PixelFormat.R8SNorm => MTLPixelFormat.R8Snorm, + PixelFormat.R8UInt => MTLPixelFormat.R8Uint, + PixelFormat.R8SInt => MTLPixelFormat.R8Sint, + + PixelFormat.R16UNorm => MTLPixelFormat.R16Unorm, + PixelFormat.R16SNorm => MTLPixelFormat.R16Snorm, + PixelFormat.R16UInt => MTLPixelFormat.R16Uint, + PixelFormat.R16SInt => MTLPixelFormat.R16Sint, + PixelFormat.R16Float => MTLPixelFormat.R16Float, + + PixelFormat.R32UInt => MTLPixelFormat.R32Uint, + PixelFormat.R32SInt => MTLPixelFormat.R32Sint, + PixelFormat.R32Float => MTLPixelFormat.R32Float, + + PixelFormat.R8G8UNorm => MTLPixelFormat.RG8Unorm, + PixelFormat.R8G8SNorm => MTLPixelFormat.RG8Snorm, + PixelFormat.R8G8UInt => MTLPixelFormat.RG8Uint, + PixelFormat.R8G8SInt => MTLPixelFormat.RG8Sint, + + PixelFormat.R16G16UNorm => MTLPixelFormat.RG16Unorm, + PixelFormat.R16G16SNorm => MTLPixelFormat.RG16Snorm, + PixelFormat.R16G16UInt => MTLPixelFormat.RG16Uint, + PixelFormat.R16G16SInt => MTLPixelFormat.RG16Sint, + PixelFormat.R16G16Float => MTLPixelFormat.RG16Float, + + PixelFormat.R32G32UInt => MTLPixelFormat.RG32Uint, + PixelFormat.R32G32SInt => MTLPixelFormat.RG32Sint, + PixelFormat.R32G32Float => MTLPixelFormat.RG32Float, + + PixelFormat.R8G8B8A8UNorm => MTLPixelFormat.RGBA8Unorm, + PixelFormat.R8G8B8A8SNorm => MTLPixelFormat.RGBA8Snorm, + PixelFormat.R8G8B8A8UInt => MTLPixelFormat.RGBA8Uint, + PixelFormat.R8G8B8A8SInt => MTLPixelFormat.RGBA8Sint, + PixelFormat.R8G8B8A8SRgb => MTLPixelFormat.RGBA8Unorm_sRGB, + + PixelFormat.R16G16B16A16UNorm => MTLPixelFormat.RGBA16Unorm, + PixelFormat.R16G16B16A16SNorm => MTLPixelFormat.RGBA16Snorm, + PixelFormat.R16G16B16A16UInt => MTLPixelFormat.RGBA16Uint, + PixelFormat.R16G16B16A16SInt => MTLPixelFormat.RGBA16Sint, + PixelFormat.R16G16B16A16Float => MTLPixelFormat.RGBA16Float, + + PixelFormat.R32G32B32A32UInt => MTLPixelFormat.RGBA32Uint, + PixelFormat.R32G32B32A32SInt => MTLPixelFormat.RGBA32Sint, + PixelFormat.R32G32B32A32Float => MTLPixelFormat.RGBA32Float, + + PixelFormat.B8G8R8A8UNorm => MTLPixelFormat.BGRA8Unorm, + PixelFormat.B8G8R8A8SRgb => MTLPixelFormat.BGRA8Unorm_sRGB, + + PixelFormat.D16UNorm => MTLPixelFormat.Depth16Unorm, + PixelFormat.D24UNormS8UInt => MTLPixelFormat.Depth24Unorm_Stencil8, + PixelFormat.D32Float => MTLPixelFormat.Depth32Float, + PixelFormat.D32FloatS8UInt => MTLPixelFormat.Depth32Float_Stencil8, + + PixelFormat.BC4UNorm => MTLPixelFormat.BC4_RUnorm, + PixelFormat.BC4SNorm => MTLPixelFormat.BC4_RSnorm, + + PixelFormat.BC5UNorm => MTLPixelFormat.BC5_RGUnorm, + PixelFormat.BC5SNorm => MTLPixelFormat.BC5_RGSnorm, + + PixelFormat.BC6HUFloat => MTLPixelFormat.BC6H_RGBUfloat, + PixelFormat.BC6HSFloat => MTLPixelFormat.BC6H_RGBFloat, + + PixelFormat.BC7UNorm => MTLPixelFormat.BC7_RGBAUnorm, + PixelFormat.BC7SRgb => MTLPixelFormat.BC7_RGBAUnorm_sRGB, + + PixelFormat.ETC2UNorm => MTLPixelFormat.ETC2_RGB8, + PixelFormat.ETC2SRgb => MTLPixelFormat.ETC2_RGB8_sRGB, + PixelFormat.ETC2A1UNorm => MTLPixelFormat.ETC2_RGB8A1, + PixelFormat.ETC2A1SRgb => MTLPixelFormat.ETC2_RGB8A1_sRGB, + PixelFormat.ETC2A8UNorm => MTLPixelFormat.EAC_RGBA8, + PixelFormat.ETC2A8SRgb => MTLPixelFormat.EAC_RGBA8_sRGB, + + PixelFormat.ASTC4x4UNorm => MTLPixelFormat.ASTC_4x4_LDR, + PixelFormat.ASTC4x4SRgb => MTLPixelFormat.ASTC_4x4_sRGB, + PixelFormat.ASTC4x4Float => MTLPixelFormat.ASTC_4x4_HDR, + + PixelFormat.ASTC5x5UNorm => MTLPixelFormat.ASTC_5x5_LDR, + PixelFormat.ASTC5x5SRgb => MTLPixelFormat.ASTC_5x5_sRGB, + PixelFormat.ASTC5x5Float => MTLPixelFormat.ASTC_5x5_HDR, + + PixelFormat.ASTC6x6UNorm => MTLPixelFormat.ASTC_6x6_LDR, + PixelFormat.ASTC6x6SRgb => MTLPixelFormat.ASTC_6x6_sRGB, + PixelFormat.ASTC6x6Float => MTLPixelFormat.ASTC_6x6_HDR, + + PixelFormat.ASTC8x8UNorm => MTLPixelFormat.ASTC_8x8_LDR, + PixelFormat.ASTC8x8SRgb => MTLPixelFormat.ASTC_8x8_sRGB, + PixelFormat.ASTC8x8Float => MTLPixelFormat.ASTC_8x8_HDR, + + PixelFormat.ASTC10x10UNorm => MTLPixelFormat.ASTC_10x10_LDR, + PixelFormat.ASTC10x10SRgb => MTLPixelFormat.ASTC_10x10_sRGB, + PixelFormat.ASTC10x10Float => MTLPixelFormat.ASTC_10x10_HDR, + + PixelFormat.ASTC12x12UNorm => MTLPixelFormat.ASTC_12x12_LDR, + PixelFormat.ASTC12x12SRgb => MTLPixelFormat.ASTC_12x12_sRGB, + PixelFormat.ASTC12x12Float => MTLPixelFormat.ASTC_12x12_HDR, + + _ => default + }, + pixelFormat switch + { + _ => default + } + ); } public static nuint Metal(SampleCount sampleCount) @@ -234,7 +242,7 @@ PrimitiveTopology.LineList or PrimitiveTopology.TriangleList or PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, - _ => MTLPrimitiveTopologyClass.Unspecified + _ => default }, primitiveTopology switch { @@ -243,7 +251,7 @@ PrimitiveTopology.TriangleList or PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip, PrimitiveTopology.TriangleList => MTLPrimitiveType.Triangle, PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip, - _ => MTLPrimitiveType.Point + _ => default } ); } @@ -455,4 +463,14 @@ public static MTLIndexType Metal(IndexFormat indexFormat) _ => default }; } + + public static MTLPackedFloat4x3 Metal(Matrix4x4 matrix4x4) + { + throw new NotImplementedException(); + } + + public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs index 05c45a4a..6d345721 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -63,7 +63,7 @@ public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc descriptor.ColorAttachments[(uint)i] = new() { - PixelFormat = MTLFormats.Metal(desc.AttachmentFormats.ColorFormats[i]), + PixelFormat = MTLFormats.Metal(desc.AttachmentFormats.ColorFormats[i]).PixelFormat, BlendingState = state.IsBlendingEnabled ? MTL4BlendState.Enabled : MTL4BlendState.Disabled, SourceRGBBlendFactor = MTLFormats.Metal(state.SrcRgbFactor), DestinationRGBBlendFactor = MTLFormats.Metal(state.DstRgbFactor), diff --git a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs index 445ee120..065b5789 100644 --- a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs @@ -46,7 +46,7 @@ public MTLMeshShadingPipeline(MTLGraphicsContext context, MeshShadingPipelineDes descriptor.ColorAttachments[(uint)i] = new() { - PixelFormat = MTLFormats.Metal(desc.AttachmentFormats.ColorFormats[i]), + PixelFormat = MTLFormats.Metal(desc.AttachmentFormats.ColorFormats[i]).PixelFormat, BlendingState = state.IsBlendingEnabled ? MTL4BlendState.Enabled : MTL4BlendState.Disabled, SourceRGBBlendFactor = MTLFormats.Metal(state.SrcRgbFactor), DestinationRGBBlendFactor = MTLFormats.Metal(state.DstRgbFactor), diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 56f8ba29..adac5218 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -93,7 +93,7 @@ private void Initialize() MetalLayer = new(Desc.Surface.NativeHandles[0], NativeObjectOwnership.Borrowed) { Device = Context.Device, - PixelFormat = MTLFormats.Metal(Desc.Format), + PixelFormat = MTLFormats.Metal(Desc.Format).PixelFormat, FramebufferOnly = false, DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height) }; diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs index 3a7c92a5..88cd42ca 100644 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -63,7 +63,7 @@ public static MTLTextureDescriptor Descriptor(TextureDesc desc) return new() { TextureType = MTLFormats.Metal(desc.Type, desc.SampleCount), - PixelFormat = MTLFormats.Metal(desc.Format), + PixelFormat = MTLFormats.Metal(desc.Format).PixelFormat, Width = desc.Width, Height = desc.Height, Depth = desc.Depth, diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs index 551aa13d..73d580e4 100644 --- a/sources/Zenith.NET.Metal/MTLTextureView.cs +++ b/sources/Zenith.NET.Metal/MTLTextureView.cs @@ -6,7 +6,7 @@ internal class MTLTextureView : TextureView public MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : base(context, desc) { - Texture = desc.Texture.Metal().Texture.MakeTextureView(MTLFormats.Metal(desc.Format), + Texture = desc.Texture.Metal().Texture.MakeTextureView(MTLFormats.Metal(desc.Format).PixelFormat, MTLFormats.Metal(desc.Type, desc.Texture.Desc.SampleCount), new(desc.Range.BaseMipLevel, desc.Range.LevelCount), new(desc.Range.BaseArrayLayer, desc.Range.LayerCount)); From f405d43931365ddeff15bc9590f5fd3fb792fcc3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 17:08:38 +0800 Subject: [PATCH 266/466] Refactor and expand MTLTopLevelAccelerationStructure Refactored the class to be internal and unsafe, moved it to the Zenith.NET.Metal namespace, and added Metal.NET usage. Introduced new fields for AccelerationStructure, Instance, and Scratch buffers. Updated the constructor to initialize these resources and build the acceleration structure. Enhanced Update to support refitting, improved resource cleanup in Destroy, and set resource labels in SetResourceName. Added a stub for the Descriptor method. --- .../MTLTopLevelAccelerationStructure.cs | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs index c835fa2f..b0e0f414 100644 --- a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs @@ -1,15 +1,51 @@ -namespace Zenith.NET.Metal; +using Metal.NET; -internal class MTLTopLevelAccelerationStructure : TopLevelAccelerationStructure +namespace Zenith.NET.Metal; + +internal unsafe class MTLTopLevelAccelerationStructure : TopLevelAccelerationStructure { + public MTLAccelerationStructure AccelerationStructure; + public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { + Instance = new(context, new() + { + SizeInBytes = (uint)(sizeof(MTLIndirectAccelerationStructureInstanceDescriptor) * desc.Instances.Length), + Residency = MemoryResidency.CpuWriteOnly + }); + + MTL4InstanceAccelerationStructureDescriptor descriptor = Descriptor(desc); + + MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); + + context.Register(AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize)); + + Scratch = new(context, new() + { + SizeInBytes = (uint)sizes.BuildScratchBufferSize, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly + }); + + commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); } + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public MTLBuffer Instance { get; } + + public MTLBuffer Scratch { get; } + public override ResourceHandle Handle { get; } public void Update(MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) { + MTL4InstanceAccelerationStructureDescriptor descriptor = Descriptor(newDesc); + descriptor.Usage |= MTLAccelerationStructureUsage.Refit; + + commandBuffer.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); } public override nint GetNativeObject(NativeObjectType type) @@ -19,9 +55,20 @@ public override nint GetNativeObject(NativeObjectType type) protected override void SetResourceName(string name) { + AccelerationStructure.Label = name; } protected override void Destroy() { + Context.Unregister(AccelerationStructure); + + Scratch.Dispose(); + Instance.Dispose(); + AccelerationStructure.Dispose(); + } + + private MTL4InstanceAccelerationStructureDescriptor Descriptor(TopLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); } } From 109339f1589079db089e7b991a822415a9ea6b8d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 18:18:33 +0800 Subject: [PATCH 267/466] Refactor geometry descriptors and implement TLAS descriptor Refactor variable names from geometryDescriptors to geometries for clarity in MTLBottomLevelAccelerationStructure. Update assignment and Opaque property usage accordingly. Stub out Metal(RayTracingInstanceFlags) in MTLFormats. Implement Descriptor method in MTLTopLevelAccelerationStructure to map and populate instance descriptors and construct the instance acceleration structure descriptor. --- .../MTLBottomLevelAccelerationStructure.cs | 10 +++--- sources/Zenith.NET.Metal/MTLFormats.cs | 5 +++ .../MTLTopLevelAccelerationStructure.cs | 35 ++++++++++++++++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs index 2f5e25d6..971d685e 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -72,7 +72,7 @@ private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccel nint pointer = Transform.Map(); MTLPackedFloat4x3* transforms = (MTLPackedFloat4x3*)pointer; - MTL4AccelerationStructureGeometryDescriptor[] geometryDescriptors = new MTL4AccelerationStructureGeometryDescriptor[geometryCount]; + MTL4AccelerationStructureGeometryDescriptor[] geometries = new MTL4AccelerationStructureGeometryDescriptor[geometryCount]; for (uint i = 0; i < geometryCount; i++) { RayTracingGeometry geometry = desc.Geometries[i]; @@ -82,7 +82,7 @@ private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccel switch (geometry.Type) { case RayTracingGeometryType.Triangle: - geometryDescriptors[i] = new MTL4AccelerationStructureTriangleGeometryDescriptor() + geometries[i] = new MTL4AccelerationStructureTriangleGeometryDescriptor() { VertexBuffer = new() { @@ -108,7 +108,7 @@ private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccel break; case RayTracingGeometryType.Aabb: - geometryDescriptors[i] = new MTL4AccelerationStructureBoundingBoxGeometryDescriptor() + geometries[i] = new MTL4AccelerationStructureBoundingBoxGeometryDescriptor() { BoundingBoxBuffer = new() { @@ -121,14 +121,14 @@ private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccel break; } - geometryDescriptors[i].Opaque = geometry.IsOpaque; + geometries[i].Opaque = geometry.IsOpaque; } Transform.Unmap(); return new() { - GeometryDescriptors = geometryDescriptors, + GeometryDescriptors = geometries, Usage = MTLFormats.Metal(desc.BuildFlags) }; } diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 209e27cc..6fb04e47 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -473,4 +473,9 @@ public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlag { throw new NotImplementedException(); } + + internal static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags flags) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs index b0e0f414..2902876b 100644 --- a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs @@ -69,6 +69,39 @@ protected override void Destroy() private MTL4InstanceAccelerationStructureDescriptor Descriptor(TopLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + uint instanceCount = (uint)desc.Instances.Length; + + nint pointer = Instance.Map(); + + MTLIndirectAccelerationStructureInstanceDescriptor* instances = (MTLIndirectAccelerationStructureInstanceDescriptor*)pointer; + for (uint i = 0; i < instanceCount; i++) + { + RayTracingInstance instance = desc.Instances[i]; + + instances[i] = new() + { + TransformationMatrix = MTLFormats.Metal(instance.Transform), + Options = MTLFormats.Metal(instance.Flags), + Mask = instance.VisibilityMask, + UserID = instance.InstanceId, + AccelerationStructureID = instance.AccelerationStructure.Metal().AccelerationStructure.GpuResourceID + }; + } + + Instance.Unmap(); + + return new() + { + InstanceDescriptorBuffer = new() + { + BufferAddress = Instance.Buffer.GpuAddress, + Length = Instance.Desc.SizeInBytes + }, + InstanceDescriptorStride = (uint)sizeof(MTLIndirectAccelerationStructureInstanceDescriptor), + InstanceCount = instanceCount, + InstanceDescriptorType = MTLAccelerationStructureInstanceDescriptorType.Indirect, + InstanceTransformationMatrixLayout = MTLMatrixLayout.RowMajor, + Usage = MTLFormats.Metal(desc.BuildFlags) + }; } } From 3cfa377daeb290c792f9f11e31d969e56fe76096 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 18:20:59 +0800 Subject: [PATCH 268/466] Make Metal method public and clarify parameter name Changed Metal method for RayTracingInstanceFlags to public and renamed its parameter to rayTracingInstanceFlags for better clarity. --- sources/Zenith.NET.Metal/MTLFormats.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 6fb04e47..d605347d 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -474,7 +474,7 @@ public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlag throw new NotImplementedException(); } - internal static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags flags) + public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) { throw new NotImplementedException(); } From 309774ad33e81025ef6de03de61c00bbbee26b20 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 18:42:26 +0800 Subject: [PATCH 269/466] Add query and timestamp support to MTLCommandBuffer Implemented query and timestamp operations in MTLCommandBuffer, including deferred BeginQuery handling, timestamp writing and resolution, and proper state clearing on reset. Added helper structs for managing deferred operations and a stub for mapping query types to Metal visibility result modes. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 67 ++++++++++++++++++-- sources/Zenith.NET.Metal/MTLFormats.cs | 5 ++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 24f0ff77..b0a5bd69 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -15,6 +15,8 @@ internal unsafe class MTLCommandBuffer : CommandBuffer public MTL4ComputeCommandEncoder? Compute; + private readonly List resolveTimestamps = []; + private Scissor[]? todoScissors; private Viewport[]? todoViewports; private GraphicsPipeline? todoGraphicsPipeline; @@ -22,6 +24,7 @@ internal unsafe class MTLCommandBuffer : CommandBuffer private MeshShadingPipeline? todoMeshShadingPipeline; private uint? todoStencilReference; private Vector4? todoBlendConstant; + private BeginQueryArgs? todoBeginQueryArgs; private IndexBinding indexBinding; @@ -372,20 +375,29 @@ protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, B protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { - throw new NotImplementedException(); + if (Render is null) + { + todoBeginQueryArgs = new(queryHeap.Metal(), index); + } + else + { + Render.SetVisibilityResultMode(MTLFormats.Metal(queryHeap.Desc.Type), index); + } } protected override void EndQueryImpl(QueryHeap queryHeap, uint index) { - throw new NotImplementedException(); + Render?.SetVisibilityResultMode(MTLVisibilityResultMode.Disabled, index); } protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) { MTLQueryHeap mtlQueryHeap = queryHeap.Metal(); - CommandBuffer.WriteTimestamp(mtlQueryHeap.CounterHeap, index); - CommandBuffer.ResolveCounterHeap(mtlQueryHeap.CounterHeap, new(index, 1), new(mtlQueryHeap.Buffer.Buffer.GpuAddress + (sizeof(ulong) * index), sizeof(ulong)), MTLFence.Null, MTLFence.Null); + Render?.WriteTimestamp(MTL4TimestampGranularity.Precise, MTLRenderStages.Fragment, mtlQueryHeap.CounterHeap, index); + Compute?.WriteTimestamp(MTL4TimestampGranularity.Precise, mtlQueryHeap.CounterHeap, index); + + resolveTimestamps.Add(new(mtlQueryHeap, index)); } protected override void BeginDebugEventImpl(string label) @@ -423,6 +435,8 @@ protected override void EndImpl() protected override void ResetImpl() { + resolveTimestamps.Clear(); + todoScissors = null; todoViewports = null; todoGraphicsPipeline = null; @@ -430,6 +444,7 @@ protected override void ResetImpl() todoMeshShadingPipeline = null; todoStencilReference = null; todoBlendConstant = null; + todoBeginQueryArgs = null; CommandAllocator.Reset(); } @@ -450,6 +465,11 @@ protected override void Destroy() private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) { + if (todoBeginQueryArgs is not null) + { + descriptor.VisibilityResultBuffer = todoBeginQueryArgs.Value.QueryHeap.Buffer.Buffer; + } + Render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); Render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); @@ -494,10 +514,19 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) todoBlendConstant = null; } + + if (todoBeginQueryArgs is not null) + { + BeginQuery(todoBeginQueryArgs.Value.QueryHeap, todoBeginQueryArgs.Value.Index); + + todoBeginQueryArgs = null; + } } private void EndRenderEncoding() { + ResolveTimestamps(); + Render?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); Render?.EndEncoding(); Render?.Dispose(); @@ -519,12 +548,28 @@ private void BeginComputeEncoding() private void EndComputeEncoding() { + ResolveTimestamps(); + Compute?.BarrierAfterEncoderStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); Compute?.EndEncoding(); Compute?.Dispose(); Compute = null; } + private void ResolveTimestamps() + { + foreach (ResolveTimestamp resolveTimestamp in resolveTimestamps) + { + CommandBuffer.ResolveCounterHeap(resolveTimestamp.QueryHeap.CounterHeap, + new(resolveTimestamp.Index, 1), + new(resolveTimestamp.QueryHeap.Buffer.Buffer.GpuAddress + (sizeof(ulong) * resolveTimestamp.Index), sizeof(ulong)), + MTLFence.Null, + MTLFence.Null); + } + + resolveTimestamps.Clear(); + } + private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) { public MTLIndexType Type = type; @@ -535,4 +580,18 @@ private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, public uint LengthInBytes = lengthInBytes; } + + private struct BeginQueryArgs(MTLQueryHeap queryHeap, uint index) + { + public MTLQueryHeap QueryHeap = queryHeap; + + public uint Index = index; + } + + private struct ResolveTimestamp(MTLQueryHeap queryHeap, uint index) + { + public MTLQueryHeap QueryHeap = queryHeap; + + public uint Index = index; + } } \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index d605347d..1a95b437 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -478,4 +478,9 @@ public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFl { throw new NotImplementedException(); } + + public static MTLVisibilityResultMode Metal(QueryType queryType) + { + throw new NotImplementedException(); + } } From ec88dcbae051ec7994db320a1ebb12e932dda8dc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:10:55 +0800 Subject: [PATCH 270/466] Refactor visibility query handling in MTLCommandBuffer Replaces single BeginQueryArgs with new tracking structures for active visibility queries using a dictionary and lists. Adds a dedicated Visibility buffer for query results. Updates query methods and render encoding to support multiple queries. Introduces new structs for query management and ensures proper resource cleanup. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 88 ++++++++++++++------ 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index b0a5bd69..273a9315 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -15,8 +15,14 @@ internal unsafe class MTLCommandBuffer : CommandBuffer public MTL4ComputeCommandEncoder? Compute; + private readonly Dictionary activeVisibilityIndices = []; + private readonly List beginVisibilityBindings = []; + private readonly List endVisibilityBindings = []; private readonly List resolveTimestamps = []; + private uint visibilityIndex; + private IndexBinding indexBinding; + private Scissor[]? todoScissors; private Viewport[]? todoViewports; private GraphicsPipeline? todoGraphicsPipeline; @@ -24,9 +30,6 @@ internal unsafe class MTLCommandBuffer : CommandBuffer private MeshShadingPipeline? todoMeshShadingPipeline; private uint? todoStencilReference; private Vector4? todoBlendConstant; - private BeginQueryArgs? todoBeginQueryArgs; - - private IndexBinding indexBinding; public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { @@ -41,10 +44,18 @@ public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : bas ArgumentTable = context.Device.MakeArgumentTable(descriptor, out NSError error); error.Success(); + + Visibility = new(context, new() + { + SizeInBytes = sizeof(ulong) * 1024, + Residency = MemoryResidency.CpuReadOnly + }); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + public MTLBuffer Visibility { get; } + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -375,19 +386,32 @@ protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, B protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { + MTLQueryHeap mtlQueryHeap = queryHeap.Metal(); + + uint scratchIndex = visibilityIndex++; + + activeVisibilityIndices.Add(new(mtlQueryHeap, index), scratchIndex); + if (Render is null) { - todoBeginQueryArgs = new(queryHeap.Metal(), index); + beginVisibilityBindings.Add(new(mtlQueryHeap, index, scratchIndex)); } else { - Render.SetVisibilityResultMode(MTLFormats.Metal(queryHeap.Desc.Type), index); + Render.SetVisibilityResultMode(MTLFormats.Metal(mtlQueryHeap.Desc.Type), sizeof(ulong) * scratchIndex); } } protected override void EndQueryImpl(QueryHeap queryHeap, uint index) { - Render?.SetVisibilityResultMode(MTLVisibilityResultMode.Disabled, index); + MTLQueryHeap mtlQueryHeap = queryHeap.Metal(); + + if (activeVisibilityIndices.Remove(new(mtlQueryHeap, index), out uint scratchIndex)) + { + Render?.SetVisibilityResultMode(MTLVisibilityResultMode.Disabled, 0); + + endVisibilityBindings.Add(new(mtlQueryHeap, index, scratchIndex)); + } } protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) @@ -435,8 +459,14 @@ protected override void EndImpl() protected override void ResetImpl() { + activeVisibilityIndices.Clear(); + beginVisibilityBindings.Clear(); + endVisibilityBindings.Clear(); resolveTimestamps.Clear(); + visibilityIndex = 0; + indexBinding = default; + todoScissors = null; todoViewports = null; todoGraphicsPipeline = null; @@ -444,7 +474,6 @@ protected override void ResetImpl() todoMeshShadingPipeline = null; todoStencilReference = null; todoBlendConstant = null; - todoBeginQueryArgs = null; CommandAllocator.Reset(); } @@ -458,6 +487,7 @@ protected override void Destroy() { base.Destroy(); + Visibility.Dispose(); ArgumentTable.Dispose(); CommandBuffer.Dispose(); CommandAllocator.Dispose(); @@ -465,11 +495,6 @@ protected override void Destroy() private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) { - if (todoBeginQueryArgs is not null) - { - descriptor.VisibilityResultBuffer = todoBeginQueryArgs.Value.QueryHeap.Buffer.Buffer; - } - Render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); Render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); @@ -515,12 +540,11 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) todoBlendConstant = null; } - if (todoBeginQueryArgs is not null) + foreach (VisibilityBinding visibilityBinding in beginVisibilityBindings) { - BeginQuery(todoBeginQueryArgs.Value.QueryHeap, todoBeginQueryArgs.Value.Index); - - todoBeginQueryArgs = null; + Render.SetVisibilityResultMode(MTLFormats.Metal(visibilityBinding.QueryHeap.Desc.Type), sizeof(ulong) * visibilityBinding.ScratchIndex); } + beginVisibilityBindings.Clear(); } private void EndRenderEncoding() @@ -544,6 +568,12 @@ private void BeginComputeEncoding() todoComputePipeline = null; } + + foreach (VisibilityBinding visibilityBinding in endVisibilityBindings) + { + CopyBuffer(Visibility, sizeof(ulong) * visibilityBinding.ScratchIndex, visibilityBinding.QueryHeap.Buffer, sizeof(ulong) * visibilityBinding.Index, sizeof(ulong)); + } + endVisibilityBindings.Clear(); } private void EndComputeEncoding() @@ -566,26 +596,23 @@ private void ResolveTimestamps() MTLFence.Null, MTLFence.Null); } - resolveTimestamps.Clear(); } - private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) + private struct VisibilityKey(MTLQueryHeap queryHeap, uint index) { - public MTLIndexType Type = type; - - public nuint Address = address; - - public uint SizeInBytes = sizeInBytes; + public MTLQueryHeap QueryHeap = queryHeap; - public uint LengthInBytes = lengthInBytes; + public uint Index = index; } - private struct BeginQueryArgs(MTLQueryHeap queryHeap, uint index) + private struct VisibilityBinding(MTLQueryHeap queryHeap, uint index, uint scratchIndex) { public MTLQueryHeap QueryHeap = queryHeap; public uint Index = index; + + public uint ScratchIndex = scratchIndex; } private struct ResolveTimestamp(MTLQueryHeap queryHeap, uint index) @@ -594,4 +621,15 @@ private struct ResolveTimestamp(MTLQueryHeap queryHeap, uint index) public uint Index = index; } + + private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) + { + public MTLIndexType Type = type; + + public nuint Address = address; + + public uint SizeInBytes = sizeInBytes; + + public uint LengthInBytes = lengthInBytes; + } } \ No newline at end of file From 109fa335f0ecf8cdabb4011d001910b2851a96cf Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:11:47 +0800 Subject: [PATCH 271/466] Reorder IndexBinding struct before VisibilityKey Moved the IndexBinding struct definition to precede the VisibilityKey struct within the MTLCommandBuffer class for improved code organization. No functional changes were made. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 273a9315..d0290bba 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -599,6 +599,17 @@ private void ResolveTimestamps() resolveTimestamps.Clear(); } + private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) + { + public MTLIndexType Type = type; + + public nuint Address = address; + + public uint SizeInBytes = sizeInBytes; + + public uint LengthInBytes = lengthInBytes; + } + private struct VisibilityKey(MTLQueryHeap queryHeap, uint index) { public MTLQueryHeap QueryHeap = queryHeap; @@ -621,15 +632,4 @@ private struct ResolveTimestamp(MTLQueryHeap queryHeap, uint index) public uint Index = index; } - - private struct IndexBinding(MTLIndexType type, nuint address, uint sizeInBytes, uint lengthInBytes) - { - public MTLIndexType Type = type; - - public nuint Address = address; - - public uint SizeInBytes = sizeInBytes; - - public uint LengthInBytes = lengthInBytes; - } } \ No newline at end of file From 9b18e9055582ffc41fa4675f4cfd665eda3f58e6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:13:29 +0800 Subject: [PATCH 272/466] Implement SubmitImpl in MTLCommandQueue SubmitImpl now commits the Metal command buffer using CommandQueue.Commit, replacing the previous NotImplementedException. This enables proper submission of command buffers in the Metal backend. --- sources/Zenith.NET.Metal/MTLCommandQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index d74ca36d..9acd0ca0 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -40,7 +40,7 @@ protected override void SignalImpl(ulong signalValue) protected override void SubmitImpl(CommandBuffer commandBuffer) { - throw new NotImplementedException(); + CommandQueue.Commit([commandBuffer.Metal().CommandBuffer]); } protected override void WaitImpl(ulong waitValue) From d6852c0b541d4bfa887bac4af44da7f81afabee9 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:20:02 +0800 Subject: [PATCH 273/466] Update render pass to use proper attachments Refactored BeginRenderPassImpl to build MTL4RenderPassDescriptor with correct color, depth, and stencil attachments from input parameters, replacing the previous use of a null descriptor. This ensures accurate setup of render targets for each pass. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 40 +++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index d0290bba..0ca0c894 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -150,7 +150,45 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { EndComputeEncoding(); - BeginRenderEncoding(MTL4RenderPassDescriptor.Null); + + MTL4RenderPassDescriptor descriptor = new() { VisibilityResultBuffer = Visibility.Buffer }; + + for (int i = 0; i < colorAttachments.Length; i++) + { + ColorAttachment attachment = colorAttachments[i]; + + MTLTexture texture = attachment.Texture.Metal(); + + descriptor.ColorAttachments[(uint)i] = new() + { + Texture = texture.Texture + }; + } + + if (depthStencilAttachment.HasValue) + { + DepthStencilAttachment attachment = depthStencilAttachment.Value; + + MTLTexture texture = attachment.Texture.Metal(); + + if (ZenithHelper.HasDepth(texture.Desc.Format)) + { + descriptor.DepthAttachment = new() + { + Texture = texture.Texture + }; + } + + if (ZenithHelper.HasStencil(texture.Desc.Format)) + { + descriptor.StencilAttachment = new() + { + Texture = texture.Texture + }; + } + } + + BeginRenderEncoding(descriptor); } protected override void EndRenderPassImpl() From 0a1a7fbd5a0153b4354b0d3f7487255f88efef43 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:28:13 +0800 Subject: [PATCH 274/466] Map more API abstractions to Metal structures Expand MTLCommandBuffer and MTLFormats to map Level, Slice, LoadAction, StoreAction, and clear values for attachments. Implement enum conversions for LoadOp, StoreOp, AccelerationStructureBuildFlags, RayTracingInstanceFlags, and QueryType. Replace NotImplementedException with proper Metal structure construction for Matrix4x4 and flag mappings. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 21 ++++- sources/Zenith.NET.Metal/MTLFormats.cs | 81 +++++++++++++++++++- 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 0ca0c894..507e9877 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -161,7 +161,12 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA descriptor.ColorAttachments[(uint)i] = new() { - Texture = texture.Texture + Texture = texture.Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = MTLFormats.Metal(attachment.LoadOp), + StoreAction = MTLFormats.Metal(attachment.StoreOp), + ClearColor = new(attachment.ClearColor.X, attachment.ClearColor.Y, attachment.ClearColor.Z, attachment.ClearColor.W) }; } @@ -175,7 +180,12 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA { descriptor.DepthAttachment = new() { - Texture = texture.Texture + Texture = texture.Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = MTLFormats.Metal(attachment.DepthLoadOp), + StoreAction = MTLFormats.Metal(attachment.DepthStoreOp), + ClearDepth = attachment.ClearDepth }; } @@ -183,7 +193,12 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA { descriptor.StencilAttachment = new() { - Texture = texture.Texture + Texture = texture.Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = MTLFormats.Metal(attachment.StencilLoadOp), + StoreAction = MTLFormats.Metal(attachment.StencilStoreOp), + ClearStencil = attachment.ClearStencil }; } } diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 1a95b437..a712bd59 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -212,6 +212,27 @@ public static MTLSamplerBorderColor Metal(BorderColor borderColor) }; } + public static MTLLoadAction Metal(LoadOp loadOp) + { + return loadOp switch + { + LoadOp.Load => MTLLoadAction.Load, + LoadOp.Clear => MTLLoadAction.Clear, + LoadOp.DontCare => MTLLoadAction.DontCare, + _ => default + }; + } + + public static MTLStoreAction Metal(StoreOp storeOp) + { + return storeOp switch + { + StoreOp.Store => MTLStoreAction.Store, + StoreOp.DontCare => MTLStoreAction.DontCare, + _ => default + }; + } + public static MTLCompareFunction Metal(CompareOp compareOp) { return compareOp switch @@ -466,21 +487,73 @@ public static MTLIndexType Metal(IndexFormat indexFormat) public static MTLPackedFloat4x3 Metal(Matrix4x4 matrix4x4) { - throw new NotImplementedException(); + return new(new(matrix4x4.M11, matrix4x4.M12, matrix4x4.M13), + new(matrix4x4.M21, matrix4x4.M22, matrix4x4.M23), + new(matrix4x4.M31, matrix4x4.M32, matrix4x4.M33), + new(matrix4x4.M41, matrix4x4.M42, matrix4x4.M43)); } public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - throw new NotImplementedException(); + MTLAccelerationStructureUsage result = MTLAccelerationStructureUsage.None; + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + { + result |= MTLAccelerationStructureUsage.Refit; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + { + result |= MTLAccelerationStructureUsage.PreferFastIntersection; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + { + result |= MTLAccelerationStructureUsage.PreferFastBuild; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + { + result |= MTLAccelerationStructureUsage.MinimizeMemory; + } + + return result; } public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) { - throw new NotImplementedException(); + MTLAccelerationStructureInstanceOptions result = MTLAccelerationStructureInstanceOptions.None; + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) + { + result |= MTLAccelerationStructureInstanceOptions.TriangleFrontFacingWindingCounterClockwise; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) + { + result |= MTLAccelerationStructureInstanceOptions.DisableTriangleCulling; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + { + result |= MTLAccelerationStructureInstanceOptions.Opaque; + } + + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) + { + result |= MTLAccelerationStructureInstanceOptions.NonOpaque; + } + + return result; } public static MTLVisibilityResultMode Metal(QueryType queryType) { - throw new NotImplementedException(); + return queryType switch + { + QueryType.Occlusion => MTLVisibilityResultMode.Counting, + QueryType.BinaryOcclusion => MTLVisibilityResultMode.Boolean, + _ => default + }; } } From 340edcb063edadd84953242834ac210d611293ae Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:35:29 +0800 Subject: [PATCH 275/466] Update Metal support: set handle, target Metal 4.0 Set Handle in MTLTopLevelAccelerationStructure after build. Add "-Xmetal" and "-std=metal4.0" to Metal compiler args in ZenithCompiler for Metal 4.0 support. --- sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs | 2 ++ sources/Zenith.NET/ZenithCompiler.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs index 2902876b..a54e4cba 100644 --- a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs @@ -29,6 +29,8 @@ public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBu commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); + + Handle = AccelerationStructure.GpuResourceID.Impl.ToHandle(); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs index 1f0ff002..ff256527 100644 --- a/sources/Zenith.NET/ZenithCompiler.cs +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -49,7 +49,7 @@ private static string[] Arguments(GraphicsApi graphicsApi, string name, string[] break; case GraphicsApi.Metal: - arguments.AddRange(["metallib", "-capability", "metallib_latest"]); + arguments.AddRange(["metallib", "-capability", "metallib_latest", "-Xmetal", "-std=metal4.0"]); break; case GraphicsApi.Vulkan: From 7cc65b4aa0296389693fb8c7aaadd64c5380cb50 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 20:41:28 +0800 Subject: [PATCH 276/466] Add full PixelFormat to MTLAttributeFormat mappings Expanded the switch expression in MTLFormats to map all relevant PixelFormat enum values to their corresponding MTLAttributeFormat values, including support for normalized, unsigned, signed, and floating-point formats across 1-4 channel types and BGRA. Default handling remains unchanged. --- sources/Zenith.NET.Metal/MTLFormats.cs | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index a712bd59..9883a93e 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -138,6 +138,57 @@ public static (MTLPixelFormat PixelFormat, MTLAttributeFormat AttributeFormat) M }, pixelFormat switch { + PixelFormat.R8UNorm => MTLAttributeFormat.UCharNormalized, + PixelFormat.R8SNorm => MTLAttributeFormat.CharNormalized, + PixelFormat.R8UInt => MTLAttributeFormat.UChar, + PixelFormat.R8SInt => MTLAttributeFormat.Char, + + PixelFormat.R16UNorm => MTLAttributeFormat.UShortNormalized, + PixelFormat.R16SNorm => MTLAttributeFormat.ShortNormalized, + PixelFormat.R16UInt => MTLAttributeFormat.UShort, + PixelFormat.R16SInt => MTLAttributeFormat.Short, + PixelFormat.R16Float => MTLAttributeFormat.Half, + + PixelFormat.R32UInt => MTLAttributeFormat.UInt, + PixelFormat.R32SInt => MTLAttributeFormat.Int, + PixelFormat.R32Float => MTLAttributeFormat.Float, + + PixelFormat.R8G8UNorm => MTLAttributeFormat.UChar2Normalized, + PixelFormat.R8G8SNorm => MTLAttributeFormat.Char2Normalized, + PixelFormat.R8G8UInt => MTLAttributeFormat.UChar2, + PixelFormat.R8G8SInt => MTLAttributeFormat.Char2, + + PixelFormat.R16G16UNorm => MTLAttributeFormat.UShort2Normalized, + PixelFormat.R16G16SNorm => MTLAttributeFormat.Short2Normalized, + PixelFormat.R16G16UInt => MTLAttributeFormat.UShort2, + PixelFormat.R16G16SInt => MTLAttributeFormat.Short2, + PixelFormat.R16G16Float => MTLAttributeFormat.Half2, + + PixelFormat.R32G32UInt => MTLAttributeFormat.UInt2, + PixelFormat.R32G32SInt => MTLAttributeFormat.Int2, + PixelFormat.R32G32Float => MTLAttributeFormat.Float2, + + PixelFormat.R32G32B32UInt => MTLAttributeFormat.UInt3, + PixelFormat.R32G32B32SInt => MTLAttributeFormat.Int3, + PixelFormat.R32G32B32Float => MTLAttributeFormat.Float3, + + PixelFormat.R8G8B8A8UNorm => MTLAttributeFormat.UChar4Normalized, + PixelFormat.R8G8B8A8SNorm => MTLAttributeFormat.Char4Normalized, + PixelFormat.R8G8B8A8UInt => MTLAttributeFormat.UChar4, + PixelFormat.R8G8B8A8SInt => MTLAttributeFormat.Char4, + + PixelFormat.R16G16B16A16UNorm => MTLAttributeFormat.UShort4Normalized, + PixelFormat.R16G16B16A16SNorm => MTLAttributeFormat.Short4Normalized, + PixelFormat.R16G16B16A16UInt => MTLAttributeFormat.UShort4, + PixelFormat.R16G16B16A16SInt => MTLAttributeFormat.Short4, + PixelFormat.R16G16B16A16Float => MTLAttributeFormat.Half4, + + PixelFormat.R32G32B32A32UInt => MTLAttributeFormat.UInt4, + PixelFormat.R32G32B32A32SInt => MTLAttributeFormat.Int4, + PixelFormat.R32G32B32A32Float => MTLAttributeFormat.Float4, + + PixelFormat.B8G8R8A8UNorm => MTLAttributeFormat.UChar4Normalized_BGRA, + _ => default } ); From 5f4b2331f0d461c46df81fa8f36401f8dab74954 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 21 Jun 2026 23:45:20 +0800 Subject: [PATCH 277/466] Change matrix layout to ColumnMajor, add GPU barriers Switched transformation matrix layout from RowMajor to ColumnMajor for both bottom-level and top-level acceleration structures. Added BarrierAfterEncoderStages calls before build and refit operations in top-level acceleration structure methods to ensure correct GPU synchronization. --- .../Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs | 2 +- sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs index 971d685e..11bbd60d 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -103,7 +103,7 @@ private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccel BufferAddress = Transform.Buffer.GpuAddress + (uint)(sizeof(MTLPackedFloat4x3) * i), Length = (uint)sizeof(MTLPackedFloat4x3) }, - TransformationMatrixLayout = MTLMatrixLayout.RowMajor + TransformationMatrixLayout = MTLMatrixLayout.ColumnMajor }; break; diff --git a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs index a54e4cba..4754399d 100644 --- a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs @@ -27,6 +27,7 @@ public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBu Residency = MemoryResidency.GpuOnly }); + commandBuffer.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); @@ -46,6 +47,7 @@ public void Update(MTLCommandBuffer commandBuffer, TopLevelAccelerationStructure MTL4InstanceAccelerationStructureDescriptor descriptor = Descriptor(newDesc); descriptor.Usage |= MTLAccelerationStructureUsage.Refit; + commandBuffer.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); commandBuffer.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); } @@ -102,7 +104,7 @@ private MTL4InstanceAccelerationStructureDescriptor Descriptor(TopLevelAccelerat InstanceDescriptorStride = (uint)sizeof(MTLIndirectAccelerationStructureInstanceDescriptor), InstanceCount = instanceCount, InstanceDescriptorType = MTLAccelerationStructureInstanceDescriptorType.Indirect, - InstanceTransformationMatrixLayout = MTLMatrixLayout.RowMajor, + InstanceTransformationMatrixLayout = MTLMatrixLayout.ColumnMajor, Usage = MTLFormats.Metal(desc.BuildFlags) }; } From 3db729ebfe3f28a62ac3ee5e40ddd69fe4270e0c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 00:00:37 +0800 Subject: [PATCH 278/466] Remove unnecessary barrier calls after AS build/refit Removed commandBuffer.Compute?.BarrierAfterStages calls following acceleration structure Build and Refit operations, as they are no longer required. No other changes were made. --- sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs index 11bbd60d..942f85e1 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -28,7 +28,6 @@ public MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, MTLComman }); commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); - commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; @@ -43,7 +42,6 @@ public void Update(MTLCommandBuffer commandBuffer, BottomLevelAccelerationStruct descriptor.Usage |= MTLAccelerationStructureUsage.Refit; commandBuffer.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); - commandBuffer.Compute?.BarrierAfterStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); } public override nint GetNativeObject(NativeObjectType type) From e159a87f826fd5032916644ace63126c0bd7144b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 00:06:46 +0800 Subject: [PATCH 279/466] Remove global barrier after BLAS build Removed explicit global barrier synchronization following the BuildRaytracingAccelerationStructure call. This change eliminates the manual enforcement of access ordering for the acceleration structure, potentially relying on implicit or external synchronization. --- .../DXBottomLevelAccelerationStructure.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 7b581f42..34534b32 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -42,23 +42,6 @@ public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBu }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); - - GlobalBarrier barrier = new() - { - SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, - SyncAfter = BarrierSync.BuildRaytracingAccelerationStructure, - AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, - AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead - }; - - BarrierGroup barrierGroup = new() - { - Type = BarrierType.Global, - NumBarriers = 1, - PGlobalBarriers = &barrier - }; - - commandBuffer.CommandList.Barrier(1, &barrierGroup); } public DXBuffer Transform { get; } @@ -83,23 +66,6 @@ public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructu }; commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); - - GlobalBarrier barrier = new() - { - SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, - SyncAfter = BarrierSync.BuildRaytracingAccelerationStructure, - AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, - AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead - }; - - BarrierGroup barrierGroup = new() - { - Type = BarrierType.Global, - NumBarriers = 1, - PGlobalBarriers = &barrier - }; - - commandBuffer.CommandList.Barrier(1, &barrierGroup); } public override nint GetNativeObject(NativeObjectType type) From 89744874250c633bae61aab84c6368838e97ad5a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 00:11:06 +0800 Subject: [PATCH 280/466] Refactor barrier logic in DXTopLevelAccelerationStructure Extract barrier setup into BuildSyncBarrier method for reuse and improved readability. Centralize global barrier creation and submission, replacing duplicated inline code. --- .../DXTopLevelAccelerationStructure.cs | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 2eb8c7ad..3e359be4 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -33,6 +33,8 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe Residency = MemoryResidency.GpuOnly }); + BuildSyncBarrier(commandBuffer, BarrierSync.BuildRaytracingAccelerationStructure); + BuildRaytracingAccelerationStructureDesc buildDesc = new() { DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, @@ -42,22 +44,7 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); - GlobalBarrier barrier = new() - { - SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, - SyncAfter = BarrierSync.AllShading, - AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, - AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead - }; - - BarrierGroup barrierGroup = new() - { - Type = BarrierType.Global, - NumBarriers = 1, - PGlobalBarriers = &barrier - }; - - commandBuffer.CommandList.Barrier(1, &barrierGroup); + BuildSyncBarrier(commandBuffer, BarrierSync.AllShading); ShaderResourceViewDesc viewDesc = new() { @@ -92,22 +79,7 @@ public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureD commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); - GlobalBarrier barrier = new() - { - SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, - SyncAfter = BarrierSync.AllShading, - AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, - AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead - }; - - BarrierGroup barrierGroup = new() - { - Type = BarrierType.Global, - NumBarriers = 1, - PGlobalBarriers = &barrier - }; - - commandBuffer.CommandList.Barrier(1, &barrierGroup); + BuildSyncBarrier(commandBuffer, BarrierSync.AllShading); } public override nint GetNativeObject(NativeObjectType type) @@ -161,4 +133,24 @@ private BuildRaytracingAccelerationStructureInputs Inputs(TopLevelAccelerationSt InstanceDescs = Instance.GPUVirtualAddress }; } + + private static void BuildSyncBarrier(DXCommandBuffer commandBuffer, BarrierSync syncAfter) + { + GlobalBarrier barrier = new() + { + SyncBefore = BarrierSync.BuildRaytracingAccelerationStructure, + SyncAfter = syncAfter, + AccessBefore = BarrierAccess.RaytracingAccelerationStructureWrite, + AccessAfter = BarrierAccess.RaytracingAccelerationStructureRead + }; + + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; + + commandBuffer.CommandList.Barrier(1, &barrierGroup); + } } From 574855ce3a7ddb28655f5f7fbf048a12b3369b53 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 00:13:52 +0800 Subject: [PATCH 281/466] Remove post-build AllShading sync barrier in TLAS build Removed the BuildSyncBarrier with BarrierSync.AllShading after building the raytracing acceleration structure. Now, only the BuildRaytracingAccelerationStructure barrier is used before the build command, affecting both creation and update paths. This streamlines synchronization during TLAS construction. --- .../Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 3e359be4..b32c76b7 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -33,8 +33,6 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe Residency = MemoryResidency.GpuOnly }); - BuildSyncBarrier(commandBuffer, BarrierSync.BuildRaytracingAccelerationStructure); - BuildRaytracingAccelerationStructureDesc buildDesc = new() { DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, @@ -42,8 +40,8 @@ public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffe ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; + BuildSyncBarrier(commandBuffer, BarrierSync.BuildRaytracingAccelerationStructure); commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); - BuildSyncBarrier(commandBuffer, BarrierSync.AllShading); ShaderResourceViewDesc viewDesc = new() @@ -77,8 +75,8 @@ public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureD ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; + BuildSyncBarrier(commandBuffer, BarrierSync.BuildRaytracingAccelerationStructure); commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); - BuildSyncBarrier(commandBuffer, BarrierSync.AllShading); } From 8a888361f983f675a3317c6d6a8ba874719646a4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 16:22:01 +0800 Subject: [PATCH 282/466] Refactor and consolidate Metal mapping utilities Refactored MTLFormats.cs to reorganize and consolidate all Metal mapping methods. Rewrote enum and flag mappings using switch expressions and bitwise checks for improved clarity and maintainability. Reintroduced and improved several previously removed mapping methods. Updated PixelFormat to attribute format mapping and restored concise mappings for TextureType and SampleCount. The file now offers a more comprehensive and maintainable set of conversion utilities between Zenith.NET and Metal.NET types. --- sources/Zenith.NET.Metal/MTLFormats.cs | 760 ++++++++++++------------- 1 file changed, 380 insertions(+), 380 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 9883a93e..83887c1e 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -5,28 +5,300 @@ namespace Zenith.NET.Metal; internal static class MTLFormats { - public static MTLResourceOptions Metal(MemoryResidency memoryResidency) + public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - return memoryResidency switch + MTLAccelerationStructureUsage result = MTLAccelerationStructureUsage.None; + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - MemoryResidency.GpuOnly => MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, - MemoryResidency.CpuReadOnly => MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModeShared | MTLResourceOptions.HazardTrackingModeUntracked, - MemoryResidency.CpuWriteOnly => MTLResourceOptions.CPUCacheModeWriteCombined | MTLResourceOptions.StorageModeShared | MTLResourceOptions.HazardTrackingModeUntracked, + result |= MTLAccelerationStructureUsage.Refit; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + { + result |= MTLAccelerationStructureUsage.PreferFastIntersection; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + { + result |= MTLAccelerationStructureUsage.PreferFastBuild; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + { + result |= MTLAccelerationStructureUsage.MinimizeMemory; + } + + return result; + } + + public static MTLSamplerAddressMode Metal(AddressMode addressMode) + { + return addressMode switch + { + AddressMode.Wrap => MTLSamplerAddressMode.Repeat, + AddressMode.Mirror => MTLSamplerAddressMode.MirrorRepeat, + AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, + AddressMode.Border => MTLSamplerAddressMode.ClampToBorderColor, _ => default }; } - public static MTLTextureType Metal(TextureType textureType, SampleCount sampleCount) + public static MTLStages Metal(BarrierStages barrierStages) { - return textureType switch + MTLStages result = default; + + if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - TextureType.Texture1D => MTLTextureType.MTL1D, - TextureType.Texture2D => sampleCount is SampleCount.Count1 ? MTLTextureType.MTL2D : MTLTextureType.MTL2DMultisample, - TextureType.Texture3D => MTLTextureType.MTL3D, - TextureType.TextureCube => MTLTextureType.MTLCube, - TextureType.Texture1DArray => MTLTextureType.MTL1DArray, - TextureType.Texture2DArray => sampleCount is SampleCount.Count1 ? MTLTextureType.MTL2DArray : MTLTextureType.MTL2DMultisampleArray, - TextureType.TextureCubeArray => MTLTextureType.MTLCubeArray, + result |= MTLStages.Vertex; + } + + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) + { + result |= MTLStages.Fragment; + } + + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) + { + result |= MTLStages.Dispatch; + } + + if (barrierStages.HasFlag(BarrierStages.Copy) || barrierStages.HasFlag(BarrierStages.Resolve)) + { + result |= MTLStages.Blit; + } + + if (barrierStages.HasFlag(BarrierStages.All)) + { + result = MTLStages.All; + } + + return result; + } + + public static MTLBlendFactor Metal(BlendFactor blendFactor) + { + return blendFactor switch + { + BlendFactor.Zero => MTLBlendFactor.Zero, + BlendFactor.One => MTLBlendFactor.One, + BlendFactor.SrcColor => MTLBlendFactor.SourceColor, + BlendFactor.OneMinusSrcColor => MTLBlendFactor.OneMinusSourceColor, + BlendFactor.DstColor => MTLBlendFactor.DestinationColor, + BlendFactor.OneMinusDstColor => MTLBlendFactor.OneMinusDestinationColor, + BlendFactor.SrcAlpha => MTLBlendFactor.SourceAlpha, + BlendFactor.OneMinusSrcAlpha => MTLBlendFactor.OneMinusSourceAlpha, + BlendFactor.DstAlpha => MTLBlendFactor.DestinationAlpha, + BlendFactor.OneMinusDstAlpha => MTLBlendFactor.OneMinusDestinationAlpha, + BlendFactor.Constant => MTLBlendFactor.BlendColor, + BlendFactor.OneMinusConstant => MTLBlendFactor.OneMinusBlendColor, + _ => default + }; + } + + public static MTLBlendOperation Metal(BlendOp blendOp) + { + return blendOp switch + { + BlendOp.Add => MTLBlendOperation.Add, + BlendOp.Subtract => MTLBlendOperation.Subtract, + BlendOp.ReverseSubtract => MTLBlendOperation.ReverseSubtract, + BlendOp.Min => MTLBlendOperation.Min, + BlendOp.Max => MTLBlendOperation.Max, + _ => default + }; + } + + public static MTLSamplerBorderColor Metal(BorderColor borderColor) + { + return borderColor switch + { + BorderColor.TransparentBlack => MTLSamplerBorderColor.TransparentBlack, + BorderColor.OpaqueBlack => MTLSamplerBorderColor.OpaqueBlack, + BorderColor.OpaqueWhite => MTLSamplerBorderColor.OpaqueWhite, + _ => default + }; + } + + public static MTLColorWriteMask Metal(ColorWrites colorWrites) + { + MTLColorWriteMask result = MTLColorWriteMask.None; + + if (colorWrites.HasFlag(ColorWrites.Red)) + { + result |= MTLColorWriteMask.Red; + } + + if (colorWrites.HasFlag(ColorWrites.Green)) + { + result |= MTLColorWriteMask.Green; + } + + if (colorWrites.HasFlag(ColorWrites.Blue)) + { + result |= MTLColorWriteMask.Blue; + } + + if (colorWrites.HasFlag(ColorWrites.Alpha)) + { + result |= MTLColorWriteMask.Alpha; + } + + return result; + } + + public static MTLCompareFunction Metal(CompareOp compareOp) + { + return compareOp switch + { + CompareOp.Never => MTLCompareFunction.Never, + CompareOp.Less => MTLCompareFunction.Less, + CompareOp.Equal => MTLCompareFunction.Equal, + CompareOp.LessEqual => MTLCompareFunction.LessEqual, + CompareOp.Greater => MTLCompareFunction.Greater, + CompareOp.NotEqual => MTLCompareFunction.NotEqual, + CompareOp.GreaterEqual => MTLCompareFunction.GreaterEqual, + CompareOp.Always => MTLCompareFunction.Always, + _ => default + }; + } + + public static MTLCullMode Metal(CullMode cullMode) + { + return cullMode switch + { + CullMode.None => MTLCullMode.None, + CullMode.Front => MTLCullMode.Front, + CullMode.Back => MTLCullMode.Back, + _ => default + }; + } + + public static MTLVertexFormat Metal(ElementFormat elementFormat) + { + return elementFormat switch + { + ElementFormat.UByte1 => MTLVertexFormat.UChar, + ElementFormat.UByte2 => MTLVertexFormat.UChar2, + ElementFormat.UByte4 => MTLVertexFormat.UChar4, + + ElementFormat.Byte1 => MTLVertexFormat.Char, + ElementFormat.Byte2 => MTLVertexFormat.Char2, + ElementFormat.Byte4 => MTLVertexFormat.Char4, + + ElementFormat.UByte1UNorm => MTLVertexFormat.UCharNormalized, + ElementFormat.UByte2UNorm => MTLVertexFormat.UChar2Normalized, + ElementFormat.UByte4UNorm => MTLVertexFormat.UChar4Normalized, + + ElementFormat.Byte1SNorm => MTLVertexFormat.CharNormalized, + ElementFormat.Byte2SNorm => MTLVertexFormat.Char2Normalized, + ElementFormat.Byte4SNorm => MTLVertexFormat.Char4Normalized, + + ElementFormat.UShort1 => MTLVertexFormat.UShort, + ElementFormat.UShort2 => MTLVertexFormat.UShort2, + ElementFormat.UShort4 => MTLVertexFormat.UShort4, + + ElementFormat.Short1 => MTLVertexFormat.Short, + ElementFormat.Short2 => MTLVertexFormat.Short2, + ElementFormat.Short4 => MTLVertexFormat.Short4, + + ElementFormat.UShort1UNorm => MTLVertexFormat.UShortNormalized, + ElementFormat.UShort2UNorm => MTLVertexFormat.UShort2Normalized, + ElementFormat.UShort4UNorm => MTLVertexFormat.UShort4Normalized, + + ElementFormat.Short1SNorm => MTLVertexFormat.ShortNormalized, + ElementFormat.Short2SNorm => MTLVertexFormat.Short2Normalized, + ElementFormat.Short4SNorm => MTLVertexFormat.Short4Normalized, + + ElementFormat.Half1 => MTLVertexFormat.Half, + ElementFormat.Half2 => MTLVertexFormat.Half2, + ElementFormat.Half4 => MTLVertexFormat.Half4, + + ElementFormat.Float1 => MTLVertexFormat.Float, + ElementFormat.Float2 => MTLVertexFormat.Float2, + ElementFormat.Float3 => MTLVertexFormat.Float3, + ElementFormat.Float4 => MTLVertexFormat.Float4, + + ElementFormat.UInt1 => MTLVertexFormat.UInt, + ElementFormat.UInt2 => MTLVertexFormat.UInt2, + ElementFormat.UInt3 => MTLVertexFormat.UInt3, + ElementFormat.UInt4 => MTLVertexFormat.UInt4, + + ElementFormat.Int1 => MTLVertexFormat.Int, + ElementFormat.Int2 => MTLVertexFormat.Int2, + ElementFormat.Int3 => MTLVertexFormat.Int3, + ElementFormat.Int4 => MTLVertexFormat.Int4, + + _ => default + }; + } + + public static MTLTriangleFillMode Metal(FillMode fillMode) + { + return fillMode switch + { + FillMode.Solid => MTLTriangleFillMode.Fill, + FillMode.Wireframe => MTLTriangleFillMode.Lines, + _ => default + }; + } + + public static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode filterMode) + { + return filterMode switch + { + FilterMode.Point => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest), + FilterMode.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), + _ => default + }; + } + + public static MTLWinding Metal(FrontFace frontFace) + { + return frontFace switch + { + FrontFace.CounterClockwise => MTLWinding.CounterClockwise, + FrontFace.Clockwise => MTLWinding.Clockwise, + _ => default + }; + } + + public static MTLIndexType Metal(IndexFormat indexFormat) + { + return indexFormat switch + { + IndexFormat.UInt16 => MTLIndexType.UInt16, + IndexFormat.UInt32 => MTLIndexType.UInt32, + _ => default + }; + } + + public static MTLLoadAction Metal(LoadOp loadOp) + { + return loadOp switch + { + LoadOp.Load => MTLLoadAction.Load, + LoadOp.Clear => MTLLoadAction.Clear, + LoadOp.DontCare => MTLLoadAction.DontCare, + _ => default + }; + } + + public static MTLPackedFloat4x3 Metal(Matrix4x4 matrix4x4) + { + return new(new(matrix4x4.M11, matrix4x4.M12, matrix4x4.M13), + new(matrix4x4.M21, matrix4x4.M22, matrix4x4.M23), + new(matrix4x4.M31, matrix4x4.M32, matrix4x4.M33), + new(matrix4x4.M41, matrix4x4.M42, matrix4x4.M43)); + } + + public static MTLResourceOptions Metal(MemoryResidency memoryResidency) + { + return memoryResidency switch + { + MemoryResidency.GpuOnly => MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, + MemoryResidency.CpuReadOnly => MTLResourceOptions.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModeShared | MTLResourceOptions.HazardTrackingModeUntracked, + MemoryResidency.CpuWriteOnly => MTLResourceOptions.CPUCacheModeWriteCombined | MTLResourceOptions.StorageModeShared | MTLResourceOptions.HazardTrackingModeUntracked, _ => default }; } @@ -168,443 +440,171 @@ public static (MTLPixelFormat PixelFormat, MTLAttributeFormat AttributeFormat) M PixelFormat.R32G32SInt => MTLAttributeFormat.Int2, PixelFormat.R32G32Float => MTLAttributeFormat.Float2, - PixelFormat.R32G32B32UInt => MTLAttributeFormat.UInt3, - PixelFormat.R32G32B32SInt => MTLAttributeFormat.Int3, - PixelFormat.R32G32B32Float => MTLAttributeFormat.Float3, - - PixelFormat.R8G8B8A8UNorm => MTLAttributeFormat.UChar4Normalized, - PixelFormat.R8G8B8A8SNorm => MTLAttributeFormat.Char4Normalized, - PixelFormat.R8G8B8A8UInt => MTLAttributeFormat.UChar4, - PixelFormat.R8G8B8A8SInt => MTLAttributeFormat.Char4, - - PixelFormat.R16G16B16A16UNorm => MTLAttributeFormat.UShort4Normalized, - PixelFormat.R16G16B16A16SNorm => MTLAttributeFormat.Short4Normalized, - PixelFormat.R16G16B16A16UInt => MTLAttributeFormat.UShort4, - PixelFormat.R16G16B16A16SInt => MTLAttributeFormat.Short4, - PixelFormat.R16G16B16A16Float => MTLAttributeFormat.Half4, - - PixelFormat.R32G32B32A32UInt => MTLAttributeFormat.UInt4, - PixelFormat.R32G32B32A32SInt => MTLAttributeFormat.Int4, - PixelFormat.R32G32B32A32Float => MTLAttributeFormat.Float4, - - PixelFormat.B8G8R8A8UNorm => MTLAttributeFormat.UChar4Normalized_BGRA, - - _ => default - } - ); - } - - public static nuint Metal(SampleCount sampleCount) - { - return sampleCount switch - { - SampleCount.Count1 => 1, - SampleCount.Count2 => 2, - SampleCount.Count4 => 4, - SampleCount.Count8 => 8, - SampleCount.Count16 => 16, - SampleCount.Count32 => 32, - _ => default - }; - } - - public static MTLTextureUsage Metal(TextureUsages textureUsages) - { - MTLTextureUsage result = MTLTextureUsage.Unknown; - - if (textureUsages.HasFlag(TextureUsages.Sampled)) - { - result |= MTLTextureUsage.ShaderRead; - } - - if (textureUsages.HasFlag(TextureUsages.Storage)) - { - result |= MTLTextureUsage.ShaderWrite; - } - - if (textureUsages.HasFlag(TextureUsages.ColorAttachment) || textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) - { - result |= MTLTextureUsage.RenderTarget; - } - - return result; - } - - public static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode filterMode) - { - return filterMode switch - { - FilterMode.Point => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest), - FilterMode.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), - _ => default - }; - } - - public static MTLSamplerAddressMode Metal(AddressMode addressMode) - { - return addressMode switch - { - AddressMode.Wrap => MTLSamplerAddressMode.Repeat, - AddressMode.Mirror => MTLSamplerAddressMode.MirrorRepeat, - AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, - AddressMode.Border => MTLSamplerAddressMode.ClampToBorderColor, - _ => default - }; - } - - public static MTLSamplerBorderColor Metal(BorderColor borderColor) - { - return borderColor switch - { - BorderColor.TransparentBlack => MTLSamplerBorderColor.TransparentBlack, - BorderColor.OpaqueBlack => MTLSamplerBorderColor.OpaqueBlack, - BorderColor.OpaqueWhite => MTLSamplerBorderColor.OpaqueWhite, - _ => default - }; - } - - public static MTLLoadAction Metal(LoadOp loadOp) - { - return loadOp switch - { - LoadOp.Load => MTLLoadAction.Load, - LoadOp.Clear => MTLLoadAction.Clear, - LoadOp.DontCare => MTLLoadAction.DontCare, - _ => default - }; - } - - public static MTLStoreAction Metal(StoreOp storeOp) - { - return storeOp switch - { - StoreOp.Store => MTLStoreAction.Store, - StoreOp.DontCare => MTLStoreAction.DontCare, - _ => default - }; - } - - public static MTLCompareFunction Metal(CompareOp compareOp) - { - return compareOp switch - { - CompareOp.Never => MTLCompareFunction.Never, - CompareOp.Less => MTLCompareFunction.Less, - CompareOp.Equal => MTLCompareFunction.Equal, - CompareOp.LessEqual => MTLCompareFunction.LessEqual, - CompareOp.Greater => MTLCompareFunction.Greater, - CompareOp.NotEqual => MTLCompareFunction.NotEqual, - CompareOp.GreaterEqual => MTLCompareFunction.GreaterEqual, - CompareOp.Always => MTLCompareFunction.Always, - _ => default - }; - } - - public static (MTLPrimitiveTopologyClass TopologyClass, MTLPrimitiveType Type) Metal(PrimitiveTopology primitiveTopology) - { - return - ( - primitiveTopology switch - { - PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, - - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, - - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, - - _ => default - }, - primitiveTopology switch - { - PrimitiveTopology.PointList => MTLPrimitiveType.Point, - PrimitiveTopology.LineList => MTLPrimitiveType.Line, - PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip, - PrimitiveTopology.TriangleList => MTLPrimitiveType.Triangle, - PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip, - _ => default - } - ); - } - - public static MTLVertexFormat Metal(ElementFormat elementFormat) - { - return elementFormat switch - { - ElementFormat.UByte1 => MTLVertexFormat.UChar, - ElementFormat.UByte2 => MTLVertexFormat.UChar2, - ElementFormat.UByte4 => MTLVertexFormat.UChar4, - - ElementFormat.Byte1 => MTLVertexFormat.Char, - ElementFormat.Byte2 => MTLVertexFormat.Char2, - ElementFormat.Byte4 => MTLVertexFormat.Char4, - - ElementFormat.UByte1UNorm => MTLVertexFormat.UCharNormalized, - ElementFormat.UByte2UNorm => MTLVertexFormat.UChar2Normalized, - ElementFormat.UByte4UNorm => MTLVertexFormat.UChar4Normalized, - - ElementFormat.Byte1SNorm => MTLVertexFormat.CharNormalized, - ElementFormat.Byte2SNorm => MTLVertexFormat.Char2Normalized, - ElementFormat.Byte4SNorm => MTLVertexFormat.Char4Normalized, - - ElementFormat.UShort1 => MTLVertexFormat.UShort, - ElementFormat.UShort2 => MTLVertexFormat.UShort2, - ElementFormat.UShort4 => MTLVertexFormat.UShort4, - - ElementFormat.Short1 => MTLVertexFormat.Short, - ElementFormat.Short2 => MTLVertexFormat.Short2, - ElementFormat.Short4 => MTLVertexFormat.Short4, - - ElementFormat.UShort1UNorm => MTLVertexFormat.UShortNormalized, - ElementFormat.UShort2UNorm => MTLVertexFormat.UShort2Normalized, - ElementFormat.UShort4UNorm => MTLVertexFormat.UShort4Normalized, - - ElementFormat.Short1SNorm => MTLVertexFormat.ShortNormalized, - ElementFormat.Short2SNorm => MTLVertexFormat.Short2Normalized, - ElementFormat.Short4SNorm => MTLVertexFormat.Short4Normalized, - - ElementFormat.Half1 => MTLVertexFormat.Half, - ElementFormat.Half2 => MTLVertexFormat.Half2, - ElementFormat.Half4 => MTLVertexFormat.Half4, - - ElementFormat.Float1 => MTLVertexFormat.Float, - ElementFormat.Float2 => MTLVertexFormat.Float2, - ElementFormat.Float3 => MTLVertexFormat.Float3, - ElementFormat.Float4 => MTLVertexFormat.Float4, + PixelFormat.R32G32B32UInt => MTLAttributeFormat.UInt3, + PixelFormat.R32G32B32SInt => MTLAttributeFormat.Int3, + PixelFormat.R32G32B32Float => MTLAttributeFormat.Float3, - ElementFormat.UInt1 => MTLVertexFormat.UInt, - ElementFormat.UInt2 => MTLVertexFormat.UInt2, - ElementFormat.UInt3 => MTLVertexFormat.UInt3, - ElementFormat.UInt4 => MTLVertexFormat.UInt4, + PixelFormat.R8G8B8A8UNorm => MTLAttributeFormat.UChar4Normalized, + PixelFormat.R8G8B8A8SNorm => MTLAttributeFormat.Char4Normalized, + PixelFormat.R8G8B8A8UInt => MTLAttributeFormat.UChar4, + PixelFormat.R8G8B8A8SInt => MTLAttributeFormat.Char4, - ElementFormat.Int1 => MTLVertexFormat.Int, - ElementFormat.Int2 => MTLVertexFormat.Int2, - ElementFormat.Int3 => MTLVertexFormat.Int3, - ElementFormat.Int4 => MTLVertexFormat.Int4, + PixelFormat.R16G16B16A16UNorm => MTLAttributeFormat.UShort4Normalized, + PixelFormat.R16G16B16A16SNorm => MTLAttributeFormat.Short4Normalized, + PixelFormat.R16G16B16A16UInt => MTLAttributeFormat.UShort4, + PixelFormat.R16G16B16A16SInt => MTLAttributeFormat.Short4, + PixelFormat.R16G16B16A16Float => MTLAttributeFormat.Half4, - _ => default - }; - } + PixelFormat.R32G32B32A32UInt => MTLAttributeFormat.UInt4, + PixelFormat.R32G32B32A32SInt => MTLAttributeFormat.Int4, + PixelFormat.R32G32B32A32Float => MTLAttributeFormat.Float4, - public static MTLBlendFactor Metal(BlendFactor blendFactor) - { - return blendFactor switch - { - BlendFactor.Zero => MTLBlendFactor.Zero, - BlendFactor.One => MTLBlendFactor.One, - BlendFactor.SrcColor => MTLBlendFactor.SourceColor, - BlendFactor.OneMinusSrcColor => MTLBlendFactor.OneMinusSourceColor, - BlendFactor.DstColor => MTLBlendFactor.DestinationColor, - BlendFactor.OneMinusDstColor => MTLBlendFactor.OneMinusDestinationColor, - BlendFactor.SrcAlpha => MTLBlendFactor.SourceAlpha, - BlendFactor.OneMinusSrcAlpha => MTLBlendFactor.OneMinusSourceAlpha, - BlendFactor.DstAlpha => MTLBlendFactor.DestinationAlpha, - BlendFactor.OneMinusDstAlpha => MTLBlendFactor.OneMinusDestinationAlpha, - BlendFactor.Constant => MTLBlendFactor.BlendColor, - BlendFactor.OneMinusConstant => MTLBlendFactor.OneMinusBlendColor, - _ => default - }; - } + PixelFormat.B8G8R8A8UNorm => MTLAttributeFormat.UChar4Normalized_BGRA, - public static MTLBlendOperation Metal(BlendOp blendOp) - { - return blendOp switch - { - BlendOp.Add => MTLBlendOperation.Add, - BlendOp.Subtract => MTLBlendOperation.Subtract, - BlendOp.ReverseSubtract => MTLBlendOperation.ReverseSubtract, - BlendOp.Min => MTLBlendOperation.Min, - BlendOp.Max => MTLBlendOperation.Max, - _ => default - }; + _ => default + } + ); } - public static MTLColorWriteMask Metal(ColorWrites colorWrites) + public static (MTLPrimitiveTopologyClass TopologyClass, MTLPrimitiveType Type) Metal(PrimitiveTopology primitiveTopology) { - MTLColorWriteMask result = MTLColorWriteMask.None; - - if (colorWrites.HasFlag(ColorWrites.Red)) - { - result |= MTLColorWriteMask.Red; - } - - if (colorWrites.HasFlag(ColorWrites.Green)) - { - result |= MTLColorWriteMask.Green; - } + return + ( + primitiveTopology switch + { + PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, - if (colorWrites.HasFlag(ColorWrites.Blue)) - { - result |= MTLColorWriteMask.Blue; - } + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, - if (colorWrites.HasFlag(ColorWrites.Alpha)) - { - result |= MTLColorWriteMask.Alpha; - } + PrimitiveTopology.TriangleList or + PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, - return result; + _ => default + }, + primitiveTopology switch + { + PrimitiveTopology.PointList => MTLPrimitiveType.Point, + PrimitiveTopology.LineList => MTLPrimitiveType.Line, + PrimitiveTopology.LineStrip => MTLPrimitiveType.LineStrip, + PrimitiveTopology.TriangleList => MTLPrimitiveType.Triangle, + PrimitiveTopology.TriangleStrip => MTLPrimitiveType.TriangleStrip, + _ => default + } + ); } - public static MTLStencilOperation Metal(StencilOp stencilOp) + public static MTLVisibilityResultMode Metal(QueryType queryType) { - return stencilOp switch + return queryType switch { - StencilOp.Keep => MTLStencilOperation.Keep, - StencilOp.Zero => MTLStencilOperation.Zero, - StencilOp.Replace => MTLStencilOperation.Replace, - StencilOp.IncrementAndClamp => MTLStencilOperation.IncrementClamp, - StencilOp.DecrementAndClamp => MTLStencilOperation.DecrementClamp, - StencilOp.Invert => MTLStencilOperation.Invert, - StencilOp.IncrementAndWrap => MTLStencilOperation.IncrementWrap, - StencilOp.DecrementAndWrap => MTLStencilOperation.DecrementWrap, + QueryType.Occlusion => MTLVisibilityResultMode.Counting, + QueryType.BinaryOcclusion => MTLVisibilityResultMode.Boolean, _ => default }; } - public static MTLStages Metal(BarrierStages barrierStages) + public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) { - MTLStages result = default; - - if (barrierStages.HasFlag(BarrierStages.VertexShading)) - { - result |= MTLStages.Vertex; - } + MTLAccelerationStructureInstanceOptions result = MTLAccelerationStructureInstanceOptions.None; - if (barrierStages.HasFlag(BarrierStages.FragmentShading)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { - result |= MTLStages.Fragment; + result |= MTLAccelerationStructureInstanceOptions.TriangleFrontFacingWindingCounterClockwise; } - if (barrierStages.HasFlag(BarrierStages.ComputeShading)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) { - result |= MTLStages.Dispatch; + result |= MTLAccelerationStructureInstanceOptions.DisableTriangleCulling; } - if (barrierStages.HasFlag(BarrierStages.Copy) || barrierStages.HasFlag(BarrierStages.Resolve)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) { - result |= MTLStages.Blit; + result |= MTLAccelerationStructureInstanceOptions.Opaque; } - if (barrierStages.HasFlag(BarrierStages.All)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) { - result = MTLStages.All; + result |= MTLAccelerationStructureInstanceOptions.NonOpaque; } return result; } - public static MTLCullMode Metal(CullMode cullMode) + public static nuint Metal(SampleCount sampleCount) { - return cullMode switch + return sampleCount switch { - CullMode.None => MTLCullMode.None, - CullMode.Front => MTLCullMode.Front, - CullMode.Back => MTLCullMode.Back, + SampleCount.Count1 => 1, + SampleCount.Count2 => 2, + SampleCount.Count4 => 4, + SampleCount.Count8 => 8, + SampleCount.Count16 => 16, + SampleCount.Count32 => 32, _ => default }; } - public static MTLWinding Metal(FrontFace frontFace) + public static MTLStencilOperation Metal(StencilOp stencilOp) { - return frontFace switch + return stencilOp switch { - FrontFace.CounterClockwise => MTLWinding.CounterClockwise, - FrontFace.Clockwise => MTLWinding.Clockwise, + StencilOp.Keep => MTLStencilOperation.Keep, + StencilOp.Zero => MTLStencilOperation.Zero, + StencilOp.Replace => MTLStencilOperation.Replace, + StencilOp.IncrementAndClamp => MTLStencilOperation.IncrementClamp, + StencilOp.DecrementAndClamp => MTLStencilOperation.DecrementClamp, + StencilOp.Invert => MTLStencilOperation.Invert, + StencilOp.IncrementAndWrap => MTLStencilOperation.IncrementWrap, + StencilOp.DecrementAndWrap => MTLStencilOperation.DecrementWrap, _ => default }; } - public static MTLTriangleFillMode Metal(FillMode fillMode) + public static MTLStoreAction Metal(StoreOp storeOp) { - return fillMode switch + return storeOp switch { - FillMode.Solid => MTLTriangleFillMode.Fill, - FillMode.Wireframe => MTLTriangleFillMode.Lines, + StoreOp.Store => MTLStoreAction.Store, + StoreOp.DontCare => MTLStoreAction.DontCare, _ => default }; } - public static MTLIndexType Metal(IndexFormat indexFormat) + public static MTLTextureType Metal(TextureType textureType, SampleCount sampleCount) { - return indexFormat switch + return textureType switch { - IndexFormat.UInt16 => MTLIndexType.UInt16, - IndexFormat.UInt32 => MTLIndexType.UInt32, + TextureType.Texture1D => MTLTextureType.MTL1D, + TextureType.Texture2D => sampleCount is SampleCount.Count1 ? MTLTextureType.MTL2D : MTLTextureType.MTL2DMultisample, + TextureType.Texture3D => MTLTextureType.MTL3D, + TextureType.TextureCube => MTLTextureType.MTLCube, + TextureType.Texture1DArray => MTLTextureType.MTL1DArray, + TextureType.Texture2DArray => sampleCount is SampleCount.Count1 ? MTLTextureType.MTL2DArray : MTLTextureType.MTL2DMultisampleArray, + TextureType.TextureCubeArray => MTLTextureType.MTLCubeArray, _ => default }; } - public static MTLPackedFloat4x3 Metal(Matrix4x4 matrix4x4) - { - return new(new(matrix4x4.M11, matrix4x4.M12, matrix4x4.M13), - new(matrix4x4.M21, matrix4x4.M22, matrix4x4.M23), - new(matrix4x4.M31, matrix4x4.M32, matrix4x4.M33), - new(matrix4x4.M41, matrix4x4.M42, matrix4x4.M43)); - } - - public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) - { - MTLAccelerationStructureUsage result = MTLAccelerationStructureUsage.None; - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) - { - result |= MTLAccelerationStructureUsage.Refit; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) - { - result |= MTLAccelerationStructureUsage.PreferFastIntersection; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) - { - result |= MTLAccelerationStructureUsage.PreferFastBuild; - } - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) - { - result |= MTLAccelerationStructureUsage.MinimizeMemory; - } - - return result; - } - - public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) + public static MTLTextureUsage Metal(TextureUsages textureUsages) { - MTLAccelerationStructureInstanceOptions result = MTLAccelerationStructureInstanceOptions.None; - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) - { - result |= MTLAccelerationStructureInstanceOptions.TriangleFrontFacingWindingCounterClockwise; - } + MTLTextureUsage result = MTLTextureUsage.Unknown; - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) + if (textureUsages.HasFlag(TextureUsages.Sampled)) { - result |= MTLAccelerationStructureInstanceOptions.DisableTriangleCulling; + result |= MTLTextureUsage.ShaderRead; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + if (textureUsages.HasFlag(TextureUsages.Storage)) { - result |= MTLAccelerationStructureInstanceOptions.Opaque; + result |= MTLTextureUsage.ShaderWrite; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) + if (textureUsages.HasFlag(TextureUsages.ColorAttachment) || textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { - result |= MTLAccelerationStructureInstanceOptions.NonOpaque; + result |= MTLTextureUsage.RenderTarget; } return result; } - - public static MTLVisibilityResultMode Metal(QueryType queryType) - { - return queryType switch - { - QueryType.Occlusion => MTLVisibilityResultMode.Counting, - QueryType.BinaryOcclusion => MTLVisibilityResultMode.Boolean, - _ => default - }; - } } From 7773df31245763dffac6dbd287df0c966f02d03f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 17:04:25 +0800 Subject: [PATCH 283/466] Refactor enum initialization and expand barrier mappings Replaced explicit enum initializations with `default` in DXFormats and MTLFormats for consistency. Expanded DirectX12 barrier stage mappings to include additional sync and access flags. Updated Metal vertex shading mapping to cover more stages. These changes improve accuracy and standardization in API flag translation. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 33 +++++++++++++---------- sources/Zenith.NET.Metal/MTLFormats.cs | 10 +++---- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index c5f3b0a0..5665dc6f 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -10,7 +10,7 @@ internal static class DXFormats { public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - RaytracingAccelerationStructureBuildFlags result = RaytracingAccelerationStructureBuildFlags.None; + RaytracingAccelerationStructureBuildFlags result = default; if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { @@ -54,37 +54,42 @@ public static TextureAddressMode DirectX12(AddressMode addressMode) public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages barrierStages) { - BarrierSync sync = BarrierSync.None; - BarrierAccess access = BarrierAccess.Common; + if (barrierStages is BarrierStages.None) + { + return (BarrierSync.None, BarrierAccess.NoAccess); + } + + BarrierSync sync = default; + BarrierAccess access = default; if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - sync |= BarrierSync.VertexShading; - access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource; + sync |= BarrierSync.IndexInput | BarrierSync.VertexShading | BarrierSync.ExecuteIndirect; + access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.IndexBuffer | BarrierAccess.UnorderedAccess | BarrierAccess.ShaderResource | BarrierAccess.IndirectArgument | BarrierAccess.RaytracingAccelerationStructureRead; } if (barrierStages.HasFlag(BarrierStages.FragmentShading)) { sync |= BarrierSync.PixelShading | BarrierSync.DepthStencil | BarrierSync.RenderTarget; - access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.DepthStencilRead | BarrierAccess.DepthStencilWrite | BarrierAccess.RenderTarget; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.RenderTarget | BarrierAccess.UnorderedAccess | BarrierAccess.DepthStencilWrite | BarrierAccess.DepthStencilRead | BarrierAccess.ShaderResource | BarrierAccess.RaytracingAccelerationStructureRead; } if (barrierStages.HasFlag(BarrierStages.ComputeShading)) { - sync |= BarrierSync.ComputeShading; - access |= BarrierAccess.ConstantBuffer | BarrierAccess.ShaderResource | BarrierAccess.UnorderedAccess; + sync |= BarrierSync.ComputeShading | BarrierSync.ExecuteIndirect; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.UnorderedAccess | BarrierAccess.ShaderResource | BarrierAccess.IndirectArgument | BarrierAccess.RaytracingAccelerationStructureRead; } if (barrierStages.HasFlag(BarrierStages.Copy)) { sync |= BarrierSync.Copy; - access |= BarrierAccess.CopySource | BarrierAccess.CopyDest; + access |= BarrierAccess.CopyDest | BarrierAccess.CopySource; } if (barrierStages.HasFlag(BarrierStages.Resolve)) { sync |= BarrierSync.Resolve; - access |= BarrierAccess.ResolveSource | BarrierAccess.ResolveDest; + access |= BarrierAccess.ResolveDest | BarrierAccess.ResolveSource; } if (barrierStages.HasFlag(BarrierStages.All)) @@ -142,7 +147,7 @@ public static (float R, float G, float B, float A) DirectX12(BorderColor borderC public static ResourceFlags DirectX12(BufferUsages bufferUsages) { - ResourceFlags result = ResourceFlags.None; + ResourceFlags result = default; if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) { @@ -154,7 +159,7 @@ public static ResourceFlags DirectX12(BufferUsages bufferUsages) public static ColorWriteEnable DirectX12(ColorWrites colorWrites) { - ColorWriteEnable result = ColorWriteEnable.None; + ColorWriteEnable result = default; if (colorWrites.HasFlag(ColorWrites.Red)) { @@ -506,7 +511,7 @@ public static RaytracingGeometryType DirectX12(RayTracingGeometryType rayTracing public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) { - RaytracingInstanceFlags result = RaytracingInstanceFlags.None; + RaytracingInstanceFlags result = default; if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { @@ -611,7 +616,7 @@ TextureType.TextureCube or public static ResourceFlags DirectX12(TextureUsages textureUsages) { - ResourceFlags result = ResourceFlags.None; + ResourceFlags result = default; if (!textureUsages.HasFlag(TextureUsages.Sampled)) { diff --git a/sources/Zenith.NET.Metal/MTLFormats.cs b/sources/Zenith.NET.Metal/MTLFormats.cs index 83887c1e..359b1283 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -7,7 +7,7 @@ internal static class MTLFormats { public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - MTLAccelerationStructureUsage result = MTLAccelerationStructureUsage.None; + MTLAccelerationStructureUsage result = default; if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { @@ -50,7 +50,7 @@ public static MTLStages Metal(BarrierStages barrierStages) if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - result |= MTLStages.Vertex; + result |= MTLStages.Vertex | MTLStages.Object | MTLStages.Mesh; } if (barrierStages.HasFlag(BarrierStages.FragmentShading)) @@ -122,7 +122,7 @@ public static MTLSamplerBorderColor Metal(BorderColor borderColor) public static MTLColorWriteMask Metal(ColorWrites colorWrites) { - MTLColorWriteMask result = MTLColorWriteMask.None; + MTLColorWriteMask result = default; if (colorWrites.HasFlag(ColorWrites.Red)) { @@ -506,7 +506,7 @@ public static MTLVisibilityResultMode Metal(QueryType queryType) public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) { - MTLAccelerationStructureInstanceOptions result = MTLAccelerationStructureInstanceOptions.None; + MTLAccelerationStructureInstanceOptions result = default; if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { @@ -588,7 +588,7 @@ public static MTLTextureType Metal(TextureType textureType, SampleCount sampleCo public static MTLTextureUsage Metal(TextureUsages textureUsages) { - MTLTextureUsage result = MTLTextureUsage.Unknown; + MTLTextureUsage result = default; if (textureUsages.HasFlag(TextureUsages.Sampled)) { From 50e818a8bf15721de7e4ea4ffa0a39c200200a77 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 22 Jun 2026 20:40:50 +0800 Subject: [PATCH 284/466] Refactor: move ZenithViewHandler to its own file Separated ZenithViewHandler from ZenithView.cs into ZenithViewHandler.cs, including all platform-specific logic and handler methods. This improves code organization by keeping the view definition and handler implementation in distinct files. --- .../Views/Zenith.NET.Views.Maui/ZenithView.cs | 59 +------------------ .../ZenithViewHandler.cs | 58 ++++++++++++++++++ 2 files changed, 59 insertions(+), 58 deletions(-) create mode 100644 sources/Views/Zenith.NET.Views.Maui/ZenithViewHandler.cs diff --git a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs index 4a86d77c..2027653e 100644 --- a/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Maui/ZenithView.cs @@ -1,61 +1,4 @@ -using Microsoft.Maui.Handlers; -#if ANDROID -using Zenith.NET.Views.Maui.Platforms.Android; -#elif IOS -using Zenith.NET.Views.Maui.Platforms.iOS; -#elif MACCATALYST -using Zenith.NET.Views.Maui.Platforms.MacCatalyst; -#elif WINDOWS -using Zenith.NET.Views.Maui.Platforms.Windows; -#endif - -namespace Zenith.NET.Views.Maui; - -internal class ZenithViewHandler() : ViewHandler(mapper, commandMapper) -{ - private static readonly PropertyMapper mapper = new(ViewMapper) - { - [nameof(ZenithView.Background)] = MapBackground - }; - - private static readonly CommandMapper commandMapper = new(ViewCommandMapper) - { - [nameof(IZenithView.EnsureResources)] = MapEnsureResources, - [nameof(IZenithView.Tick)] = MapTick, - [nameof(IZenithView.Present)] = MapPresent, - [nameof(IZenithView.ReleaseResources)] = MapReleaseResources - }; - - protected override MauiZenithView CreatePlatformView() - { - return new(this); - } - - private static void MapBackground(ZenithViewHandler handler, ZenithView view) - { - // ZenithView does not support Background property. - } - - private static void MapEnsureResources(ZenithViewHandler handler, ZenithView view, object? arg3) - { - handler.PlatformView.EnsureResources(); - } - - private static void MapTick(ZenithViewHandler handler, ZenithView view, object? arg3) - { - handler.PlatformView.Tick(); - } - - private static void MapPresent(ZenithViewHandler handler, ZenithView view, object? arg3) - { - handler.PlatformView.Present(); - } - - private static void MapReleaseResources(ZenithViewHandler handler, ZenithView view, object? arg3) - { - handler.PlatformView.ReleaseResources(); - } -} +namespace Zenith.NET.Views.Maui; public partial class ZenithView : View, IZenithView { diff --git a/sources/Views/Zenith.NET.Views.Maui/ZenithViewHandler.cs b/sources/Views/Zenith.NET.Views.Maui/ZenithViewHandler.cs new file mode 100644 index 00000000..2aafa944 --- /dev/null +++ b/sources/Views/Zenith.NET.Views.Maui/ZenithViewHandler.cs @@ -0,0 +1,58 @@ +using Microsoft.Maui.Handlers; +#if ANDROID +using Zenith.NET.Views.Maui.Platforms.Android; +#elif IOS +using Zenith.NET.Views.Maui.Platforms.iOS; +#elif MACCATALYST +using Zenith.NET.Views.Maui.Platforms.MacCatalyst; +#elif WINDOWS +using Zenith.NET.Views.Maui.Platforms.Windows; +#endif + +namespace Zenith.NET.Views.Maui; + +internal class ZenithViewHandler() : ViewHandler(mapper, commandMapper) +{ + private static readonly PropertyMapper mapper = new(ViewMapper) + { + [nameof(ZenithView.Background)] = MapBackground + }; + + private static readonly CommandMapper commandMapper = new(ViewCommandMapper) + { + [nameof(IZenithView.EnsureResources)] = MapEnsureResources, + [nameof(IZenithView.Tick)] = MapTick, + [nameof(IZenithView.Present)] = MapPresent, + [nameof(IZenithView.ReleaseResources)] = MapReleaseResources + }; + + protected override MauiZenithView CreatePlatformView() + { + return new(this); + } + + private static void MapBackground(ZenithViewHandler handler, ZenithView view) + { + // ZenithView does not support Background property. + } + + private static void MapEnsureResources(ZenithViewHandler handler, ZenithView view, object? arg3) + { + handler.PlatformView.EnsureResources(); + } + + private static void MapTick(ZenithViewHandler handler, ZenithView view, object? arg3) + { + handler.PlatformView.Tick(); + } + + private static void MapPresent(ZenithViewHandler handler, ZenithView view, object? arg3) + { + handler.PlatformView.Present(); + } + + private static void MapReleaseResources(ZenithViewHandler handler, ZenithView view, object? arg3) + { + handler.PlatformView.ReleaseResources(); + } +} \ No newline at end of file From 0f0a8a6e291a13bdaabc765ffa946309567ad7c2 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 23 Jun 2026 19:27:04 +0800 Subject: [PATCH 285/466] Add VK_EXT_descriptor_heap extension bindings Implemented managed bindings for VK_EXT_descriptor_heap in Silk.NET, including the ExtDescriptorHeap class with all related Vulkan API methods. Added new structs, enums, and extension methods for the extension's types and flags. Provided VTable for dynamic function loading, structure chaining support, and proper interop annotations. Deprecated old enum values in favor of updated alternatives. --- .../Zenith.NET.Vulkan/ExtDescriptorHeap.cs | 2720 +++++++++++++++++ 1 file changed, 2720 insertions(+) create mode 100644 sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs diff --git a/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs new file mode 100644 index 00000000..9cd76607 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs @@ -0,0 +1,2720 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.Core; +using Silk.NET.Core.Attributes; +using Silk.NET.Core.Contexts; +using Silk.NET.Core.Native; +using Extension = Silk.NET.Core.Attributes.ExtensionAttribute; + +namespace Silk.NET.Vulkan.Extensions.EXT +{ + [Extension("VK_EXT_descriptor_heap")] + public unsafe partial class ExtDescriptorHeap : NativeExtension + { + public const uint SpecVersion = 1; + public const string ExtensionName = "VK_EXT_descriptor_heap"; + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] + public unsafe void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) + { + ((delegate* unmanaged[Stdcall]) VTable.CmdBindResourceHeap)(commandBuffer, pBindInfo); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] + public void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly BindHeapInfoEXT pBindInfo) + { + fixed (BindHeapInfoEXT* pBindInfoPtr = &pBindInfo) + { + CmdBindResourceHeap(commandBuffer, pBindInfoPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] + public unsafe void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) + { + ((delegate* unmanaged[Stdcall]) VTable.CmdBindSamplerHeap)(commandBuffer, pBindInfo); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] + public void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly BindHeapInfoEXT pBindInfo) + { + fixed (BindHeapInfoEXT* pBindInfoPtr = &pBindInfo) + { + CmdBindSamplerHeap(commandBuffer, pBindInfoPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] + public unsafe void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] PushDataInfoEXT* pPushDataInfo) + { + ((delegate* unmanaged[Stdcall]) VTable.CmdPushData)(commandBuffer, pPushDataInfo); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] + public void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly PushDataInfoEXT pPushDataInfo) + { + fixed (PushDataInfoEXT* pPushDataInfoPtr = &pPushDataInfo) + { + CmdPushData(commandBuffer, pPushDataInfoPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return ((delegate* unmanaged[Stdcall]) VTable.GetImageOpaqueCaptureData)(device, imageCount, pImages, pDatas); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetImageOpaqueCaptureData(device, imageCount, pImages, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ref readonly Image pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + fixed (Image* pImagesPtr = &pImages) + { + return GetImageOpaqueCaptureData(device, imageCount, pImagesPtr, pDatas); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ref readonly Image pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (Image* pImagesPtr = &pImages) + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetImageOpaqueCaptureData(device, imageCount, pImagesPtr, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetPhysicalDeviceDescriptorSizeEXT", Convention = CallingConvention.Winapi)] + public ulong GetPhysicalDeviceDescriptorSize([Count(Count = 0)] PhysicalDevice physicalDevice, [Count(Count = 0)] DescriptorType descriptorType) + { + return ((delegate* unmanaged[Stdcall]) VTable.GetPhysicalDeviceDescriptorSize)(physicalDevice, descriptorType); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return ((delegate* unmanaged[Stdcall]) VTable.GetTensorOpaqueCaptureData)(device, tensorCount, pTensors, pDatas); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetTensorOpaqueCaptureData(device, tensorCount, pTensors, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ref readonly TensorARM pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + fixed (TensorARM* pTensorsPtr = &pTensors) + { + return GetTensorOpaqueCaptureData(device, tensorCount, pTensorsPtr, pDatas); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ref readonly TensorARM pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (TensorARM* pTensorsPtr = &pTensors) + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetTensorOpaqueCaptureData(device, tensorCount, pTensorsPtr, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) + { + return ((delegate* unmanaged[Stdcall]) VTable.RegisterCustomBorderColor)(device, pBorderColor, requestIndex, pIndex); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] ref uint pIndex) + { + fixed (uint* pIndexPtr = &pIndex) + { + return RegisterCustomBorderColor(device, pBorderColor, requestIndex, pIndexPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly SamplerCustomBorderColorCreateInfoEXT pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) + { + fixed (SamplerCustomBorderColorCreateInfoEXT* pBorderColorPtr = &pBorderColor) + { + return RegisterCustomBorderColor(device, pBorderColorPtr, requestIndex, pIndex); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly SamplerCustomBorderColorCreateInfoEXT pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] ref uint pIndex) + { + fixed (SamplerCustomBorderColorCreateInfoEXT* pBorderColorPtr = &pBorderColor) + fixed (uint* pIndexPtr = &pIndex) + { + return RegisterCustomBorderColor(device, pBorderColorPtr, requestIndex, pIndexPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkUnregisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public void UnregisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0)] uint index) + { + ((delegate* unmanaged[Stdcall]) VTable.UnregisterCustomBorderColor)(device, index); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return ((delegate* unmanaged[Stdcall]) VTable.WriteResourceDescriptors)(device, resourceCount, pResources, pDescriptors); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteResourceDescriptors(device, resourceCount, pResources, pDescriptorsPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly ResourceDescriptorInfoEXT pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + fixed (ResourceDescriptorInfoEXT* pResourcesPtr = &pResources) + { + return WriteResourceDescriptors(device, resourceCount, pResourcesPtr, pDescriptors); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly ResourceDescriptorInfoEXT pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (ResourceDescriptorInfoEXT* pResourcesPtr = &pResources) + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteResourceDescriptors(device, resourceCount, pResourcesPtr, pDescriptorsPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return ((delegate* unmanaged[Stdcall]) VTable.WriteSamplerDescriptors)(device, samplerCount, pSamplers, pDescriptors); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteSamplerDescriptors(device, samplerCount, pSamplers, pDescriptorsPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly SamplerCreateInfo pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + fixed (SamplerCreateInfo* pSamplersPtr = &pSamplers) + { + return WriteSamplerDescriptors(device, samplerCount, pSamplersPtr, pDescriptors); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly SamplerCreateInfo pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (SamplerCreateInfo* pSamplersPtr = &pSamplers) + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteSamplerDescriptors(device, samplerCount, pSamplersPtr, pDescriptorsPtr); + } + } + + /// To be documented. + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetImageOpaqueCaptureData(device, (uint) pDatas.Length, pImages, out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return GetImageOpaqueCaptureData(device, (uint) pImages.Length, in pImages.GetPinnableReference(), pDatas); + } + + /// To be documented. + public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetImageOpaqueCaptureData(device, (uint) pDatas.Length, in pImages.GetPinnableReference(), out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetTensorOpaqueCaptureData(device, (uint) pDatas.Length, pTensors, out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return GetTensorOpaqueCaptureData(device, (uint) pTensors.Length, in pTensors.GetPinnableReference(), pDatas); + } + + /// To be documented. + public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetTensorOpaqueCaptureData(device, (uint) pDatas.Length, in pTensors.GetPinnableReference(), out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteResourceDescriptors(device, (uint) pDescriptors.Length, pResources, in pDescriptors.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return WriteResourceDescriptors(device, (uint) pResources.Length, in pResources.GetPinnableReference(), pDescriptors); + } + + /// To be documented. + public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteResourceDescriptors(device, (uint) pDescriptors.Length, in pResources.GetPinnableReference(), in pDescriptors.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteSamplerDescriptors(device, (uint) pDescriptors.Length, pSamplers, in pDescriptors.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return WriteSamplerDescriptors(device, (uint) pSamplers.Length, in pSamplers.GetPinnableReference(), pDescriptors); + } + + /// To be documented. + public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteSamplerDescriptors(device, (uint) pDescriptors.Length, in pSamplers.GetPinnableReference(), in pDescriptors.GetPinnableReference()); + } + + public ExtDescriptorHeap(INativeContext ctx) + : base(ctx) + { + } + + protected override IVTable CreateVTable() + { + return new GeneratedVTable(_ctx); + } + + private GeneratedVTable VTable => (GeneratedVTable) CurrentVTable; + + private sealed class GeneratedVTable(INativeContext ctx) : IVTable, IDisposable + { + private readonly INativeContext ctx = ctx; + private nint cmdBindResourceHeap; + private nint cmdBindSamplerHeap; + private nint cmdPushData; + private nint getImageOpaqueCaptureData; + private nint getPhysicalDeviceDescriptorSize; + private nint getTensorOpaqueCaptureData; + private nint registerCustomBorderColor; + private nint unregisterCustomBorderColor; + private nint writeResourceDescriptors; + private nint writeSamplerDescriptors; + + public nint CmdBindResourceHeap => cmdBindResourceHeap is 0 ? cmdBindResourceHeap = Load("vkCmdBindResourceHeapEXT") : cmdBindResourceHeap; + public nint CmdBindSamplerHeap => cmdBindSamplerHeap is 0 ? cmdBindSamplerHeap = Load("vkCmdBindSamplerHeapEXT") : cmdBindSamplerHeap; + public nint CmdPushData => cmdPushData is 0 ? cmdPushData = Load("vkCmdPushDataEXT") : cmdPushData; + public nint GetImageOpaqueCaptureData => getImageOpaqueCaptureData is 0 ? getImageOpaqueCaptureData = Load("vkGetImageOpaqueCaptureDataEXT") : getImageOpaqueCaptureData; + public nint GetPhysicalDeviceDescriptorSize => getPhysicalDeviceDescriptorSize is 0 ? getPhysicalDeviceDescriptorSize = Load("vkGetPhysicalDeviceDescriptorSizeEXT") : getPhysicalDeviceDescriptorSize; + public nint GetTensorOpaqueCaptureData => getTensorOpaqueCaptureData is 0 ? getTensorOpaqueCaptureData = Load("vkGetTensorOpaqueCaptureDataARM") : getTensorOpaqueCaptureData; + public nint RegisterCustomBorderColor => registerCustomBorderColor is 0 ? registerCustomBorderColor = Load("vkRegisterCustomBorderColorEXT") : registerCustomBorderColor; + public nint UnregisterCustomBorderColor => unregisterCustomBorderColor is 0 ? unregisterCustomBorderColor = Load("vkUnregisterCustomBorderColorEXT") : unregisterCustomBorderColor; + public nint WriteResourceDescriptors => writeResourceDescriptors is 0 ? writeResourceDescriptors = Load("vkWriteResourceDescriptorsEXT") : writeResourceDescriptors; + public nint WriteSamplerDescriptors => writeSamplerDescriptors is 0 ? writeSamplerDescriptors = Load("vkWriteSamplerDescriptorsEXT") : writeSamplerDescriptors; + + public nint Load(int slot, string name) + { + return Load(name); + } + + public nint Load(string name) + { + return ctx.GetProcAddress(name, null); + } + + public IVTable Clone() + { + return new GeneratedVTable(ctx); + } + + public void Purge() + { + cmdBindResourceHeap = 0; + cmdBindSamplerHeap = 0; + cmdPushData = 0; + getImageOpaqueCaptureData = 0; + getPhysicalDeviceDescriptorSize = 0; + getTensorOpaqueCaptureData = 0; + registerCustomBorderColor = 0; + unregisterCustomBorderColor = 0; + writeResourceDescriptors = 0; + writeSamplerDescriptors = 0; + } + + public void Dispose() + { + Purge(); + } + } + } +} + +namespace Silk.NET.Vulkan +{ + public static class ExtDescriptorHeapStructureTypeExtensions + { + extension(StructureType) + { + public static StructureType TexelBufferDescriptorInfoExt() + { + return (StructureType) 1000135000; + } + + public static StructureType ImageDescriptorInfoExt() + { + return (StructureType) 1000135001; + } + + public static StructureType ResourceDescriptorInfoExt() + { + return (StructureType) 1000135002; + } + + public static StructureType BindHeapInfoExt() + { + return (StructureType) 1000135003; + } + + public static StructureType PushDataInfoExt() + { + return (StructureType) 1000135004; + } + + public static StructureType DescriptorSetAndBindingMappingExt() + { + return (StructureType) 1000135005; + } + + public static StructureType ShaderDescriptorSetAndBindingMappingInfoExt() + { + return (StructureType) 1000135006; + } + + public static StructureType OpaqueCaptureDataCreateInfoExt() + { + return (StructureType) 1000135007; + } + + public static StructureType PhysicalDeviceDescriptorHeapPropertiesExt() + { + return (StructureType) 1000135008; + } + + public static StructureType PhysicalDeviceDescriptorHeapFeaturesExt() + { + return (StructureType) 1000135009; + } + + public static StructureType CommandBufferInheritanceDescriptorHeapInfoExt() + { + return (StructureType) 1000135010; + } + + public static StructureType SamplerCustomBorderColorIndexCreateInfoExt() + { + return (StructureType) 1000135011; + } + + public static StructureType IndirectCommandsLayoutPushDataTokenNV() + { + return (StructureType) 1000135012; + } + + public static StructureType SubsampledImageFormatPropertiesExt() + { + return (StructureType) 1000135013; + } + + public static StructureType PhysicalDeviceDescriptorHeapTensorPropertiesArm() + { + return (StructureType) 1000135014; + } + } + } + + public static class ExtDescriptorHeapAccessFlags2Extensions + { + extension(AccessFlags2) + { + [Obsolete("Deprecated in favour of \"SamplerHeapReadBitExt\"")] + [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] + public static AccessFlags2 Access2SamplerHeapReadBitExt() + { + return (AccessFlags2) 144115188075855872L; + } + + [Obsolete("Deprecated in favour of \"ResourceHeapReadBitExt\"")] + [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] + public static AccessFlags2 Access2ResourceHeapReadBitExt() + { + return (AccessFlags2) 288230376151711744L; + } + + [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] + public static AccessFlags2 SamplerHeapReadBitExt() + { + return (AccessFlags2) 144115188075855872L; + } + + [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] + public static AccessFlags2 ResourceHeapReadBitExt() + { + return (AccessFlags2) 288230376151711744L; + } + } + } + + public static class ExtDescriptorHeapBufferUsageFlagsExtensions + { + extension(BufferUsageFlags) + { + [Obsolete("Deprecated in favour of \"DescriptorHeapBitExt\"")] + [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags BufferUsageDescriptorHeapBitExt() + { + return (BufferUsageFlags) 268435456; + } + + [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags DescriptorHeapBitExt() + { + return (BufferUsageFlags) 268435456; + } + } + } + + public static class ExtDescriptorHeapBufferUsageFlags2Extensions + { + extension(BufferUsageFlags2) + { + [Obsolete("Deprecated in favour of \"DescriptorHeapBitExt\"")] + [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags2 BufferUsage2DescriptorHeapBitExt() + { + return (BufferUsageFlags2) 268435456; + } + + [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags2 DescriptorHeapBitExt() + { + return (BufferUsageFlags2) 268435456; + } + } + } + + public static class ExtDescriptorHeapPipelineCreateFlags2Extensions + { + extension(PipelineCreateFlags2) + { + [Obsolete("Deprecated in favour of \"Vk2DescriptorHeapBitExt\"")] + [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static PipelineCreateFlags2 PipelineCreate2DescriptorHeapBitExt() + { + return (PipelineCreateFlags2) 68719476736L; + } + + [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static PipelineCreateFlags2 Vk2DescriptorHeapBitExt() + { + return (PipelineCreateFlags2) 68719476736L; + } + } + } + + public static class ExtDescriptorHeapImageCreateFlagsExtensions + { + extension(ImageCreateFlags) + { + [Obsolete("Deprecated in favour of \"CreateDescriptorHeapCaptureReplayBitExt\"")] + [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] + public static ImageCreateFlags ImageCreateDescriptorHeapCaptureReplayBitExt() + { + return (ImageCreateFlags) 65536; + } + + [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] + public static ImageCreateFlags CreateDescriptorHeapCaptureReplayBitExt() + { + return (ImageCreateFlags) 65536; + } + } + } + + public static class ExtDescriptorHeapShaderCreateFlagsExtExtensions + { + extension(ShaderCreateFlagsEXT) + { + [Obsolete("Deprecated in favour of \"CreateDescriptorHeapBitExt\"")] + [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] + public static ShaderCreateFlagsEXT ShaderCreateDescriptorHeapBitExt() + { + return (ShaderCreateFlagsEXT) 1024; + } + + [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] + public static ShaderCreateFlagsEXT CreateDescriptorHeapBitExt() + { + return (ShaderCreateFlagsEXT) 1024; + } + } + } + + public static class ExtDescriptorHeapTensorCreateFlagsArmExtensions + { + extension(TensorCreateFlagsARM) + { + [Obsolete("Deprecated in favour of \"DescriptorHeapCaptureReplayBitArm\"")] + [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] + public static TensorCreateFlagsARM TensorCreateDescriptorHeapCaptureReplayBitArm() + { + return (TensorCreateFlagsARM) 8; + } + + [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] + public static TensorCreateFlagsARM DescriptorHeapCaptureReplayBitArm() + { + return (TensorCreateFlagsARM) 8; + } + } + } + + public static class ExtDescriptorHeapIndirectCommandsTokenTypeExtExtensions + { + extension(IndirectCommandsTokenTypeEXT) + { + [Obsolete("Deprecated in favour of \"PushDataExt\"")] + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] + public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataExt() + { + return (IndirectCommandsTokenTypeEXT) 1000135000; + } + + [Obsolete("Deprecated in favour of \"PushDataSequenceIndexExt\"")] + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] + public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataSequenceIndexExt() + { + return (IndirectCommandsTokenTypeEXT) 1000135001; + } + + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] + public static IndirectCommandsTokenTypeEXT PushDataExt() + { + return (IndirectCommandsTokenTypeEXT) 1000135000; + } + + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] + public static IndirectCommandsTokenTypeEXT PushDataSequenceIndexExt() + { + return (IndirectCommandsTokenTypeEXT) 1000135001; + } + } + } + + public static class ExtDescriptorHeapIndirectCommandsTokenTypeNvExtensions + { + extension(IndirectCommandsTokenTypeNV) + { + [Obsolete("Deprecated in favour of \"PushDataNV\"")] + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] + public static IndirectCommandsTokenTypeNV IndirectCommandsTokenTypePushDataNV() + { + return (IndirectCommandsTokenTypeNV) 1000135000; + } + + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] + public static IndirectCommandsTokenTypeNV PushDataNV() + { + return (IndirectCommandsTokenTypeNV) 1000135000; + } + } + } + + [NativeName("Name", "VkDescriptorMappingSourceEXT")] + public enum DescriptorMappingSourceEXT : int + { + [Obsolete("Deprecated in favour of \"HeapWithConstantOffsetExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT")] + DescriptorMappingSourceHeapWithConstantOffsetExt = 0, + [Obsolete("Deprecated in favour of \"HeapWithPushIndexExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT")] + DescriptorMappingSourceHeapWithPushIndexExt = 1, + [Obsolete("Deprecated in favour of \"HeapWithIndirectIndexExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_EXT")] + DescriptorMappingSourceHeapWithIndirectIndexExt = 2, + [Obsolete("Deprecated in favour of \"HeapWithIndirectIndexArrayExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_ARRAY_EXT")] + DescriptorMappingSourceHeapWithIndirectIndexArrayExt = 3, + [Obsolete("Deprecated in favour of \"ResourceHeapDataExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_RESOURCE_HEAP_DATA_EXT")] + DescriptorMappingSourceResourceHeapDataExt = 4, + [Obsolete("Deprecated in favour of \"PushDataExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_DATA_EXT")] + DescriptorMappingSourcePushDataExt = 5, + [Obsolete("Deprecated in favour of \"PushAddressExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_EXT")] + DescriptorMappingSourcePushAddressExt = 6, + [Obsolete("Deprecated in favour of \"IndirectAddressExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT")] + DescriptorMappingSourceIndirectAddressExt = 7, + [Obsolete("Deprecated in favour of \"HeapWithShaderRecordIndexExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_SHADER_RECORD_INDEX_EXT")] + DescriptorMappingSourceHeapWithShaderRecordIndexExt = 8, + [Obsolete("Deprecated in favour of \"ShaderRecordDataExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_DATA_EXT")] + DescriptorMappingSourceShaderRecordDataExt = 9, + [Obsolete("Deprecated in favour of \"ShaderRecordAddressExt\"")] + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_ADDRESS_EXT")] + DescriptorMappingSourceShaderRecordAddressExt = 10, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT")] + HeapWithConstantOffsetExt = 0, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT")] + HeapWithPushIndexExt = 1, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_EXT")] + HeapWithIndirectIndexExt = 2, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_ARRAY_EXT")] + HeapWithIndirectIndexArrayExt = 3, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_RESOURCE_HEAP_DATA_EXT")] + ResourceHeapDataExt = 4, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_DATA_EXT")] + PushDataExt = 5, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_EXT")] + PushAddressExt = 6, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT")] + IndirectAddressExt = 7, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_SHADER_RECORD_INDEX_EXT")] + HeapWithShaderRecordIndexExt = 8, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_DATA_EXT")] + ShaderRecordDataExt = 9, + [NativeName("Name", "VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_ADDRESS_EXT")] + ShaderRecordAddressExt = 10, + } + + [Flags] + [NativeName("Name", "VkSpirvResourceTypeFlagsEXT")] + public enum SpirvResourceTypeFlagsEXT : int + { + [NativeName("Name", "")] + None = 0, + [Obsolete("Deprecated in favour of \"AllExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_ALL_EXT")] + SpirvResourceTypeAllExt = 2147483647, + [Obsolete("Deprecated in favour of \"SamplerBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT")] + SpirvResourceTypeSamplerBitExt = 1, + [Obsolete("Deprecated in favour of \"SampledImageBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT")] + SpirvResourceTypeSampledImageBitExt = 2, + [Obsolete("Deprecated in favour of \"ReadOnlyImageBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT")] + SpirvResourceTypeReadOnlyImageBitExt = 4, + [Obsolete("Deprecated in favour of \"ReadWriteImageBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT")] + SpirvResourceTypeReadWriteImageBitExt = 8, + [Obsolete("Deprecated in favour of \"CombinedSampledImageBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT")] + SpirvResourceTypeCombinedSampledImageBitExt = 16, + [Obsolete("Deprecated in favour of \"UniformBufferBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT")] + SpirvResourceTypeUniformBufferBitExt = 32, + [Obsolete("Deprecated in favour of \"ReadOnlyStorageBufferBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT")] + SpirvResourceTypeReadOnlyStorageBufferBitExt = 64, + [Obsolete("Deprecated in favour of \"ReadWriteStorageBufferBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT")] + SpirvResourceTypeReadWriteStorageBufferBitExt = 128, + [Obsolete("Deprecated in favour of \"AccelerationStructureBitExt\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_ACCELERATION_STRUCTURE_BIT_EXT")] + SpirvResourceTypeAccelerationStructureBitExt = 256, + [Obsolete("Deprecated in favour of \"TensorBitArm\"")] + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM")] + SpirvResourceTypeTensorBitArm = 512, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_ALL_EXT")] + AllExt = 2147483647, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT")] + SamplerBitExt = 1, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT")] + SampledImageBitExt = 2, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT")] + ReadOnlyImageBitExt = 4, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT")] + ReadWriteImageBitExt = 8, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT")] + CombinedSampledImageBitExt = 16, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT")] + UniformBufferBitExt = 32, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT")] + ReadOnlyStorageBufferBitExt = 64, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT")] + ReadWriteStorageBufferBitExt = 128, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_ACCELERATION_STRUCTURE_BIT_EXT")] + AccelerationStructureBitExt = 256, + [NativeName("Name", "VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM")] + TensorBitArm = 512, + } + + [NativeName("Name", "VkHostAddressRangeEXT")] + public unsafe partial struct HostAddressRangeEXT + { + public HostAddressRangeEXT(void* address = null, nuint? size = null) + : this() + { + if (address is not null) + { + Address = address; + } + + if (size is not null) + { + Size = size.Value; + } + } + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "address")] + public void* Address; + + [NativeName("Type", "size_t")] + [NativeName("Type.Name", "size_t")] + [NativeName("Name", "size")] + public nuint Size; + } + + [NativeName("Name", "VkHostAddressRangeConstEXT")] + public unsafe partial struct HostAddressRangeConstEXT + { + public HostAddressRangeConstEXT(void* address = null, nuint? size = null) + : this() + { + if (address is not null) + { + Address = address; + } + + if (size is not null) + { + Size = size.Value; + } + } + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "address")] + public void* Address; + + [NativeName("Type", "size_t")] + [NativeName("Type.Name", "size_t")] + [NativeName("Name", "size")] + public nuint Size; + } + + [NativeName("Name", "VkDeviceAddressRangeEXT")] + [NativeName("AliasOf", "VkDeviceAddressRangeKHR")] + public unsafe partial struct DeviceAddressRangeEXT + { + public DeviceAddressRangeEXT(ulong? address = null, ulong? size = null) + : this() + { + if (address is not null) + { + Address = address.Value; + } + + if (size is not null) + { + Size = size.Value; + } + } + + [NativeName("Type", "VkDeviceAddress")] + [NativeName("Type.Name", "VkDeviceAddress")] + [NativeName("Name", "address")] + public ulong Address; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "size")] + public ulong Size; + } + + [NativeName("Name", "VkTexelBufferDescriptorInfoEXT")] + public unsafe partial struct TexelBufferDescriptorInfoEXT : IChainable + { + public TexelBufferDescriptorInfoEXT(StructureType? sType = null, void* pNext = null, Format? format = null, DeviceAddressRangeEXT? addressRange = null) + : this() + { + SType = sType ?? StructureType.TexelBufferDescriptorInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (format is not null) + { + Format = format.Value; + } + + if (addressRange is not null) + { + AddressRange = addressRange.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkFormat")] + [NativeName("Type.Name", "VkFormat")] + [NativeName("Name", "format")] + public Format Format; + + [NativeName("Type", "VkDeviceAddressRangeEXT")] + [NativeName("Type.Name", "VkDeviceAddressRangeEXT")] + [NativeName("Name", "addressRange")] + public DeviceAddressRangeEXT AddressRange; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.TexelBufferDescriptorInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkImageDescriptorInfoEXT")] + public unsafe partial struct ImageDescriptorInfoEXT : IChainable + { + public ImageDescriptorInfoEXT(StructureType? sType = null, void* pNext = null, ImageViewCreateInfo* pView = null, ImageLayout? layout = null) + : this() + { + SType = sType ?? StructureType.ImageDescriptorInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (pView is not null) + { + PView = pView; + } + + if (layout is not null) + { + Layout = layout.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkImageViewCreateInfo*")] + [NativeName("Type.Name", "VkImageViewCreateInfo")] + [NativeName("Name", "pView")] + public ImageViewCreateInfo* PView; + + [NativeName("Type", "VkImageLayout")] + [NativeName("Type.Name", "VkImageLayout")] + [NativeName("Name", "layout")] + public ImageLayout Layout; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.ImageDescriptorInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [StructLayout(LayoutKind.Explicit)] + [NativeName("Name", "VkResourceDescriptorDataEXT")] + public unsafe partial struct ResourceDescriptorDataEXT + { + public ResourceDescriptorDataEXT(ImageDescriptorInfoEXT* pImage = null, TexelBufferDescriptorInfoEXT* pTexelBuffer = null, DeviceAddressRangeEXT* pAddressRange = null, TensorViewCreateInfoARM* pTensorArm = null) + : this() + { + if (pImage is not null) + { + PImage = pImage; + } + + if (pTexelBuffer is not null) + { + PTexelBuffer = pTexelBuffer; + } + + if (pAddressRange is not null) + { + PAddressRange = pAddressRange; + } + + if (pTensorArm is not null) + { + PTensorArm = pTensorArm; + } + } + + [FieldOffset(0)] + [NativeName("Type", "VkImageDescriptorInfoEXT*")] + [NativeName("Type.Name", "VkImageDescriptorInfoEXT")] + [NativeName("Name", "pImage")] + public ImageDescriptorInfoEXT* PImage; + + [FieldOffset(0)] + [NativeName("Type", "VkTexelBufferDescriptorInfoEXT*")] + [NativeName("Type.Name", "VkTexelBufferDescriptorInfoEXT")] + [NativeName("Name", "pTexelBuffer")] + public TexelBufferDescriptorInfoEXT* PTexelBuffer; + + [FieldOffset(0)] + [NativeName("Type", "VkDeviceAddressRangeEXT*")] + [NativeName("Type.Name", "VkDeviceAddressRangeEXT")] + [NativeName("Name", "pAddressRange")] + public DeviceAddressRangeEXT* PAddressRange; + + [FieldOffset(0)] + [NativeName("Type", "VkTensorViewCreateInfoARM*")] + [NativeName("Type.Name", "VkTensorViewCreateInfoARM")] + [NativeName("Name", "pTensorARM")] + public TensorViewCreateInfoARM* PTensorArm; + } + + [NativeName("Name", "VkResourceDescriptorInfoEXT")] + public unsafe partial struct ResourceDescriptorInfoEXT : IChainStart + { + public ResourceDescriptorInfoEXT(StructureType? sType = null, void* pNext = null, DescriptorType? type = null, ResourceDescriptorDataEXT? data = null) + : this() + { + SType = sType ?? StructureType.ResourceDescriptorInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (type is not null) + { + Type = type.Value; + } + + if (data is not null) + { + Data = data.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkDescriptorType")] + [NativeName("Type.Name", "VkDescriptorType")] + [NativeName("Name", "type")] + public DescriptorType Type; + + [NativeName("Type", "VkResourceDescriptorDataEXT")] + [NativeName("Type.Name", "VkResourceDescriptorDataEXT")] + [NativeName("Name", "data")] + public ResourceDescriptorDataEXT Data; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.ResourceDescriptorInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + + public static ref ResourceDescriptorInfoEXT Chain(out ResourceDescriptorInfoEXT capture) + { + capture = new ResourceDescriptorInfoEXT(StructureType.ResourceDescriptorInfoExt()); + return ref capture; + } + } + + [NativeName("Name", "VkBindHeapInfoEXT")] + public unsafe partial struct BindHeapInfoEXT : IChainable + { + public BindHeapInfoEXT(StructureType? sType = null, void* pNext = null, DeviceAddressRangeEXT? heapRange = null, ulong? reservedRangeOffset = null, ulong? reservedRangeSize = null) + : this() + { + SType = sType ?? StructureType.BindHeapInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (heapRange is not null) + { + HeapRange = heapRange.Value; + } + + if (reservedRangeOffset is not null) + { + ReservedRangeOffset = reservedRangeOffset.Value; + } + + if (reservedRangeSize is not null) + { + ReservedRangeSize = reservedRangeSize.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkDeviceAddressRangeEXT")] + [NativeName("Type.Name", "VkDeviceAddressRangeEXT")] + [NativeName("Name", "heapRange")] + public DeviceAddressRangeEXT HeapRange; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "reservedRangeOffset")] + public ulong ReservedRangeOffset; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "reservedRangeSize")] + public ulong ReservedRangeSize; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.BindHeapInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkPushDataInfoEXT")] + public unsafe partial struct PushDataInfoEXT : IChainStart + { + public PushDataInfoEXT(StructureType? sType = null, void* pNext = null, uint? offset = null, HostAddressRangeConstEXT? data = null) + : this() + { + SType = sType ?? StructureType.PushDataInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (offset is not null) + { + Offset = offset.Value; + } + + if (data is not null) + { + Data = data.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "offset")] + public uint Offset; + + [NativeName("Type", "VkHostAddressRangeConstEXT")] + [NativeName("Type.Name", "VkHostAddressRangeConstEXT")] + [NativeName("Name", "data")] + public HostAddressRangeConstEXT Data; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.PushDataInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + + public static ref PushDataInfoEXT Chain(out PushDataInfoEXT capture) + { + capture = new PushDataInfoEXT(StructureType.PushDataInfoExt()); + return ref capture; + } + } + + [NativeName("Name", "VkIndirectCommandsLayoutPushDataTokenNV")] + public unsafe partial struct IndirectCommandsLayoutPushDataTokenNV : IExtendsChain + { + public IndirectCommandsLayoutPushDataTokenNV(StructureType? sType = null, void* pNext = null, uint? pushDataOffset = null, uint? pushDataSize = null) + : this() + { + SType = sType ?? StructureType.IndirectCommandsLayoutPushDataTokenNV(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (pushDataOffset is not null) + { + PushDataOffset = pushDataOffset.Value; + } + + if (pushDataSize is not null) + { + PushDataSize = pushDataSize.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushDataOffset")] + public uint PushDataOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushDataSize")] + public uint PushDataSize; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.IndirectCommandsLayoutPushDataTokenNV(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkDescriptorMappingSourceConstantOffsetEXT")] + public unsafe partial struct DescriptorMappingSourceConstantOffsetEXT + { + public DescriptorMappingSourceConstantOffsetEXT(uint? heapOffset = null, uint? heapArrayStride = null, SamplerCreateInfo* pEmbeddedSampler = null, uint? samplerHeapOffset = null, uint? samplerHeapArrayStride = null) + : this() + { + if (heapOffset is not null) + { + HeapOffset = heapOffset.Value; + } + + if (heapArrayStride is not null) + { + HeapArrayStride = heapArrayStride.Value; + } + + if (pEmbeddedSampler is not null) + { + PEmbeddedSampler = pEmbeddedSampler; + } + + if (samplerHeapOffset is not null) + { + SamplerHeapOffset = samplerHeapOffset.Value; + } + + if (samplerHeapArrayStride is not null) + { + SamplerHeapArrayStride = samplerHeapArrayStride.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapOffset")] + public uint HeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapArrayStride")] + public uint HeapArrayStride; + + [NativeName("Type", "VkSamplerCreateInfo*")] + [NativeName("Type.Name", "VkSamplerCreateInfo")] + [NativeName("Name", "pEmbeddedSampler")] + public SamplerCreateInfo* PEmbeddedSampler; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapOffset")] + public uint SamplerHeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapArrayStride")] + public uint SamplerHeapArrayStride; + } + + [NativeName("Name", "VkDescriptorMappingSourcePushIndexEXT")] + public unsafe partial struct DescriptorMappingSourcePushIndexEXT + { + public DescriptorMappingSourcePushIndexEXT(uint? heapOffset = null, uint? pushOffset = null, uint? heapIndexStride = null, uint? heapArrayStride = null, SamplerCreateInfo* pEmbeddedSampler = null, Bool32? useCombinedImageSamplerIndex = null, uint? samplerHeapOffset = null, uint? samplerPushOffset = null, uint? samplerHeapIndexStride = null, uint? samplerHeapArrayStride = null) + : this() + { + if (heapOffset is not null) + { + HeapOffset = heapOffset.Value; + } + + if (pushOffset is not null) + { + PushOffset = pushOffset.Value; + } + + if (heapIndexStride is not null) + { + HeapIndexStride = heapIndexStride.Value; + } + + if (heapArrayStride is not null) + { + HeapArrayStride = heapArrayStride.Value; + } + + if (pEmbeddedSampler is not null) + { + PEmbeddedSampler = pEmbeddedSampler; + } + + if (useCombinedImageSamplerIndex is not null) + { + UseCombinedImageSamplerIndex = useCombinedImageSamplerIndex.Value; + } + + if (samplerHeapOffset is not null) + { + SamplerHeapOffset = samplerHeapOffset.Value; + } + + if (samplerPushOffset is not null) + { + SamplerPushOffset = samplerPushOffset.Value; + } + + if (samplerHeapIndexStride is not null) + { + SamplerHeapIndexStride = samplerHeapIndexStride.Value; + } + + if (samplerHeapArrayStride is not null) + { + SamplerHeapArrayStride = samplerHeapArrayStride.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapOffset")] + public uint HeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushOffset")] + public uint PushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapIndexStride")] + public uint HeapIndexStride; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapArrayStride")] + public uint HeapArrayStride; + + [NativeName("Type", "VkSamplerCreateInfo*")] + [NativeName("Type.Name", "VkSamplerCreateInfo")] + [NativeName("Name", "pEmbeddedSampler")] + public SamplerCreateInfo* PEmbeddedSampler; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "useCombinedImageSamplerIndex")] + public Bool32 UseCombinedImageSamplerIndex; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapOffset")] + public uint SamplerHeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerPushOffset")] + public uint SamplerPushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapIndexStride")] + public uint SamplerHeapIndexStride; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapArrayStride")] + public uint SamplerHeapArrayStride; + } + + [NativeName("Name", "VkDescriptorMappingSourceIndirectIndexEXT")] + public unsafe partial struct DescriptorMappingSourceIndirectIndexEXT + { + public DescriptorMappingSourceIndirectIndexEXT(uint? heapOffset = null, uint? pushOffset = null, uint? addressOffset = null, uint? heapIndexStride = null, uint? heapArrayStride = null, SamplerCreateInfo* pEmbeddedSampler = null, Bool32? useCombinedImageSamplerIndex = null, uint? samplerHeapOffset = null, uint? samplerPushOffset = null, uint? samplerAddressOffset = null, uint? samplerHeapIndexStride = null, uint? samplerHeapArrayStride = null) + : this() + { + if (heapOffset is not null) + { + HeapOffset = heapOffset.Value; + } + + if (pushOffset is not null) + { + PushOffset = pushOffset.Value; + } + + if (addressOffset is not null) + { + AddressOffset = addressOffset.Value; + } + + if (heapIndexStride is not null) + { + HeapIndexStride = heapIndexStride.Value; + } + + if (heapArrayStride is not null) + { + HeapArrayStride = heapArrayStride.Value; + } + + if (pEmbeddedSampler is not null) + { + PEmbeddedSampler = pEmbeddedSampler; + } + + if (useCombinedImageSamplerIndex is not null) + { + UseCombinedImageSamplerIndex = useCombinedImageSamplerIndex.Value; + } + + if (samplerHeapOffset is not null) + { + SamplerHeapOffset = samplerHeapOffset.Value; + } + + if (samplerPushOffset is not null) + { + SamplerPushOffset = samplerPushOffset.Value; + } + + if (samplerAddressOffset is not null) + { + SamplerAddressOffset = samplerAddressOffset.Value; + } + + if (samplerHeapIndexStride is not null) + { + SamplerHeapIndexStride = samplerHeapIndexStride.Value; + } + + if (samplerHeapArrayStride is not null) + { + SamplerHeapArrayStride = samplerHeapArrayStride.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapOffset")] + public uint HeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushOffset")] + public uint PushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "addressOffset")] + public uint AddressOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapIndexStride")] + public uint HeapIndexStride; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapArrayStride")] + public uint HeapArrayStride; + + [NativeName("Type", "VkSamplerCreateInfo*")] + [NativeName("Type.Name", "VkSamplerCreateInfo")] + [NativeName("Name", "pEmbeddedSampler")] + public SamplerCreateInfo* PEmbeddedSampler; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "useCombinedImageSamplerIndex")] + public Bool32 UseCombinedImageSamplerIndex; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapOffset")] + public uint SamplerHeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerPushOffset")] + public uint SamplerPushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerAddressOffset")] + public uint SamplerAddressOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapIndexStride")] + public uint SamplerHeapIndexStride; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapArrayStride")] + public uint SamplerHeapArrayStride; + } + + [NativeName("Name", "VkDescriptorMappingSourceIndirectIndexArrayEXT")] + public unsafe partial struct DescriptorMappingSourceIndirectIndexArrayEXT + { + public DescriptorMappingSourceIndirectIndexArrayEXT(uint? heapOffset = null, uint? pushOffset = null, uint? addressOffset = null, uint? heapIndexStride = null, SamplerCreateInfo* pEmbeddedSampler = null, Bool32? useCombinedImageSamplerIndex = null, uint? samplerHeapOffset = null, uint? samplerPushOffset = null, uint? samplerAddressOffset = null, uint? samplerHeapIndexStride = null) + : this() + { + if (heapOffset is not null) + { + HeapOffset = heapOffset.Value; + } + + if (pushOffset is not null) + { + PushOffset = pushOffset.Value; + } + + if (addressOffset is not null) + { + AddressOffset = addressOffset.Value; + } + + if (heapIndexStride is not null) + { + HeapIndexStride = heapIndexStride.Value; + } + + if (pEmbeddedSampler is not null) + { + PEmbeddedSampler = pEmbeddedSampler; + } + + if (useCombinedImageSamplerIndex is not null) + { + UseCombinedImageSamplerIndex = useCombinedImageSamplerIndex.Value; + } + + if (samplerHeapOffset is not null) + { + SamplerHeapOffset = samplerHeapOffset.Value; + } + + if (samplerPushOffset is not null) + { + SamplerPushOffset = samplerPushOffset.Value; + } + + if (samplerAddressOffset is not null) + { + SamplerAddressOffset = samplerAddressOffset.Value; + } + + if (samplerHeapIndexStride is not null) + { + SamplerHeapIndexStride = samplerHeapIndexStride.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapOffset")] + public uint HeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushOffset")] + public uint PushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "addressOffset")] + public uint AddressOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapIndexStride")] + public uint HeapIndexStride; + + [NativeName("Type", "VkSamplerCreateInfo*")] + [NativeName("Type.Name", "VkSamplerCreateInfo")] + [NativeName("Name", "pEmbeddedSampler")] + public SamplerCreateInfo* PEmbeddedSampler; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "useCombinedImageSamplerIndex")] + public Bool32 UseCombinedImageSamplerIndex; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapOffset")] + public uint SamplerHeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerPushOffset")] + public uint SamplerPushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerAddressOffset")] + public uint SamplerAddressOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapIndexStride")] + public uint SamplerHeapIndexStride; + } + + [NativeName("Name", "VkDescriptorMappingSourceHeapDataEXT")] + public unsafe partial struct DescriptorMappingSourceHeapDataEXT + { + public DescriptorMappingSourceHeapDataEXT(uint? heapOffset = null, uint? pushOffset = null) + : this() + { + if (heapOffset is not null) + { + HeapOffset = heapOffset.Value; + } + + if (pushOffset is not null) + { + PushOffset = pushOffset.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapOffset")] + public uint HeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushOffset")] + public uint PushOffset; + } + + [NativeName("Name", "VkDescriptorMappingSourceIndirectAddressEXT")] + public unsafe partial struct DescriptorMappingSourceIndirectAddressEXT + { + public DescriptorMappingSourceIndirectAddressEXT(uint? pushOffset = null, uint? addressOffset = null) + : this() + { + if (pushOffset is not null) + { + PushOffset = pushOffset.Value; + } + + if (addressOffset is not null) + { + AddressOffset = addressOffset.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushOffset")] + public uint PushOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "addressOffset")] + public uint AddressOffset; + } + + [NativeName("Name", "VkDescriptorMappingSourceShaderRecordIndexEXT")] + public unsafe partial struct DescriptorMappingSourceShaderRecordIndexEXT + { + public DescriptorMappingSourceShaderRecordIndexEXT(uint? heapOffset = null, uint? shaderRecordOffset = null, uint? heapIndexStride = null, uint? heapArrayStride = null, SamplerCreateInfo* pEmbeddedSampler = null, Bool32? useCombinedImageSamplerIndex = null, uint? samplerHeapOffset = null, uint? samplerShaderRecordOffset = null, uint? samplerHeapIndexStride = null, uint? samplerHeapArrayStride = null) + : this() + { + if (heapOffset is not null) + { + HeapOffset = heapOffset.Value; + } + + if (shaderRecordOffset is not null) + { + ShaderRecordOffset = shaderRecordOffset.Value; + } + + if (heapIndexStride is not null) + { + HeapIndexStride = heapIndexStride.Value; + } + + if (heapArrayStride is not null) + { + HeapArrayStride = heapArrayStride.Value; + } + + if (pEmbeddedSampler is not null) + { + PEmbeddedSampler = pEmbeddedSampler; + } + + if (useCombinedImageSamplerIndex is not null) + { + UseCombinedImageSamplerIndex = useCombinedImageSamplerIndex.Value; + } + + if (samplerHeapOffset is not null) + { + SamplerHeapOffset = samplerHeapOffset.Value; + } + + if (samplerShaderRecordOffset is not null) + { + SamplerShaderRecordOffset = samplerShaderRecordOffset.Value; + } + + if (samplerHeapIndexStride is not null) + { + SamplerHeapIndexStride = samplerHeapIndexStride.Value; + } + + if (samplerHeapArrayStride is not null) + { + SamplerHeapArrayStride = samplerHeapArrayStride.Value; + } + } + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapOffset")] + public uint HeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "shaderRecordOffset")] + public uint ShaderRecordOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapIndexStride")] + public uint HeapIndexStride; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "heapArrayStride")] + public uint HeapArrayStride; + + [NativeName("Type", "VkSamplerCreateInfo*")] + [NativeName("Type.Name", "VkSamplerCreateInfo")] + [NativeName("Name", "pEmbeddedSampler")] + public SamplerCreateInfo* PEmbeddedSampler; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "useCombinedImageSamplerIndex")] + public Bool32 UseCombinedImageSamplerIndex; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapOffset")] + public uint SamplerHeapOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerShaderRecordOffset")] + public uint SamplerShaderRecordOffset; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapIndexStride")] + public uint SamplerHeapIndexStride; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerHeapArrayStride")] + public uint SamplerHeapArrayStride; + } + + [StructLayout(LayoutKind.Explicit)] + [NativeName("Name", "VkDescriptorMappingSourceDataEXT")] + public unsafe partial struct DescriptorMappingSourceDataEXT + { + public DescriptorMappingSourceDataEXT(DescriptorMappingSourceConstantOffsetEXT? constantOffset = null, DescriptorMappingSourcePushIndexEXT? pushIndex = null, DescriptorMappingSourceIndirectIndexEXT? indirectIndex = null, DescriptorMappingSourceIndirectIndexArrayEXT? indirectIndexArray = null, DescriptorMappingSourceHeapDataEXT? heapData = null, uint? pushDataOffset = null, uint? pushAddressOffset = null, DescriptorMappingSourceIndirectAddressEXT? indirectAddress = null, DescriptorMappingSourceShaderRecordIndexEXT? shaderRecordIndex = null, uint? shaderRecordDataOffset = null, uint? shaderRecordAddressOffset = null) + : this() + { + if (constantOffset is not null) + { + ConstantOffset = constantOffset.Value; + } + + if (pushIndex is not null) + { + PushIndex = pushIndex.Value; + } + + if (indirectIndex is not null) + { + IndirectIndex = indirectIndex.Value; + } + + if (indirectIndexArray is not null) + { + IndirectIndexArray = indirectIndexArray.Value; + } + + if (heapData is not null) + { + HeapData = heapData.Value; + } + + if (pushDataOffset is not null) + { + PushDataOffset = pushDataOffset.Value; + } + + if (pushAddressOffset is not null) + { + PushAddressOffset = pushAddressOffset.Value; + } + + if (indirectAddress is not null) + { + IndirectAddress = indirectAddress.Value; + } + + if (shaderRecordIndex is not null) + { + ShaderRecordIndex = shaderRecordIndex.Value; + } + + if (shaderRecordDataOffset is not null) + { + ShaderRecordDataOffset = shaderRecordDataOffset.Value; + } + + if (shaderRecordAddressOffset is not null) + { + ShaderRecordAddressOffset = shaderRecordAddressOffset.Value; + } + } + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourceConstantOffsetEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceConstantOffsetEXT")] + [NativeName("Name", "constantOffset")] + public DescriptorMappingSourceConstantOffsetEXT ConstantOffset; + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourcePushIndexEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourcePushIndexEXT")] + [NativeName("Name", "pushIndex")] + public DescriptorMappingSourcePushIndexEXT PushIndex; + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourceIndirectIndexEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceIndirectIndexEXT")] + [NativeName("Name", "indirectIndex")] + public DescriptorMappingSourceIndirectIndexEXT IndirectIndex; + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourceIndirectIndexArrayEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceIndirectIndexArrayEXT")] + [NativeName("Name", "indirectIndexArray")] + public DescriptorMappingSourceIndirectIndexArrayEXT IndirectIndexArray; + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourceHeapDataEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceHeapDataEXT")] + [NativeName("Name", "heapData")] + public DescriptorMappingSourceHeapDataEXT HeapData; + + [FieldOffset(0)] + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushDataOffset")] + public uint PushDataOffset; + + [FieldOffset(0)] + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "pushAddressOffset")] + public uint PushAddressOffset; + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourceIndirectAddressEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceIndirectAddressEXT")] + [NativeName("Name", "indirectAddress")] + public DescriptorMappingSourceIndirectAddressEXT IndirectAddress; + + [FieldOffset(0)] + [NativeName("Type", "VkDescriptorMappingSourceShaderRecordIndexEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceShaderRecordIndexEXT")] + [NativeName("Name", "shaderRecordIndex")] + public DescriptorMappingSourceShaderRecordIndexEXT ShaderRecordIndex; + + [FieldOffset(0)] + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "shaderRecordDataOffset")] + public uint ShaderRecordDataOffset; + + [FieldOffset(0)] + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "shaderRecordAddressOffset")] + public uint ShaderRecordAddressOffset; + } + + [NativeName("Name", "VkDescriptorSetAndBindingMappingEXT")] + public unsafe partial struct DescriptorSetAndBindingMappingEXT : IChainStart + { + public DescriptorSetAndBindingMappingEXT(StructureType? sType = null, void* pNext = null, uint? descriptorSet = null, uint? firstBinding = null, uint? bindingCount = null, SpirvResourceTypeFlagsEXT? resourceMask = null, DescriptorMappingSourceEXT? source = null, DescriptorMappingSourceDataEXT? sourceData = null) + : this() + { + SType = sType ?? StructureType.DescriptorSetAndBindingMappingExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (descriptorSet is not null) + { + DescriptorSet = descriptorSet.Value; + } + + if (firstBinding is not null) + { + FirstBinding = firstBinding.Value; + } + + if (bindingCount is not null) + { + BindingCount = bindingCount.Value; + } + + if (resourceMask is not null) + { + ResourceMask = resourceMask.Value; + } + + if (source is not null) + { + Source = source.Value; + } + + if (sourceData is not null) + { + SourceData = sourceData.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "descriptorSet")] + public uint DescriptorSet; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "firstBinding")] + public uint FirstBinding; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "bindingCount")] + public uint BindingCount; + + [NativeName("Type", "VkSpirvResourceTypeFlagsEXT")] + [NativeName("Type.Name", "VkSpirvResourceTypeFlagsEXT")] + [NativeName("Name", "resourceMask")] + public SpirvResourceTypeFlagsEXT ResourceMask; + + [NativeName("Type", "VkDescriptorMappingSourceEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceEXT")] + [NativeName("Name", "source")] + public DescriptorMappingSourceEXT Source; + + [NativeName("Type", "VkDescriptorMappingSourceDataEXT")] + [NativeName("Type.Name", "VkDescriptorMappingSourceDataEXT")] + [NativeName("Name", "sourceData")] + public DescriptorMappingSourceDataEXT SourceData; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.DescriptorSetAndBindingMappingExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + + public static ref DescriptorSetAndBindingMappingEXT Chain(out DescriptorSetAndBindingMappingEXT capture) + { + capture = new DescriptorSetAndBindingMappingEXT(StructureType.DescriptorSetAndBindingMappingExt()); + return ref capture; + } + } + + [NativeName("Name", "VkShaderDescriptorSetAndBindingMappingInfoEXT")] + public unsafe partial struct ShaderDescriptorSetAndBindingMappingInfoEXT : IExtendsChain, IExtendsChain + { + public ShaderDescriptorSetAndBindingMappingInfoEXT(StructureType? sType = null, void* pNext = null, uint? mappingCount = null, DescriptorSetAndBindingMappingEXT* pMappings = null) + : this() + { + SType = sType ?? StructureType.ShaderDescriptorSetAndBindingMappingInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (mappingCount is not null) + { + MappingCount = mappingCount.Value; + } + + if (pMappings is not null) + { + PMappings = pMappings; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "mappingCount")] + public uint MappingCount; + + [NativeName("Type", "VkDescriptorSetAndBindingMappingEXT*")] + [NativeName("Type.Name", "VkDescriptorSetAndBindingMappingEXT")] + [NativeName("Name", "pMappings")] + public DescriptorSetAndBindingMappingEXT* PMappings; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.ShaderDescriptorSetAndBindingMappingInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkOpaqueCaptureDataCreateInfoEXT")] + public unsafe partial struct OpaqueCaptureDataCreateInfoEXT : IExtendsChain, IExtendsChain + { + public OpaqueCaptureDataCreateInfoEXT(StructureType? sType = null, void* pNext = null, HostAddressRangeConstEXT* pData = null) + : this() + { + SType = sType ?? StructureType.OpaqueCaptureDataCreateInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (pData is not null) + { + PData = pData; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkHostAddressRangeConstEXT*")] + [NativeName("Type.Name", "VkHostAddressRangeConstEXT")] + [NativeName("Name", "pData")] + public HostAddressRangeConstEXT* PData; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.OpaqueCaptureDataCreateInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkPhysicalDeviceDescriptorHeapFeaturesEXT")] + public unsafe partial struct PhysicalDeviceDescriptorHeapFeaturesEXT : IExtendsChain, IExtendsChain, IExtendsChain + { + public PhysicalDeviceDescriptorHeapFeaturesEXT(StructureType? sType = null, void* pNext = null, Bool32? descriptorHeap = null, Bool32? descriptorHeapCaptureReplay = null) + : this() + { + SType = sType ?? StructureType.PhysicalDeviceDescriptorHeapFeaturesExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (descriptorHeap is not null) + { + DescriptorHeap = descriptorHeap.Value; + } + + if (descriptorHeapCaptureReplay is not null) + { + DescriptorHeapCaptureReplay = descriptorHeapCaptureReplay.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "descriptorHeap")] + public Bool32 DescriptorHeap; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "descriptorHeapCaptureReplay")] + public Bool32 DescriptorHeapCaptureReplay; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.PhysicalDeviceDescriptorHeapFeaturesExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkPhysicalDeviceDescriptorHeapPropertiesEXT")] + public unsafe partial struct PhysicalDeviceDescriptorHeapPropertiesEXT : IExtendsChain, IExtendsChain + { + public PhysicalDeviceDescriptorHeapPropertiesEXT(StructureType? sType = null, void* pNext = null, ulong? samplerHeapAlignment = null, ulong? resourceHeapAlignment = null, ulong? maxSamplerHeapSize = null, ulong? maxResourceHeapSize = null, ulong? minSamplerHeapReservedRange = null, ulong? minSamplerHeapReservedRangeWithEmbedded = null, ulong? minResourceHeapReservedRange = null, ulong? samplerDescriptorSize = null, ulong? imageDescriptorSize = null, ulong? bufferDescriptorSize = null, ulong? samplerDescriptorAlignment = null, ulong? imageDescriptorAlignment = null, ulong? bufferDescriptorAlignment = null, ulong? maxPushDataSize = null, nuint? imageCaptureReplayOpaqueDataSize = null, uint? maxDescriptorHeapEmbeddedSamplers = null, uint? samplerYcbcrConversionCount = null, Bool32? sparseDescriptorHeaps = null, Bool32? protectedDescriptorHeaps = null) + : this() + { + SType = sType ?? StructureType.PhysicalDeviceDescriptorHeapPropertiesExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (samplerHeapAlignment is not null) + { + SamplerHeapAlignment = samplerHeapAlignment.Value; + } + + if (resourceHeapAlignment is not null) + { + ResourceHeapAlignment = resourceHeapAlignment.Value; + } + + if (maxSamplerHeapSize is not null) + { + MaxSamplerHeapSize = maxSamplerHeapSize.Value; + } + + if (maxResourceHeapSize is not null) + { + MaxResourceHeapSize = maxResourceHeapSize.Value; + } + + if (minSamplerHeapReservedRange is not null) + { + MinSamplerHeapReservedRange = minSamplerHeapReservedRange.Value; + } + + if (minSamplerHeapReservedRangeWithEmbedded is not null) + { + MinSamplerHeapReservedRangeWithEmbedded = minSamplerHeapReservedRangeWithEmbedded.Value; + } + + if (minResourceHeapReservedRange is not null) + { + MinResourceHeapReservedRange = minResourceHeapReservedRange.Value; + } + + if (samplerDescriptorSize is not null) + { + SamplerDescriptorSize = samplerDescriptorSize.Value; + } + + if (imageDescriptorSize is not null) + { + ImageDescriptorSize = imageDescriptorSize.Value; + } + + if (bufferDescriptorSize is not null) + { + BufferDescriptorSize = bufferDescriptorSize.Value; + } + + if (samplerDescriptorAlignment is not null) + { + SamplerDescriptorAlignment = samplerDescriptorAlignment.Value; + } + + if (imageDescriptorAlignment is not null) + { + ImageDescriptorAlignment = imageDescriptorAlignment.Value; + } + + if (bufferDescriptorAlignment is not null) + { + BufferDescriptorAlignment = bufferDescriptorAlignment.Value; + } + + if (maxPushDataSize is not null) + { + MaxPushDataSize = maxPushDataSize.Value; + } + + if (imageCaptureReplayOpaqueDataSize is not null) + { + ImageCaptureReplayOpaqueDataSize = imageCaptureReplayOpaqueDataSize.Value; + } + + if (maxDescriptorHeapEmbeddedSamplers is not null) + { + MaxDescriptorHeapEmbeddedSamplers = maxDescriptorHeapEmbeddedSamplers.Value; + } + + if (samplerYcbcrConversionCount is not null) + { + SamplerYcbcrConversionCount = samplerYcbcrConversionCount.Value; + } + + if (sparseDescriptorHeaps is not null) + { + SparseDescriptorHeaps = sparseDescriptorHeaps.Value; + } + + if (protectedDescriptorHeaps is not null) + { + ProtectedDescriptorHeaps = protectedDescriptorHeaps.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "samplerHeapAlignment")] + public ulong SamplerHeapAlignment; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "resourceHeapAlignment")] + public ulong ResourceHeapAlignment; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "maxSamplerHeapSize")] + public ulong MaxSamplerHeapSize; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "maxResourceHeapSize")] + public ulong MaxResourceHeapSize; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "minSamplerHeapReservedRange")] + public ulong MinSamplerHeapReservedRange; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "minSamplerHeapReservedRangeWithEmbedded")] + public ulong MinSamplerHeapReservedRangeWithEmbedded; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "minResourceHeapReservedRange")] + public ulong MinResourceHeapReservedRange; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "samplerDescriptorSize")] + public ulong SamplerDescriptorSize; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "imageDescriptorSize")] + public ulong ImageDescriptorSize; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "bufferDescriptorSize")] + public ulong BufferDescriptorSize; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "samplerDescriptorAlignment")] + public ulong SamplerDescriptorAlignment; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "imageDescriptorAlignment")] + public ulong ImageDescriptorAlignment; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "bufferDescriptorAlignment")] + public ulong BufferDescriptorAlignment; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "maxPushDataSize")] + public ulong MaxPushDataSize; + + [NativeName("Type", "size_t")] + [NativeName("Type.Name", "size_t")] + [NativeName("Name", "imageCaptureReplayOpaqueDataSize")] + public nuint ImageCaptureReplayOpaqueDataSize; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "maxDescriptorHeapEmbeddedSamplers")] + public uint MaxDescriptorHeapEmbeddedSamplers; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "samplerYcbcrConversionCount")] + public uint SamplerYcbcrConversionCount; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "sparseDescriptorHeaps")] + public Bool32 SparseDescriptorHeaps; + + [NativeName("Type", "VkBool32")] + [NativeName("Type.Name", "VkBool32")] + [NativeName("Name", "protectedDescriptorHeaps")] + public Bool32 ProtectedDescriptorHeaps; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.PhysicalDeviceDescriptorHeapPropertiesExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkCommandBufferInheritanceDescriptorHeapInfoEXT")] + public unsafe partial struct CommandBufferInheritanceDescriptorHeapInfoEXT : IExtendsChain + { + public CommandBufferInheritanceDescriptorHeapInfoEXT(StructureType? sType = null, void* pNext = null, BindHeapInfoEXT* pSamplerHeapBindInfo = null, BindHeapInfoEXT* pResourceHeapBindInfo = null) + : this() + { + SType = sType ?? StructureType.CommandBufferInheritanceDescriptorHeapInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (pSamplerHeapBindInfo is not null) + { + PSamplerHeapBindInfo = pSamplerHeapBindInfo; + } + + if (pResourceHeapBindInfo is not null) + { + PResourceHeapBindInfo = pResourceHeapBindInfo; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkBindHeapInfoEXT*")] + [NativeName("Type.Name", "VkBindHeapInfoEXT")] + [NativeName("Name", "pSamplerHeapBindInfo")] + public BindHeapInfoEXT* PSamplerHeapBindInfo; + + [NativeName("Type", "VkBindHeapInfoEXT*")] + [NativeName("Type.Name", "VkBindHeapInfoEXT")] + [NativeName("Name", "pResourceHeapBindInfo")] + public BindHeapInfoEXT* PResourceHeapBindInfo; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.CommandBufferInheritanceDescriptorHeapInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkSamplerCustomBorderColorIndexCreateInfoEXT")] + public unsafe partial struct SamplerCustomBorderColorIndexCreateInfoEXT : IExtendsChain + { + public SamplerCustomBorderColorIndexCreateInfoEXT(StructureType? sType = null, void* pNext = null, uint? index = null) + : this() + { + SType = sType ?? StructureType.SamplerCustomBorderColorIndexCreateInfoExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (index is not null) + { + Index = index.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "index")] + public uint Index; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.SamplerCustomBorderColorIndexCreateInfoExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkPhysicalDeviceDescriptorHeapTensorPropertiesARM")] + public unsafe partial struct PhysicalDeviceDescriptorHeapTensorPropertiesARM : IExtendsChain, IExtendsChain + { + public PhysicalDeviceDescriptorHeapTensorPropertiesARM(StructureType? sType = null, void* pNext = null, ulong? tensorDescriptorSize = null, ulong? tensorDescriptorAlignment = null, nuint? tensorCaptureReplayOpaqueDataSize = null) + : this() + { + SType = sType ?? StructureType.PhysicalDeviceDescriptorHeapTensorPropertiesArm(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (tensorDescriptorSize is not null) + { + TensorDescriptorSize = tensorDescriptorSize.Value; + } + + if (tensorDescriptorAlignment is not null) + { + TensorDescriptorAlignment = tensorDescriptorAlignment.Value; + } + + if (tensorCaptureReplayOpaqueDataSize is not null) + { + TensorCaptureReplayOpaqueDataSize = tensorCaptureReplayOpaqueDataSize.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "tensorDescriptorSize")] + public ulong TensorDescriptorSize; + + [NativeName("Type", "VkDeviceSize")] + [NativeName("Type.Name", "VkDeviceSize")] + [NativeName("Name", "tensorDescriptorAlignment")] + public ulong TensorDescriptorAlignment; + + [NativeName("Type", "size_t")] + [NativeName("Type.Name", "size_t")] + [NativeName("Name", "tensorCaptureReplayOpaqueDataSize")] + public nuint TensorCaptureReplayOpaqueDataSize; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.PhysicalDeviceDescriptorHeapTensorPropertiesArm(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } + + [NativeName("Name", "VkSubsampledImageFormatPropertiesEXT")] + public unsafe partial struct SubsampledImageFormatPropertiesEXT : IExtendsChain, IExtendsChain + { + public SubsampledImageFormatPropertiesEXT(StructureType? sType = null, void* pNext = null, uint? subsampledImageDescriptorCount = null) + : this() + { + SType = sType ?? StructureType.SubsampledImageFormatPropertiesExt(); + + if (pNext is not null) + { + PNext = pNext; + } + + if (subsampledImageDescriptorCount is not null) + { + SubsampledImageDescriptorCount = subsampledImageDescriptorCount.Value; + } + } + + [NativeName("Type", "VkStructureType")] + [NativeName("Type.Name", "VkStructureType")] + [NativeName("Name", "sType")] + public StructureType SType; + + [NativeName("Type", "void*")] + [NativeName("Type.Name", "void")] + [NativeName("Name", "pNext")] + public void* PNext; + + [NativeName("Type", "uint32_t")] + [NativeName("Type.Name", "uint32_t")] + [NativeName("Name", "subsampledImageDescriptorCount")] + public uint SubsampledImageDescriptorCount; + + StructureType IStructuredType.StructureType() + { + return SType = StructureType.SubsampledImageFormatPropertiesExt(); + } + + BaseInStructure* IChainable.PNext + { + get => (BaseInStructure*) PNext; + set => PNext = value; + } + } +} \ No newline at end of file From 0c4e0fb56822149a4f99dbbc39acbf4cce56b6b1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 23 Jun 2026 19:30:57 +0800 Subject: [PATCH 286/466] Style: remove spaces in casts and assignments Refactored ExtDescriptorHeap.cs to eliminate unnecessary spaces in type casts and assignments, improving code consistency and style. Added auto-generated file comment and disabled warnings. No functional changes. --- .../Zenith.NET.Vulkan/ExtDescriptorHeap.cs | 155 +++++++++--------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs index 9cd76607..b5d5d3c4 100644 --- a/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// +#pragma warning disable + +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Silk.NET.Core; using Silk.NET.Core.Attributes; @@ -18,7 +21,7 @@ public unsafe partial class ExtDescriptorHeap : NativeExtension [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] public unsafe void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) { - ((delegate* unmanaged[Stdcall]) VTable.CmdBindResourceHeap)(commandBuffer, pBindInfo); + ((delegate* unmanaged[Stdcall])VTable.CmdBindResourceHeap)(commandBuffer, pBindInfo); } /// To be documented. @@ -35,7 +38,7 @@ public void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] public unsafe void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) { - ((delegate* unmanaged[Stdcall]) VTable.CmdBindSamplerHeap)(commandBuffer, pBindInfo); + ((delegate* unmanaged[Stdcall])VTable.CmdBindSamplerHeap)(commandBuffer, pBindInfo); } /// To be documented. @@ -52,7 +55,7 @@ public void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [ [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] public unsafe void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] PushDataInfoEXT* pPushDataInfo) { - ((delegate* unmanaged[Stdcall]) VTable.CmdPushData)(commandBuffer, pPushDataInfo); + ((delegate* unmanaged[Stdcall])VTable.CmdPushData)(commandBuffer, pPushDataInfo); } /// To be documented. @@ -69,7 +72,7 @@ public void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(C [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) { - return ((delegate* unmanaged[Stdcall]) VTable.GetImageOpaqueCaptureData)(device, imageCount, pImages, pDatas); + return ((delegate* unmanaged[Stdcall])VTable.GetImageOpaqueCaptureData)(device, imageCount, pImages, pDatas); } /// To be documented. @@ -111,14 +114,14 @@ public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count [NativeApi(EntryPoint = "vkGetPhysicalDeviceDescriptorSizeEXT", Convention = CallingConvention.Winapi)] public ulong GetPhysicalDeviceDescriptorSize([Count(Count = 0)] PhysicalDevice physicalDevice, [Count(Count = 0)] DescriptorType descriptorType) { - return ((delegate* unmanaged[Stdcall]) VTable.GetPhysicalDeviceDescriptorSize)(physicalDevice, descriptorType); + return ((delegate* unmanaged[Stdcall])VTable.GetPhysicalDeviceDescriptorSize)(physicalDevice, descriptorType); } /// To be documented. [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) { - return ((delegate* unmanaged[Stdcall]) VTable.GetTensorOpaqueCaptureData)(device, tensorCount, pTensors, pDatas); + return ((delegate* unmanaged[Stdcall])VTable.GetTensorOpaqueCaptureData)(device, tensorCount, pTensors, pDatas); } /// To be documented. @@ -160,7 +163,7 @@ public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Coun [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) { - return ((delegate* unmanaged[Stdcall]) VTable.RegisterCustomBorderColor)(device, pBorderColor, requestIndex, pIndex); + return ((delegate* unmanaged[Stdcall])VTable.RegisterCustomBorderColor)(device, pBorderColor, requestIndex, pIndex); } /// To be documented. @@ -198,14 +201,14 @@ public Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count [NativeApi(EntryPoint = "vkUnregisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] public void UnregisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0)] uint index) { - ((delegate* unmanaged[Stdcall]) VTable.UnregisterCustomBorderColor)(device, index); + ((delegate* unmanaged[Stdcall])VTable.UnregisterCustomBorderColor)(device, index); } /// To be documented. [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) { - return ((delegate* unmanaged[Stdcall]) VTable.WriteResourceDescriptors)(device, resourceCount, pResources, pDescriptors); + return ((delegate* unmanaged[Stdcall])VTable.WriteResourceDescriptors)(device, resourceCount, pResources, pDescriptors); } /// To be documented. @@ -243,7 +246,7 @@ public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count( [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) { - return ((delegate* unmanaged[Stdcall]) VTable.WriteSamplerDescriptors)(device, samplerCount, pSamplers, pDescriptors); + return ((delegate* unmanaged[Stdcall])VTable.WriteSamplerDescriptors)(device, samplerCount, pSamplers, pDescriptors); } /// To be documented. @@ -280,73 +283,73 @@ public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(C /// To be documented. public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) { - return GetImageOpaqueCaptureData(device, (uint) pDatas.Length, pImages, out pDatas.GetPinnableReference()); + return GetImageOpaqueCaptureData(device, (uint)pDatas.Length, pImages, out pDatas.GetPinnableReference()); } /// To be documented. public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) { - return GetImageOpaqueCaptureData(device, (uint) pImages.Length, in pImages.GetPinnableReference(), pDatas); + return GetImageOpaqueCaptureData(device, (uint)pImages.Length, in pImages.GetPinnableReference(), pDatas); } /// To be documented. public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) { - return GetImageOpaqueCaptureData(device, (uint) pDatas.Length, in pImages.GetPinnableReference(), out pDatas.GetPinnableReference()); + return GetImageOpaqueCaptureData(device, (uint)pDatas.Length, in pImages.GetPinnableReference(), out pDatas.GetPinnableReference()); } /// To be documented. public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) { - return GetTensorOpaqueCaptureData(device, (uint) pDatas.Length, pTensors, out pDatas.GetPinnableReference()); + return GetTensorOpaqueCaptureData(device, (uint)pDatas.Length, pTensors, out pDatas.GetPinnableReference()); } /// To be documented. public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) { - return GetTensorOpaqueCaptureData(device, (uint) pTensors.Length, in pTensors.GetPinnableReference(), pDatas); + return GetTensorOpaqueCaptureData(device, (uint)pTensors.Length, in pTensors.GetPinnableReference(), pDatas); } /// To be documented. public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) { - return GetTensorOpaqueCaptureData(device, (uint) pDatas.Length, in pTensors.GetPinnableReference(), out pDatas.GetPinnableReference()); + return GetTensorOpaqueCaptureData(device, (uint)pDatas.Length, in pTensors.GetPinnableReference(), out pDatas.GetPinnableReference()); } /// To be documented. public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) { - return WriteResourceDescriptors(device, (uint) pDescriptors.Length, pResources, in pDescriptors.GetPinnableReference()); + return WriteResourceDescriptors(device, (uint)pDescriptors.Length, pResources, in pDescriptors.GetPinnableReference()); } /// To be documented. public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) { - return WriteResourceDescriptors(device, (uint) pResources.Length, in pResources.GetPinnableReference(), pDescriptors); + return WriteResourceDescriptors(device, (uint)pResources.Length, in pResources.GetPinnableReference(), pDescriptors); } /// To be documented. public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) { - return WriteResourceDescriptors(device, (uint) pDescriptors.Length, in pResources.GetPinnableReference(), in pDescriptors.GetPinnableReference()); + return WriteResourceDescriptors(device, (uint)pDescriptors.Length, in pResources.GetPinnableReference(), in pDescriptors.GetPinnableReference()); } /// To be documented. public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) { - return WriteSamplerDescriptors(device, (uint) pDescriptors.Length, pSamplers, in pDescriptors.GetPinnableReference()); + return WriteSamplerDescriptors(device, (uint)pDescriptors.Length, pSamplers, in pDescriptors.GetPinnableReference()); } /// To be documented. public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) { - return WriteSamplerDescriptors(device, (uint) pSamplers.Length, in pSamplers.GetPinnableReference(), pDescriptors); + return WriteSamplerDescriptors(device, (uint)pSamplers.Length, in pSamplers.GetPinnableReference(), pDescriptors); } /// To be documented. public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) { - return WriteSamplerDescriptors(device, (uint) pDescriptors.Length, in pSamplers.GetPinnableReference(), in pDescriptors.GetPinnableReference()); + return WriteSamplerDescriptors(device, (uint)pDescriptors.Length, in pSamplers.GetPinnableReference(), in pDescriptors.GetPinnableReference()); } public ExtDescriptorHeap(INativeContext ctx) @@ -359,7 +362,7 @@ protected override IVTable CreateVTable() return new GeneratedVTable(_ctx); } - private GeneratedVTable VTable => (GeneratedVTable) CurrentVTable; + private GeneratedVTable VTable => (GeneratedVTable)CurrentVTable; private sealed class GeneratedVTable(INativeContext ctx) : IVTable, IDisposable { @@ -431,77 +434,77 @@ public static class ExtDescriptorHeapStructureTypeExtensions { public static StructureType TexelBufferDescriptorInfoExt() { - return (StructureType) 1000135000; + return (StructureType)1000135000; } public static StructureType ImageDescriptorInfoExt() { - return (StructureType) 1000135001; + return (StructureType)1000135001; } public static StructureType ResourceDescriptorInfoExt() { - return (StructureType) 1000135002; + return (StructureType)1000135002; } public static StructureType BindHeapInfoExt() { - return (StructureType) 1000135003; + return (StructureType)1000135003; } public static StructureType PushDataInfoExt() { - return (StructureType) 1000135004; + return (StructureType)1000135004; } public static StructureType DescriptorSetAndBindingMappingExt() { - return (StructureType) 1000135005; + return (StructureType)1000135005; } public static StructureType ShaderDescriptorSetAndBindingMappingInfoExt() { - return (StructureType) 1000135006; + return (StructureType)1000135006; } public static StructureType OpaqueCaptureDataCreateInfoExt() { - return (StructureType) 1000135007; + return (StructureType)1000135007; } public static StructureType PhysicalDeviceDescriptorHeapPropertiesExt() { - return (StructureType) 1000135008; + return (StructureType)1000135008; } public static StructureType PhysicalDeviceDescriptorHeapFeaturesExt() { - return (StructureType) 1000135009; + return (StructureType)1000135009; } public static StructureType CommandBufferInheritanceDescriptorHeapInfoExt() { - return (StructureType) 1000135010; + return (StructureType)1000135010; } public static StructureType SamplerCustomBorderColorIndexCreateInfoExt() { - return (StructureType) 1000135011; + return (StructureType)1000135011; } public static StructureType IndirectCommandsLayoutPushDataTokenNV() { - return (StructureType) 1000135012; + return (StructureType)1000135012; } public static StructureType SubsampledImageFormatPropertiesExt() { - return (StructureType) 1000135013; + return (StructureType)1000135013; } public static StructureType PhysicalDeviceDescriptorHeapTensorPropertiesArm() { - return (StructureType) 1000135014; + return (StructureType)1000135014; } } } @@ -514,26 +517,26 @@ public static class ExtDescriptorHeapAccessFlags2Extensions [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] public static AccessFlags2 Access2SamplerHeapReadBitExt() { - return (AccessFlags2) 144115188075855872L; + return (AccessFlags2)144115188075855872L; } [Obsolete("Deprecated in favour of \"ResourceHeapReadBitExt\"")] [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] public static AccessFlags2 Access2ResourceHeapReadBitExt() { - return (AccessFlags2) 288230376151711744L; + return (AccessFlags2)288230376151711744L; } [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] public static AccessFlags2 SamplerHeapReadBitExt() { - return (AccessFlags2) 144115188075855872L; + return (AccessFlags2)144115188075855872L; } [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] public static AccessFlags2 ResourceHeapReadBitExt() { - return (AccessFlags2) 288230376151711744L; + return (AccessFlags2)288230376151711744L; } } } @@ -546,13 +549,13 @@ public static class ExtDescriptorHeapBufferUsageFlagsExtensions [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] public static BufferUsageFlags BufferUsageDescriptorHeapBitExt() { - return (BufferUsageFlags) 268435456; + return (BufferUsageFlags)268435456; } [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] public static BufferUsageFlags DescriptorHeapBitExt() { - return (BufferUsageFlags) 268435456; + return (BufferUsageFlags)268435456; } } } @@ -565,13 +568,13 @@ public static class ExtDescriptorHeapBufferUsageFlags2Extensions [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] public static BufferUsageFlags2 BufferUsage2DescriptorHeapBitExt() { - return (BufferUsageFlags2) 268435456; + return (BufferUsageFlags2)268435456; } [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] public static BufferUsageFlags2 DescriptorHeapBitExt() { - return (BufferUsageFlags2) 268435456; + return (BufferUsageFlags2)268435456; } } } @@ -584,13 +587,13 @@ public static class ExtDescriptorHeapPipelineCreateFlags2Extensions [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] public static PipelineCreateFlags2 PipelineCreate2DescriptorHeapBitExt() { - return (PipelineCreateFlags2) 68719476736L; + return (PipelineCreateFlags2)68719476736L; } [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] public static PipelineCreateFlags2 Vk2DescriptorHeapBitExt() { - return (PipelineCreateFlags2) 68719476736L; + return (PipelineCreateFlags2)68719476736L; } } } @@ -603,13 +606,13 @@ public static class ExtDescriptorHeapImageCreateFlagsExtensions [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] public static ImageCreateFlags ImageCreateDescriptorHeapCaptureReplayBitExt() { - return (ImageCreateFlags) 65536; + return (ImageCreateFlags)65536; } [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] public static ImageCreateFlags CreateDescriptorHeapCaptureReplayBitExt() { - return (ImageCreateFlags) 65536; + return (ImageCreateFlags)65536; } } } @@ -622,13 +625,13 @@ public static class ExtDescriptorHeapShaderCreateFlagsExtExtensions [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] public static ShaderCreateFlagsEXT ShaderCreateDescriptorHeapBitExt() { - return (ShaderCreateFlagsEXT) 1024; + return (ShaderCreateFlagsEXT)1024; } [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] public static ShaderCreateFlagsEXT CreateDescriptorHeapBitExt() { - return (ShaderCreateFlagsEXT) 1024; + return (ShaderCreateFlagsEXT)1024; } } } @@ -641,13 +644,13 @@ public static class ExtDescriptorHeapTensorCreateFlagsArmExtensions [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] public static TensorCreateFlagsARM TensorCreateDescriptorHeapCaptureReplayBitArm() { - return (TensorCreateFlagsARM) 8; + return (TensorCreateFlagsARM)8; } [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] public static TensorCreateFlagsARM DescriptorHeapCaptureReplayBitArm() { - return (TensorCreateFlagsARM) 8; + return (TensorCreateFlagsARM)8; } } } @@ -660,26 +663,26 @@ public static class ExtDescriptorHeapIndirectCommandsTokenTypeExtExtensions [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataExt() { - return (IndirectCommandsTokenTypeEXT) 1000135000; + return (IndirectCommandsTokenTypeEXT)1000135000; } [Obsolete("Deprecated in favour of \"PushDataSequenceIndexExt\"")] [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataSequenceIndexExt() { - return (IndirectCommandsTokenTypeEXT) 1000135001; + return (IndirectCommandsTokenTypeEXT)1000135001; } [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] public static IndirectCommandsTokenTypeEXT PushDataExt() { - return (IndirectCommandsTokenTypeEXT) 1000135000; + return (IndirectCommandsTokenTypeEXT)1000135000; } [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] public static IndirectCommandsTokenTypeEXT PushDataSequenceIndexExt() { - return (IndirectCommandsTokenTypeEXT) 1000135001; + return (IndirectCommandsTokenTypeEXT)1000135001; } } } @@ -692,13 +695,13 @@ public static class ExtDescriptorHeapIndirectCommandsTokenTypeNvExtensions [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] public static IndirectCommandsTokenTypeNV IndirectCommandsTokenTypePushDataNV() { - return (IndirectCommandsTokenTypeNV) 1000135000; + return (IndirectCommandsTokenTypeNV)1000135000; } [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] public static IndirectCommandsTokenTypeNV PushDataNV() { - return (IndirectCommandsTokenTypeNV) 1000135000; + return (IndirectCommandsTokenTypeNV)1000135000; } } } @@ -962,7 +965,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -1018,7 +1021,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -1127,7 +1130,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } @@ -1199,7 +1202,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -1255,7 +1258,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } @@ -1317,7 +1320,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2109,7 +2112,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } @@ -2171,7 +2174,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2217,7 +2220,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2273,7 +2276,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2499,7 +2502,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2555,7 +2558,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2601,7 +2604,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2667,7 +2670,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } @@ -2713,7 +2716,7 @@ StructureType IStructuredType.StructureType() BaseInStructure* IChainable.PNext { - get => (BaseInStructure*) PNext; + get => (BaseInStructure*)PNext; set => PNext = value; } } From 13fdc3e6edaa5cb9f1f987c494d6caf21138c3c3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 23 Jun 2026 19:35:44 +0800 Subject: [PATCH 287/466] Restore VK_EXT_descriptor_heap native API bindings Refactored VK_EXT_descriptor_heap to reinstate the ExtDescriptorHeap NativeExtension class with all native API method bindings and VTable logic. Added overloads supporting Span/ReadOnlySpan for improved .NET interop. Moved static enum extension classes to the main Vulkan namespace and placed ExtDescriptorHeap in the correct Extensions.EXT namespace. Restores full native interop for the extension. --- .../Zenith.NET.Vulkan/ExtDescriptorHeap.cs | 1160 ++++++++--------- 1 file changed, 580 insertions(+), 580 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs index b5d5d3c4..af13bef4 100644 --- a/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs @@ -9,697 +9,280 @@ using Silk.NET.Core.Native; using Extension = Silk.NET.Core.Attributes.ExtensionAttribute; -namespace Silk.NET.Vulkan.Extensions.EXT +namespace Silk.NET.Vulkan { - [Extension("VK_EXT_descriptor_heap")] - public unsafe partial class ExtDescriptorHeap : NativeExtension + public static class ExtDescriptorHeapStructureTypeExtensions { - public const uint SpecVersion = 1; - public const string ExtensionName = "VK_EXT_descriptor_heap"; - - /// To be documented. - [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] - public unsafe void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) - { - ((delegate* unmanaged[Stdcall])VTable.CmdBindResourceHeap)(commandBuffer, pBindInfo); - } - - /// To be documented. - [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] - public void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly BindHeapInfoEXT pBindInfo) + extension(StructureType) { - fixed (BindHeapInfoEXT* pBindInfoPtr = &pBindInfo) + public static StructureType TexelBufferDescriptorInfoExt() { - CmdBindResourceHeap(commandBuffer, pBindInfoPtr); + return (StructureType)1000135000; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] - public unsafe void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) - { - ((delegate* unmanaged[Stdcall])VTable.CmdBindSamplerHeap)(commandBuffer, pBindInfo); - } - - /// To be documented. - [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] - public void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly BindHeapInfoEXT pBindInfo) - { - fixed (BindHeapInfoEXT* pBindInfoPtr = &pBindInfo) + public static StructureType ImageDescriptorInfoExt() { - CmdBindSamplerHeap(commandBuffer, pBindInfoPtr); + return (StructureType)1000135001; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] - public unsafe void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] PushDataInfoEXT* pPushDataInfo) - { - ((delegate* unmanaged[Stdcall])VTable.CmdPushData)(commandBuffer, pPushDataInfo); - } - - /// To be documented. - [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] - public void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly PushDataInfoEXT pPushDataInfo) - { - fixed (PushDataInfoEXT* pPushDataInfoPtr = &pPushDataInfo) + public static StructureType ResourceDescriptorInfoExt() { - CmdPushData(commandBuffer, pPushDataInfoPtr); + return (StructureType)1000135002; } - } - - /// To be documented. - [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] - public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) - { - return ((delegate* unmanaged[Stdcall])VTable.GetImageOpaqueCaptureData)(device, imageCount, pImages, pDatas); - } - - /// To be documented. - [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] - public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) - { - pDatas = default; - fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + public static StructureType BindHeapInfoExt() { - return GetImageOpaqueCaptureData(device, imageCount, pImages, pDatasPtr); + return (StructureType)1000135003; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] - public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ref readonly Image pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) - { - fixed (Image* pImagesPtr = &pImages) + public static StructureType PushDataInfoExt() { - return GetImageOpaqueCaptureData(device, imageCount, pImagesPtr, pDatas); + return (StructureType)1000135004; } - } - - /// To be documented. - [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] - public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ref readonly Image pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) - { - pDatas = default; - fixed (Image* pImagesPtr = &pImages) - fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + public static StructureType DescriptorSetAndBindingMappingExt() { - return GetImageOpaqueCaptureData(device, imageCount, pImagesPtr, pDatasPtr); + return (StructureType)1000135005; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkGetPhysicalDeviceDescriptorSizeEXT", Convention = CallingConvention.Winapi)] - public ulong GetPhysicalDeviceDescriptorSize([Count(Count = 0)] PhysicalDevice physicalDevice, [Count(Count = 0)] DescriptorType descriptorType) - { - return ((delegate* unmanaged[Stdcall])VTable.GetPhysicalDeviceDescriptorSize)(physicalDevice, descriptorType); - } + public static StructureType ShaderDescriptorSetAndBindingMappingInfoExt() + { + return (StructureType)1000135006; + } - /// To be documented. - [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] - public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) - { - return ((delegate* unmanaged[Stdcall])VTable.GetTensorOpaqueCaptureData)(device, tensorCount, pTensors, pDatas); - } + public static StructureType OpaqueCaptureDataCreateInfoExt() + { + return (StructureType)1000135007; + } - /// To be documented. - [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] - public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) - { - pDatas = default; + public static StructureType PhysicalDeviceDescriptorHeapPropertiesExt() + { + return (StructureType)1000135008; + } - fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + public static StructureType PhysicalDeviceDescriptorHeapFeaturesExt() { - return GetTensorOpaqueCaptureData(device, tensorCount, pTensors, pDatasPtr); + return (StructureType)1000135009; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] - public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ref readonly TensorARM pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) - { - fixed (TensorARM* pTensorsPtr = &pTensors) + public static StructureType CommandBufferInheritanceDescriptorHeapInfoExt() { - return GetTensorOpaqueCaptureData(device, tensorCount, pTensorsPtr, pDatas); + return (StructureType)1000135010; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] - public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ref readonly TensorARM pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) - { - pDatas = default; + public static StructureType SamplerCustomBorderColorIndexCreateInfoExt() + { + return (StructureType)1000135011; + } - fixed (TensorARM* pTensorsPtr = &pTensors) - fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + public static StructureType IndirectCommandsLayoutPushDataTokenNV() { - return GetTensorOpaqueCaptureData(device, tensorCount, pTensorsPtr, pDatasPtr); + return (StructureType)1000135012; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] - public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) - { - return ((delegate* unmanaged[Stdcall])VTable.RegisterCustomBorderColor)(device, pBorderColor, requestIndex, pIndex); - } + public static StructureType SubsampledImageFormatPropertiesExt() + { + return (StructureType)1000135013; + } - /// To be documented. - [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] - public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] ref uint pIndex) - { - fixed (uint* pIndexPtr = &pIndex) + public static StructureType PhysicalDeviceDescriptorHeapTensorPropertiesArm() { - return RegisterCustomBorderColor(device, pBorderColor, requestIndex, pIndexPtr); + return (StructureType)1000135014; } } + } - /// To be documented. - [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] - public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly SamplerCustomBorderColorCreateInfoEXT pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) + public static class ExtDescriptorHeapAccessFlags2Extensions + { + extension(AccessFlags2) { - fixed (SamplerCustomBorderColorCreateInfoEXT* pBorderColorPtr = &pBorderColor) + [Obsolete("Deprecated in favour of \"SamplerHeapReadBitExt\"")] + [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] + public static AccessFlags2 Access2SamplerHeapReadBitExt() { - return RegisterCustomBorderColor(device, pBorderColorPtr, requestIndex, pIndex); + return (AccessFlags2)144115188075855872L; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] - public Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly SamplerCustomBorderColorCreateInfoEXT pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] ref uint pIndex) - { - fixed (SamplerCustomBorderColorCreateInfoEXT* pBorderColorPtr = &pBorderColor) - fixed (uint* pIndexPtr = &pIndex) + [Obsolete("Deprecated in favour of \"ResourceHeapReadBitExt\"")] + [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] + public static AccessFlags2 Access2ResourceHeapReadBitExt() { - return RegisterCustomBorderColor(device, pBorderColorPtr, requestIndex, pIndexPtr); + return (AccessFlags2)288230376151711744L; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkUnregisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] - public void UnregisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0)] uint index) - { - ((delegate* unmanaged[Stdcall])VTable.UnregisterCustomBorderColor)(device, index); - } + [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] + public static AccessFlags2 SamplerHeapReadBitExt() + { + return (AccessFlags2)144115188075855872L; + } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] - public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) - { - return ((delegate* unmanaged[Stdcall])VTable.WriteResourceDescriptors)(device, resourceCount, pResources, pDescriptors); + [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] + public static AccessFlags2 ResourceHeapReadBitExt() + { + return (AccessFlags2)288230376151711744L; + } } + } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] - public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + public static class ExtDescriptorHeapBufferUsageFlagsExtensions + { + extension(BufferUsageFlags) { - fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + [Obsolete("Deprecated in favour of \"DescriptorHeapBitExt\"")] + [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags BufferUsageDescriptorHeapBitExt() { - return WriteResourceDescriptors(device, resourceCount, pResources, pDescriptorsPtr); + return (BufferUsageFlags)268435456; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] - public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly ResourceDescriptorInfoEXT pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) - { - fixed (ResourceDescriptorInfoEXT* pResourcesPtr = &pResources) + [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags DescriptorHeapBitExt() { - return WriteResourceDescriptors(device, resourceCount, pResourcesPtr, pDescriptors); + return (BufferUsageFlags)268435456; } } + } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] - public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly ResourceDescriptorInfoEXT pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + public static class ExtDescriptorHeapBufferUsageFlags2Extensions + { + extension(BufferUsageFlags2) { - fixed (ResourceDescriptorInfoEXT* pResourcesPtr = &pResources) - fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + [Obsolete("Deprecated in favour of \"DescriptorHeapBitExt\"")] + [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags2 BufferUsage2DescriptorHeapBitExt() { - return WriteResourceDescriptors(device, resourceCount, pResourcesPtr, pDescriptorsPtr); + return (BufferUsageFlags2)268435456; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] - public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) - { - return ((delegate* unmanaged[Stdcall])VTable.WriteSamplerDescriptors)(device, samplerCount, pSamplers, pDescriptors); - } - - /// To be documented. - [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] - public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) - { - fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static BufferUsageFlags2 DescriptorHeapBitExt() { - return WriteSamplerDescriptors(device, samplerCount, pSamplers, pDescriptorsPtr); + return (BufferUsageFlags2)268435456; } } + } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] - public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly SamplerCreateInfo pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + public static class ExtDescriptorHeapPipelineCreateFlags2Extensions + { + extension(PipelineCreateFlags2) { - fixed (SamplerCreateInfo* pSamplersPtr = &pSamplers) + [Obsolete("Deprecated in favour of \"Vk2DescriptorHeapBitExt\"")] + [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static PipelineCreateFlags2 PipelineCreate2DescriptorHeapBitExt() { - return WriteSamplerDescriptors(device, samplerCount, pSamplersPtr, pDescriptors); + return (PipelineCreateFlags2)68719476736L; } - } - /// To be documented. - [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] - public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly SamplerCreateInfo pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) - { - fixed (SamplerCreateInfo* pSamplersPtr = &pSamplers) - fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] + public static PipelineCreateFlags2 Vk2DescriptorHeapBitExt() { - return WriteSamplerDescriptors(device, samplerCount, pSamplersPtr, pDescriptorsPtr); + return (PipelineCreateFlags2)68719476736L; } } + } - /// To be documented. - public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) - { - return GetImageOpaqueCaptureData(device, (uint)pDatas.Length, pImages, out pDatas.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) - { - return GetImageOpaqueCaptureData(device, (uint)pImages.Length, in pImages.GetPinnableReference(), pDatas); - } - - /// To be documented. - public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) - { - return GetImageOpaqueCaptureData(device, (uint)pDatas.Length, in pImages.GetPinnableReference(), out pDatas.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) - { - return GetTensorOpaqueCaptureData(device, (uint)pDatas.Length, pTensors, out pDatas.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) - { - return GetTensorOpaqueCaptureData(device, (uint)pTensors.Length, in pTensors.GetPinnableReference(), pDatas); - } - - /// To be documented. - public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) - { - return GetTensorOpaqueCaptureData(device, (uint)pDatas.Length, in pTensors.GetPinnableReference(), out pDatas.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) - { - return WriteResourceDescriptors(device, (uint)pDescriptors.Length, pResources, in pDescriptors.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) - { - return WriteResourceDescriptors(device, (uint)pResources.Length, in pResources.GetPinnableReference(), pDescriptors); - } - - /// To be documented. - public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) - { - return WriteResourceDescriptors(device, (uint)pDescriptors.Length, in pResources.GetPinnableReference(), in pDescriptors.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) - { - return WriteSamplerDescriptors(device, (uint)pDescriptors.Length, pSamplers, in pDescriptors.GetPinnableReference()); - } - - /// To be documented. - public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) - { - return WriteSamplerDescriptors(device, (uint)pSamplers.Length, in pSamplers.GetPinnableReference(), pDescriptors); - } - - /// To be documented. - public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) - { - return WriteSamplerDescriptors(device, (uint)pDescriptors.Length, in pSamplers.GetPinnableReference(), in pDescriptors.GetPinnableReference()); - } - - public ExtDescriptorHeap(INativeContext ctx) - : base(ctx) - { - } - - protected override IVTable CreateVTable() - { - return new GeneratedVTable(_ctx); - } - - private GeneratedVTable VTable => (GeneratedVTable)CurrentVTable; - - private sealed class GeneratedVTable(INativeContext ctx) : IVTable, IDisposable + public static class ExtDescriptorHeapImageCreateFlagsExtensions + { + extension(ImageCreateFlags) { - private readonly INativeContext ctx = ctx; - private nint cmdBindResourceHeap; - private nint cmdBindSamplerHeap; - private nint cmdPushData; - private nint getImageOpaqueCaptureData; - private nint getPhysicalDeviceDescriptorSize; - private nint getTensorOpaqueCaptureData; - private nint registerCustomBorderColor; - private nint unregisterCustomBorderColor; - private nint writeResourceDescriptors; - private nint writeSamplerDescriptors; - - public nint CmdBindResourceHeap => cmdBindResourceHeap is 0 ? cmdBindResourceHeap = Load("vkCmdBindResourceHeapEXT") : cmdBindResourceHeap; - public nint CmdBindSamplerHeap => cmdBindSamplerHeap is 0 ? cmdBindSamplerHeap = Load("vkCmdBindSamplerHeapEXT") : cmdBindSamplerHeap; - public nint CmdPushData => cmdPushData is 0 ? cmdPushData = Load("vkCmdPushDataEXT") : cmdPushData; - public nint GetImageOpaqueCaptureData => getImageOpaqueCaptureData is 0 ? getImageOpaqueCaptureData = Load("vkGetImageOpaqueCaptureDataEXT") : getImageOpaqueCaptureData; - public nint GetPhysicalDeviceDescriptorSize => getPhysicalDeviceDescriptorSize is 0 ? getPhysicalDeviceDescriptorSize = Load("vkGetPhysicalDeviceDescriptorSizeEXT") : getPhysicalDeviceDescriptorSize; - public nint GetTensorOpaqueCaptureData => getTensorOpaqueCaptureData is 0 ? getTensorOpaqueCaptureData = Load("vkGetTensorOpaqueCaptureDataARM") : getTensorOpaqueCaptureData; - public nint RegisterCustomBorderColor => registerCustomBorderColor is 0 ? registerCustomBorderColor = Load("vkRegisterCustomBorderColorEXT") : registerCustomBorderColor; - public nint UnregisterCustomBorderColor => unregisterCustomBorderColor is 0 ? unregisterCustomBorderColor = Load("vkUnregisterCustomBorderColorEXT") : unregisterCustomBorderColor; - public nint WriteResourceDescriptors => writeResourceDescriptors is 0 ? writeResourceDescriptors = Load("vkWriteResourceDescriptorsEXT") : writeResourceDescriptors; - public nint WriteSamplerDescriptors => writeSamplerDescriptors is 0 ? writeSamplerDescriptors = Load("vkWriteSamplerDescriptorsEXT") : writeSamplerDescriptors; - - public nint Load(int slot, string name) - { - return Load(name); - } - - public nint Load(string name) + [Obsolete("Deprecated in favour of \"CreateDescriptorHeapCaptureReplayBitExt\"")] + [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] + public static ImageCreateFlags ImageCreateDescriptorHeapCaptureReplayBitExt() { - return ctx.GetProcAddress(name, null); + return (ImageCreateFlags)65536; } - public IVTable Clone() + [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] + public static ImageCreateFlags CreateDescriptorHeapCaptureReplayBitExt() { - return new GeneratedVTable(ctx); + return (ImageCreateFlags)65536; } + } + } - public void Purge() + public static class ExtDescriptorHeapShaderCreateFlagsExtExtensions + { + extension(ShaderCreateFlagsEXT) + { + [Obsolete("Deprecated in favour of \"CreateDescriptorHeapBitExt\"")] + [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] + public static ShaderCreateFlagsEXT ShaderCreateDescriptorHeapBitExt() { - cmdBindResourceHeap = 0; - cmdBindSamplerHeap = 0; - cmdPushData = 0; - getImageOpaqueCaptureData = 0; - getPhysicalDeviceDescriptorSize = 0; - getTensorOpaqueCaptureData = 0; - registerCustomBorderColor = 0; - unregisterCustomBorderColor = 0; - writeResourceDescriptors = 0; - writeSamplerDescriptors = 0; + return (ShaderCreateFlagsEXT)1024; } - public void Dispose() + [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] + public static ShaderCreateFlagsEXT CreateDescriptorHeapBitExt() { - Purge(); + return (ShaderCreateFlagsEXT)1024; } } } -} -namespace Silk.NET.Vulkan -{ - public static class ExtDescriptorHeapStructureTypeExtensions + public static class ExtDescriptorHeapTensorCreateFlagsArmExtensions { - extension(StructureType) + extension(TensorCreateFlagsARM) { - public static StructureType TexelBufferDescriptorInfoExt() + [Obsolete("Deprecated in favour of \"DescriptorHeapCaptureReplayBitArm\"")] + [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] + public static TensorCreateFlagsARM TensorCreateDescriptorHeapCaptureReplayBitArm() { - return (StructureType)1000135000; + return (TensorCreateFlagsARM)8; } - public static StructureType ImageDescriptorInfoExt() + [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] + public static TensorCreateFlagsARM DescriptorHeapCaptureReplayBitArm() { - return (StructureType)1000135001; + return (TensorCreateFlagsARM)8; } + } + } - public static StructureType ResourceDescriptorInfoExt() + public static class ExtDescriptorHeapIndirectCommandsTokenTypeExtExtensions + { + extension(IndirectCommandsTokenTypeEXT) + { + [Obsolete("Deprecated in favour of \"PushDataExt\"")] + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] + public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataExt() { - return (StructureType)1000135002; + return (IndirectCommandsTokenTypeEXT)1000135000; } - public static StructureType BindHeapInfoExt() + [Obsolete("Deprecated in favour of \"PushDataSequenceIndexExt\"")] + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] + public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataSequenceIndexExt() { - return (StructureType)1000135003; + return (IndirectCommandsTokenTypeEXT)1000135001; } - public static StructureType PushDataInfoExt() + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] + public static IndirectCommandsTokenTypeEXT PushDataExt() { - return (StructureType)1000135004; + return (IndirectCommandsTokenTypeEXT)1000135000; } - public static StructureType DescriptorSetAndBindingMappingExt() + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] + public static IndirectCommandsTokenTypeEXT PushDataSequenceIndexExt() { - return (StructureType)1000135005; + return (IndirectCommandsTokenTypeEXT)1000135001; } + } + } - public static StructureType ShaderDescriptorSetAndBindingMappingInfoExt() + public static class ExtDescriptorHeapIndirectCommandsTokenTypeNvExtensions + { + extension(IndirectCommandsTokenTypeNV) + { + [Obsolete("Deprecated in favour of \"PushDataNV\"")] + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] + public static IndirectCommandsTokenTypeNV IndirectCommandsTokenTypePushDataNV() { - return (StructureType)1000135006; + return (IndirectCommandsTokenTypeNV)1000135000; } - public static StructureType OpaqueCaptureDataCreateInfoExt() - { - return (StructureType)1000135007; - } - - public static StructureType PhysicalDeviceDescriptorHeapPropertiesExt() - { - return (StructureType)1000135008; - } - - public static StructureType PhysicalDeviceDescriptorHeapFeaturesExt() - { - return (StructureType)1000135009; - } - - public static StructureType CommandBufferInheritanceDescriptorHeapInfoExt() - { - return (StructureType)1000135010; - } - - public static StructureType SamplerCustomBorderColorIndexCreateInfoExt() - { - return (StructureType)1000135011; - } - - public static StructureType IndirectCommandsLayoutPushDataTokenNV() - { - return (StructureType)1000135012; - } - - public static StructureType SubsampledImageFormatPropertiesExt() - { - return (StructureType)1000135013; - } - - public static StructureType PhysicalDeviceDescriptorHeapTensorPropertiesArm() - { - return (StructureType)1000135014; - } - } - } - - public static class ExtDescriptorHeapAccessFlags2Extensions - { - extension(AccessFlags2) - { - [Obsolete("Deprecated in favour of \"SamplerHeapReadBitExt\"")] - [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] - public static AccessFlags2 Access2SamplerHeapReadBitExt() - { - return (AccessFlags2)144115188075855872L; - } - - [Obsolete("Deprecated in favour of \"ResourceHeapReadBitExt\"")] - [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] - public static AccessFlags2 Access2ResourceHeapReadBitExt() - { - return (AccessFlags2)288230376151711744L; - } - - [NativeName("Name", "VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_EXT")] - public static AccessFlags2 SamplerHeapReadBitExt() - { - return (AccessFlags2)144115188075855872L; - } - - [NativeName("Name", "VK_ACCESS_2_RESOURCE_HEAP_READ_BIT_EXT")] - public static AccessFlags2 ResourceHeapReadBitExt() - { - return (AccessFlags2)288230376151711744L; - } - } - } - - public static class ExtDescriptorHeapBufferUsageFlagsExtensions - { - extension(BufferUsageFlags) - { - [Obsolete("Deprecated in favour of \"DescriptorHeapBitExt\"")] - [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] - public static BufferUsageFlags BufferUsageDescriptorHeapBitExt() - { - return (BufferUsageFlags)268435456; - } - - [NativeName("Name", "VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT")] - public static BufferUsageFlags DescriptorHeapBitExt() - { - return (BufferUsageFlags)268435456; - } - } - } - - public static class ExtDescriptorHeapBufferUsageFlags2Extensions - { - extension(BufferUsageFlags2) - { - [Obsolete("Deprecated in favour of \"DescriptorHeapBitExt\"")] - [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] - public static BufferUsageFlags2 BufferUsage2DescriptorHeapBitExt() - { - return (BufferUsageFlags2)268435456; - } - - [NativeName("Name", "VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_EXT")] - public static BufferUsageFlags2 DescriptorHeapBitExt() - { - return (BufferUsageFlags2)268435456; - } - } - } - - public static class ExtDescriptorHeapPipelineCreateFlags2Extensions - { - extension(PipelineCreateFlags2) - { - [Obsolete("Deprecated in favour of \"Vk2DescriptorHeapBitExt\"")] - [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] - public static PipelineCreateFlags2 PipelineCreate2DescriptorHeapBitExt() - { - return (PipelineCreateFlags2)68719476736L; - } - - [NativeName("Name", "VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT")] - public static PipelineCreateFlags2 Vk2DescriptorHeapBitExt() - { - return (PipelineCreateFlags2)68719476736L; - } - } - } - - public static class ExtDescriptorHeapImageCreateFlagsExtensions - { - extension(ImageCreateFlags) - { - [Obsolete("Deprecated in favour of \"CreateDescriptorHeapCaptureReplayBitExt\"")] - [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] - public static ImageCreateFlags ImageCreateDescriptorHeapCaptureReplayBitExt() - { - return (ImageCreateFlags)65536; - } - - [NativeName("Name", "VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_EXT")] - public static ImageCreateFlags CreateDescriptorHeapCaptureReplayBitExt() - { - return (ImageCreateFlags)65536; - } - } - } - - public static class ExtDescriptorHeapShaderCreateFlagsExtExtensions - { - extension(ShaderCreateFlagsEXT) - { - [Obsolete("Deprecated in favour of \"CreateDescriptorHeapBitExt\"")] - [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] - public static ShaderCreateFlagsEXT ShaderCreateDescriptorHeapBitExt() - { - return (ShaderCreateFlagsEXT)1024; - } - - [NativeName("Name", "VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT")] - public static ShaderCreateFlagsEXT CreateDescriptorHeapBitExt() - { - return (ShaderCreateFlagsEXT)1024; - } - } - } - - public static class ExtDescriptorHeapTensorCreateFlagsArmExtensions - { - extension(TensorCreateFlagsARM) - { - [Obsolete("Deprecated in favour of \"DescriptorHeapCaptureReplayBitArm\"")] - [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] - public static TensorCreateFlagsARM TensorCreateDescriptorHeapCaptureReplayBitArm() - { - return (TensorCreateFlagsARM)8; - } - - [NativeName("Name", "VK_TENSOR_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_ARM")] - public static TensorCreateFlagsARM DescriptorHeapCaptureReplayBitArm() - { - return (TensorCreateFlagsARM)8; - } - } - } - - public static class ExtDescriptorHeapIndirectCommandsTokenTypeExtExtensions - { - extension(IndirectCommandsTokenTypeEXT) - { - [Obsolete("Deprecated in favour of \"PushDataExt\"")] - [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] - public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataExt() - { - return (IndirectCommandsTokenTypeEXT)1000135000; - } - - [Obsolete("Deprecated in favour of \"PushDataSequenceIndexExt\"")] - [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] - public static IndirectCommandsTokenTypeEXT IndirectCommandsTokenTypePushDataSequenceIndexExt() - { - return (IndirectCommandsTokenTypeEXT)1000135001; - } - - [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT")] - public static IndirectCommandsTokenTypeEXT PushDataExt() - { - return (IndirectCommandsTokenTypeEXT)1000135000; - } - - [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT")] - public static IndirectCommandsTokenTypeEXT PushDataSequenceIndexExt() - { - return (IndirectCommandsTokenTypeEXT)1000135001; - } - } - } - - public static class ExtDescriptorHeapIndirectCommandsTokenTypeNvExtensions - { - extension(IndirectCommandsTokenTypeNV) - { - [Obsolete("Deprecated in favour of \"PushDataNV\"")] - [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] - public static IndirectCommandsTokenTypeNV IndirectCommandsTokenTypePushDataNV() - { - return (IndirectCommandsTokenTypeNV)1000135000; - } - - [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] - public static IndirectCommandsTokenTypeNV PushDataNV() + [NativeName("Name", "VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_NV")] + public static IndirectCommandsTokenTypeNV PushDataNV() { return (IndirectCommandsTokenTypeNV)1000135000; } @@ -2720,4 +2303,421 @@ StructureType IStructuredType.StructureType() set => PNext = value; } } +} + +namespace Silk.NET.Vulkan.Extensions.EXT +{ + [Extension("VK_EXT_descriptor_heap")] + public unsafe partial class ExtDescriptorHeap : NativeExtension + { + public const uint SpecVersion = 1; + public const string ExtensionName = "VK_EXT_descriptor_heap"; + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] + public unsafe void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) + { + ((delegate* unmanaged[Stdcall])VTable.CmdBindResourceHeap)(commandBuffer, pBindInfo); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindResourceHeapEXT", Convention = CallingConvention.Winapi)] + public void CmdBindResourceHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly BindHeapInfoEXT pBindInfo) + { + fixed (BindHeapInfoEXT* pBindInfoPtr = &pBindInfo) + { + CmdBindResourceHeap(commandBuffer, pBindInfoPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] + public unsafe void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] BindHeapInfoEXT* pBindInfo) + { + ((delegate* unmanaged[Stdcall])VTable.CmdBindSamplerHeap)(commandBuffer, pBindInfo); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdBindSamplerHeapEXT", Convention = CallingConvention.Winapi)] + public void CmdBindSamplerHeap([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly BindHeapInfoEXT pBindInfo) + { + fixed (BindHeapInfoEXT* pBindInfoPtr = &pBindInfo) + { + CmdBindSamplerHeap(commandBuffer, pBindInfoPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] + public unsafe void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] PushDataInfoEXT* pPushDataInfo) + { + ((delegate* unmanaged[Stdcall])VTable.CmdPushData)(commandBuffer, pPushDataInfo); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkCmdPushDataEXT", Convention = CallingConvention.Winapi)] + public void CmdPushData([Count(Count = 0)] CommandBuffer commandBuffer, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly PushDataInfoEXT pPushDataInfo) + { + fixed (PushDataInfoEXT* pPushDataInfoPtr = &pPushDataInfo) + { + CmdPushData(commandBuffer, pPushDataInfoPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return ((delegate* unmanaged[Stdcall])VTable.GetImageOpaqueCaptureData)(device, imageCount, pImages, pDatas); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetImageOpaqueCaptureData(device, imageCount, pImages, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ref readonly Image pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + fixed (Image* pImagesPtr = &pImages) + { + return GetImageOpaqueCaptureData(device, imageCount, pImagesPtr, pDatas); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetImageOpaqueCaptureDataEXT", Convention = CallingConvention.Winapi)] + public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint imageCount, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ref readonly Image pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (Image* pImagesPtr = &pImages) + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetImageOpaqueCaptureData(device, imageCount, pImagesPtr, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetPhysicalDeviceDescriptorSizeEXT", Convention = CallingConvention.Winapi)] + public ulong GetPhysicalDeviceDescriptorSize([Count(Count = 0)] PhysicalDevice physicalDevice, [Count(Count = 0)] DescriptorType descriptorType) + { + return ((delegate* unmanaged[Stdcall])VTable.GetPhysicalDeviceDescriptorSize)(physicalDevice, descriptorType); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return ((delegate* unmanaged[Stdcall])VTable.GetTensorOpaqueCaptureData)(device, tensorCount, pTensors, pDatas); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetTensorOpaqueCaptureData(device, tensorCount, pTensors, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ref readonly TensorARM pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + fixed (TensorARM* pTensorsPtr = &pTensors) + { + return GetTensorOpaqueCaptureData(device, tensorCount, pTensorsPtr, pDatas); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkGetTensorOpaqueCaptureDataARM", Convention = CallingConvention.Winapi)] + public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Count = 0)] uint tensorCount, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ref readonly TensorARM pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] out HostAddressRangeEXT pDatas) + { + pDatas = default; + + fixed (TensorARM* pTensorsPtr = &pTensors) + fixed (HostAddressRangeEXT* pDatasPtr = &pDatas) + { + return GetTensorOpaqueCaptureData(device, tensorCount, pTensorsPtr, pDatasPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) + { + return ((delegate* unmanaged[Stdcall])VTable.RegisterCustomBorderColor)(device, pBorderColor, requestIndex, pIndex); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] SamplerCustomBorderColorCreateInfoEXT* pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] ref uint pIndex) + { + fixed (uint* pIndexPtr = &pIndex) + { + return RegisterCustomBorderColor(device, pBorderColor, requestIndex, pIndexPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public unsafe Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly SamplerCustomBorderColorCreateInfoEXT pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] uint* pIndex) + { + fixed (SamplerCustomBorderColorCreateInfoEXT* pBorderColorPtr = &pBorderColor) + { + return RegisterCustomBorderColor(device, pBorderColorPtr, requestIndex, pIndex); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkRegisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public Result RegisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0), Flow(FlowDirection.In)] ref readonly SamplerCustomBorderColorCreateInfoEXT pBorderColor, [Count(Count = 0)] Bool32 requestIndex, [Count(Count = 0)] ref uint pIndex) + { + fixed (SamplerCustomBorderColorCreateInfoEXT* pBorderColorPtr = &pBorderColor) + fixed (uint* pIndexPtr = &pIndex) + { + return RegisterCustomBorderColor(device, pBorderColorPtr, requestIndex, pIndexPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkUnregisterCustomBorderColorEXT", Convention = CallingConvention.Winapi)] + public void UnregisterCustomBorderColor([Count(Count = 0)] Device device, [Count(Count = 0)] uint index) + { + ((delegate* unmanaged[Stdcall])VTable.UnregisterCustomBorderColor)(device, index); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return ((delegate* unmanaged[Stdcall])VTable.WriteResourceDescriptors)(device, resourceCount, pResources, pDescriptors); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteResourceDescriptors(device, resourceCount, pResources, pDescriptorsPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly ResourceDescriptorInfoEXT pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + fixed (ResourceDescriptorInfoEXT* pResourcesPtr = &pResources) + { + return WriteResourceDescriptors(device, resourceCount, pResourcesPtr, pDescriptors); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteResourceDescriptorsEXT", Convention = CallingConvention.Winapi)] + public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint resourceCount, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly ResourceDescriptorInfoEXT pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (ResourceDescriptorInfoEXT* pResourcesPtr = &pResources) + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteResourceDescriptors(device, resourceCount, pResourcesPtr, pDescriptorsPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return ((delegate* unmanaged[Stdcall])VTable.WriteSamplerDescriptors)(device, samplerCount, pSamplers, pDescriptors); + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteSamplerDescriptors(device, samplerCount, pSamplers, pDescriptorsPtr); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly SamplerCreateInfo pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + fixed (SamplerCreateInfo* pSamplersPtr = &pSamplers) + { + return WriteSamplerDescriptors(device, samplerCount, pSamplersPtr, pDescriptors); + } + } + + /// To be documented. + [NativeApi(EntryPoint = "vkWriteSamplerDescriptorsEXT", Convention = CallingConvention.Winapi)] + public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Count = 0)] uint samplerCount, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly SamplerCreateInfo pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ref readonly HostAddressRangeEXT pDescriptors) + { + fixed (SamplerCreateInfo* pSamplersPtr = &pSamplers) + fixed (HostAddressRangeEXT* pDescriptorsPtr = &pDescriptors) + { + return WriteSamplerDescriptors(device, samplerCount, pSamplersPtr, pDescriptorsPtr); + } + } + + /// To be documented. + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] Image* pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetImageOpaqueCaptureData(device, (uint)pDatas.Length, pImages, out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return GetImageOpaqueCaptureData(device, (uint)pImages.Length, in pImages.GetPinnableReference(), pDatas); + } + + /// To be documented. + public Result GetImageOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "imageCount"), Flow(FlowDirection.In)] ReadOnlySpan pImages, [Count(Parameter = "imageCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetImageOpaqueCaptureData(device, (uint)pDatas.Length, in pImages.GetPinnableReference(), out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] TensorARM* pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetTensorOpaqueCaptureData(device, (uint)pDatas.Length, pTensors, out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] HostAddressRangeEXT* pDatas) + { + return GetTensorOpaqueCaptureData(device, (uint)pTensors.Length, in pTensors.GetPinnableReference(), pDatas); + } + + /// To be documented. + public Result GetTensorOpaqueCaptureData([Count(Count = 0)] Device device, [Count(Parameter = "tensorCount"), Flow(FlowDirection.In)] ReadOnlySpan pTensors, [Count(Parameter = "tensorCount"), Flow(FlowDirection.Out)] Span pDatas) + { + return GetTensorOpaqueCaptureData(device, (uint)pDatas.Length, in pTensors.GetPinnableReference(), out pDatas.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ResourceDescriptorInfoEXT* pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteResourceDescriptors(device, (uint)pDescriptors.Length, pResources, in pDescriptors.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return WriteResourceDescriptors(device, (uint)pResources.Length, in pResources.GetPinnableReference(), pDescriptors); + } + + /// To be documented. + public Result WriteResourceDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pResources, [Count(Parameter = "resourceCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteResourceDescriptors(device, (uint)pDescriptors.Length, in pResources.GetPinnableReference(), in pDescriptors.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] SamplerCreateInfo* pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteSamplerDescriptors(device, (uint)pDescriptors.Length, pSamplers, in pDescriptors.GetPinnableReference()); + } + + /// To be documented. + public unsafe Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] HostAddressRangeEXT* pDescriptors) + { + return WriteSamplerDescriptors(device, (uint)pSamplers.Length, in pSamplers.GetPinnableReference(), pDescriptors); + } + + /// To be documented. + public Result WriteSamplerDescriptors([Count(Count = 0)] Device device, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pSamplers, [Count(Parameter = "samplerCount"), Flow(FlowDirection.In)] ReadOnlySpan pDescriptors) + { + return WriteSamplerDescriptors(device, (uint)pDescriptors.Length, in pSamplers.GetPinnableReference(), in pDescriptors.GetPinnableReference()); + } + + public ExtDescriptorHeap(INativeContext ctx) + : base(ctx) + { + } + + protected override IVTable CreateVTable() + { + return new GeneratedVTable(_ctx); + } + + private GeneratedVTable VTable => (GeneratedVTable)CurrentVTable; + + private sealed class GeneratedVTable(INativeContext ctx) : IVTable, IDisposable + { + private readonly INativeContext ctx = ctx; + private nint cmdBindResourceHeap; + private nint cmdBindSamplerHeap; + private nint cmdPushData; + private nint getImageOpaqueCaptureData; + private nint getPhysicalDeviceDescriptorSize; + private nint getTensorOpaqueCaptureData; + private nint registerCustomBorderColor; + private nint unregisterCustomBorderColor; + private nint writeResourceDescriptors; + private nint writeSamplerDescriptors; + + public nint CmdBindResourceHeap => cmdBindResourceHeap is 0 ? cmdBindResourceHeap = Load("vkCmdBindResourceHeapEXT") : cmdBindResourceHeap; + public nint CmdBindSamplerHeap => cmdBindSamplerHeap is 0 ? cmdBindSamplerHeap = Load("vkCmdBindSamplerHeapEXT") : cmdBindSamplerHeap; + public nint CmdPushData => cmdPushData is 0 ? cmdPushData = Load("vkCmdPushDataEXT") : cmdPushData; + public nint GetImageOpaqueCaptureData => getImageOpaqueCaptureData is 0 ? getImageOpaqueCaptureData = Load("vkGetImageOpaqueCaptureDataEXT") : getImageOpaqueCaptureData; + public nint GetPhysicalDeviceDescriptorSize => getPhysicalDeviceDescriptorSize is 0 ? getPhysicalDeviceDescriptorSize = Load("vkGetPhysicalDeviceDescriptorSizeEXT") : getPhysicalDeviceDescriptorSize; + public nint GetTensorOpaqueCaptureData => getTensorOpaqueCaptureData is 0 ? getTensorOpaqueCaptureData = Load("vkGetTensorOpaqueCaptureDataARM") : getTensorOpaqueCaptureData; + public nint RegisterCustomBorderColor => registerCustomBorderColor is 0 ? registerCustomBorderColor = Load("vkRegisterCustomBorderColorEXT") : registerCustomBorderColor; + public nint UnregisterCustomBorderColor => unregisterCustomBorderColor is 0 ? unregisterCustomBorderColor = Load("vkUnregisterCustomBorderColorEXT") : unregisterCustomBorderColor; + public nint WriteResourceDescriptors => writeResourceDescriptors is 0 ? writeResourceDescriptors = Load("vkWriteResourceDescriptorsEXT") : writeResourceDescriptors; + public nint WriteSamplerDescriptors => writeSamplerDescriptors is 0 ? writeSamplerDescriptors = Load("vkWriteSamplerDescriptorsEXT") : writeSamplerDescriptors; + + public nint Load(int slot, string name) + { + return Load(name); + } + + public nint Load(string name) + { + return ctx.GetProcAddress(name, null); + } + + public IVTable Clone() + { + return new GeneratedVTable(ctx); + } + + public void Purge() + { + cmdBindResourceHeap = 0; + cmdBindSamplerHeap = 0; + cmdPushData = 0; + getImageOpaqueCaptureData = 0; + getPhysicalDeviceDescriptorSize = 0; + getTensorOpaqueCaptureData = 0; + registerCustomBorderColor = 0; + unregisterCustomBorderColor = 0; + writeResourceDescriptors = 0; + writeSamplerDescriptors = 0; + } + + public void Dispose() + { + Purge(); + } + } + } } \ No newline at end of file From 9870da8b221cd481b30a1d05f012d11a3c280ee0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 24 Jun 2026 11:11:39 +0800 Subject: [PATCH 288/466] Update Avalonia and SkiaSharp package versions Upgraded Avalonia from 12.0.4 to 12.0.5 and SkiaSharp from 3.119.4 to 4.148.0 to include the latest features and fixes. --- sources/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index adb64224..c28636af 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,7 +5,7 @@ - + @@ -18,7 +18,7 @@ - + From 99053311186a79ef3f15afd33debfa5ccb20858e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 24 Jun 2026 11:27:47 +0800 Subject: [PATCH 289/466] Add Vulkan extension and layer arrays to VKGraphicsContext Introduce static arrays for instance layers, instance extensions, and device extensions using Silk.NET Vulkan extension constants. Add public fields for Instance, PhysicalDevice, and Device to VKGraphicsContext. --- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 1e9a52eb..2f39ce81 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -1,9 +1,44 @@ using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using Silk.NET.Vulkan.Extensions.KHR; namespace Zenith.NET.Vulkan; internal class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Vulkan, useValidationLayer) { + private static readonly string[] InstanceLayers = + [ + "VK_LAYER_KHRONOS_validation" + ]; + + private static readonly string[] InstanceExtensions = + [ + ExtDebugUtils.ExtensionName, + ExtMetalSurface.ExtensionName, + KhrAndroidSurface.ExtensionName, + KhrSurface.ExtensionName, + KhrWaylandSurface.ExtensionName, + KhrWin32Surface.ExtensionName, + KhrXlibSurface.ExtensionName + ]; + + private static readonly string[] DeviceExtensions = + [ + ExtDescriptorHeap.ExtensionName, + ExtMeshShader.ExtensionName, + KhrAccelerationStructure.ExtensionName, + KhrDeferredHostOperations.ExtensionName, + KhrExternalMemoryWin32.ExtensionName, + KhrRayQuery.ExtensionName, + KhrSwapchain.ExtensionName + ]; + + public Instance Instance; + + public PhysicalDevice PhysicalDevice; + + public Device Device; + public Vk Vk { get; } = Vk.GetApi(); public override nint GetNativeObject(NativeObjectType type) From 0203c44f1f0c6da14168e82d6f24847914921a76 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 16:14:46 +0800 Subject: [PATCH 290/466] Simplify feature checks and error messages for DX12/Metal Removed detailed DirectX 12 feature checks and replaced them with a single version check and general exception message. Updated Metal context to use a broader error message for unsupported versions, reducing feature-specific validation code. --- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 24 +------------------ .../Zenith.NET.Metal/MTLGraphicsContext.cs | 2 +- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 5a04fc1d..4d33df9f 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -1,7 +1,6 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; using Silk.NET.DXGI; -using Feature = Silk.NET.Direct3D12.Feature; namespace Zenith.NET.DirectX12; @@ -63,28 +62,7 @@ protected override void Initialize(bool useValidationLayer, if (!D3D12.CreateDevice((IUnknown*)Adapter.Handle, D3DFeatureLevel.Level120, SilkMarshal.GuidPtrOf(), (void**)Device.GetAddressOf()).IsSuccess()) { - throw new NotSupportedException("Direct3D 12 Feature Level 12_0 is not supported on the selected adapter."); - } - - FeatureDataShaderModel shaderModel = new() { HighestShaderModel = D3DShaderModel.ShaderModel66 }; - Device.CheckFeatureSupport(Feature.ShaderModel, &shaderModel, (uint)sizeof(FeatureDataShaderModel)).Success(); - if (shaderModel.HighestShaderModel < D3DShaderModel.ShaderModel66) - { - throw new NotSupportedException("Shader Model 6.6 is required but not supported on the selected adapter."); - } - - FeatureDataD3D12Options options = new(); - Device.CheckFeatureSupport(Feature.D3D12Options, &options, (uint)sizeof(FeatureDataD3D12Options)).Success(); - if (options.ResourceBindingTier < ResourceBindingTier.Tier3) - { - throw new NotSupportedException("Resource Binding Tier 3 (bindless) is required but not supported on the selected adapter."); - } - - FeatureDataD3D12Options12 options12 = new(); - Device.CheckFeatureSupport(Feature.D3D12Options12, &options12, (uint)sizeof(FeatureDataD3D12Options12)).Success(); - if (!options12.EnhancedBarriersSupported) - { - throw new NotSupportedException("Enhanced Barriers are required but not supported on the selected adapter."); + throw new NotSupportedException("This device does not support DirectX 12.0 or higher."); } RootParameter1 rootParameter = new() diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index b9366769..07dade45 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -42,7 +42,7 @@ protected override void Initialize(bool useValidationLayer, { if (!Device.SupportsFamily(MTLGPUFamily.Metal4)) { - throw new NotSupportedException("Metal 4 is required but not supported by this device."); + throw new NotSupportedException("This device does not support Metal 4.0 or higher."); } Compiler = Device.MakeCompiler(new(), out NSError error); From d53ea6508b99867dc1920b5d5ae10730355182bc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 16:39:54 +0800 Subject: [PATCH 291/466] Refactor CopyQueue to TransferQueue; Vulkan init overhaul Refactor all CopyQueue references to TransferQueue across the codebase, including queue type, properties, and usage. Major expansion of VKGraphicsContext to support Vulkan instance/device creation, physical device selection, and queue family setup for graphics, compute, and transfer. Add support for new Vulkan extensions (including Android and KHR_shader_untyped_pointers). Update project files for Silk.NET Vulkan Android extension. Minor namespace and code style fixes. --- sources/Directory.Packages.props | 1 + .../Extensions.cs | 2 +- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 2 +- sources/Zenith.NET.DirectX12/DXFormats.cs | 2 +- .../Zenith.NET.DirectX12/DXGraphicsContext.cs | 4 +- .../Zenith.NET.Metal/MTLGraphicsContext.cs | 4 +- sources/Zenith.NET.Vulkan/KhrRayQuery.cs | 2 +- .../KhrShaderUntypedPointers.cs | 6 + .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 367 +++++++++++++++++- .../Zenith.NET.Vulkan.csproj | 1 + sources/Zenith.NET/Buffer.cs | 4 +- sources/Zenith.NET/Enums/CommandQueueType.cs | 2 +- sources/Zenith.NET/GraphicsContext.cs | 10 +- sources/Zenith.NET/Texture.cs | 4 +- 14 files changed, 390 insertions(+), 21 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/KhrShaderUntypedPointers.cs diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index c28636af..b8cd1e99 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -15,6 +15,7 @@ + diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index af8317dd..e28cd6c3 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -23,7 +23,7 @@ public Texture LoadTextureFromStream(Stream stream, bool generateMipMaps = true) Rgba32[] pixels = new Rgba32[image.Width * image.Height]; image.CopyPixelDataTo(pixels); - CommandBuffer commandBuffer = context.CopyQueue.CommandBuffer(); + CommandBuffer commandBuffer = context.TransferQueue.CommandBuffer(); unsafe { diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index dba80c71..0fa402b2 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -498,7 +498,7 @@ protected override void InsertDebugMarkerImpl(string label) protected override void BeginImpl() { - if (Queue.Type is CommandQueueType.Copy) + if (Queue.Type is CommandQueueType.Transfer) { return; } diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 5665dc6f..992397a3 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -190,7 +190,7 @@ public static CommandListType DirectX12(CommandQueueType commandQueueType) { CommandQueueType.Graphics => CommandListType.Direct, CommandQueueType.Compute => CommandListType.Compute, - CommandQueueType.Copy => CommandListType.Copy, + CommandQueueType.Transfer => CommandListType.Copy, _ => default }; } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 4d33df9f..8b6d4cd2 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -47,7 +47,7 @@ protected override void Initialize(bool useValidationLayer, out Capabilities capabilities, out CommandQueue graphicsQueue, out CommandQueue computeQueue, - out CommandQueue copyQueue, + out CommandQueue transferQueue, out ValidationLayer? validationLayer) { using ComPtr debug = new(); @@ -115,7 +115,7 @@ protected override void Initialize(bool useValidationLayer, capabilities = new DXCapabilities(this); graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics); computeQueue = new DXCommandQueue(this, CommandQueueType.Compute); - copyQueue = new DXCommandQueue(this, CommandQueueType.Copy); + transferQueue = new DXCommandQueue(this, CommandQueueType.Transfer); validationLayer = useValidationLayer ? new DXValidationLayer(this) : null; } diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index 07dade45..f2ecdd4d 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -37,7 +37,7 @@ protected override void Initialize(bool useValidationLayer, out Capabilities capabilities, out CommandQueue graphicsQueue, out CommandQueue computeQueue, - out CommandQueue copyQueue, + out CommandQueue transferQueue, out ValidationLayer? validationLayer) { if (!Device.SupportsFamily(MTLGPUFamily.Metal4)) @@ -54,7 +54,7 @@ protected override void Initialize(bool useValidationLayer, capabilities = new MTLCapabilities(this); graphicsQueue = new MTLCommandQueue(this, CommandQueueType.Graphics); computeQueue = new MTLCommandQueue(this, CommandQueueType.Compute); - copyQueue = new MTLCommandQueue(this, CommandQueueType.Copy); + transferQueue = new MTLCommandQueue(this, CommandQueueType.Transfer); validationLayer = null; } diff --git a/sources/Zenith.NET.Vulkan/KhrRayQuery.cs b/sources/Zenith.NET.Vulkan/KhrRayQuery.cs index 38951889..3a21a97c 100644 --- a/sources/Zenith.NET.Vulkan/KhrRayQuery.cs +++ b/sources/Zenith.NET.Vulkan/KhrRayQuery.cs @@ -3,4 +3,4 @@ internal static class KhrRayQuery { public const string ExtensionName = "VK_KHR_ray_query"; -} +} \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/KhrShaderUntypedPointers.cs b/sources/Zenith.NET.Vulkan/KhrShaderUntypedPointers.cs new file mode 100644 index 00000000..8b63a7a7 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/KhrShaderUntypedPointers.cs @@ -0,0 +1,6 @@ +namespace Zenith.NET.Vulkan; + +internal static class KhrShaderUntypedPointers +{ + public const string ExtensionName = "VK_KHR_shader_untyped_pointers"; +} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 2f39ce81..2be26ced 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -1,10 +1,13 @@ -using Silk.NET.Vulkan; +using Silk.NET.Core; +using Silk.NET.Core.Contexts; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.ANDROID; using Silk.NET.Vulkan.Extensions.EXT; using Silk.NET.Vulkan.Extensions.KHR; namespace Zenith.NET.Vulkan; -internal class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Vulkan, useValidationLayer) +internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Vulkan, useValidationLayer) { private static readonly string[] InstanceLayers = [ @@ -24,12 +27,16 @@ internal class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(Grap private static readonly string[] DeviceExtensions = [ + AndroidExternalMemoryAndroidHardwareBuffer.ExtensionName, ExtDescriptorHeap.ExtensionName, ExtMeshShader.ExtensionName, + ExtMetalObjects.ExtensionName, KhrAccelerationStructure.ExtensionName, KhrDeferredHostOperations.ExtensionName, + KhrExternalFenceFd.ExtensionName, KhrExternalMemoryWin32.ExtensionName, KhrRayQuery.ExtensionName, + KhrShaderUntypedPointers.ExtensionName, KhrSwapchain.ExtensionName ]; @@ -41,6 +48,38 @@ internal class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(Grap public Vk Vk { get; } = Vk.GetApi(); + public ExtDebugUtils? DebugUtils { get; private set; } + + public ExtMetalSurface? MetalSurface { get; private set; } + + public KhrAndroidSurface? AndroidSurface { get; private set; } + + public KhrSurface? Surface { get; private set; } + + public KhrWaylandSurface? WaylandSurface { get; private set; } + + public KhrWin32Surface? Win32Surface { get; private set; } + + public KhrXlibSurface? XlibSurface { get; private set; } + + public AndroidExternalMemoryAndroidHardwareBuffer? ExternalMemoryAndroidHardwareBuffer { get; private set; } + + public ExtDescriptorHeap? DescriptorHeap { get; private set; } + + public ExtMeshShader? MeshShader { get; private set; } + + public ExtMetalObjects? MetalObjects { get; private set; } + + public KhrAccelerationStructure? AccelerationStructure { get; private set; } + + public KhrDeferredHostOperations? DeferredHostOperations { get; private set; } + + public KhrExternalFenceFd? ExternalFenceFd { get; private set; } + + public KhrExternalMemoryWin32? ExternalMemoryWin32 { get; private set; } + + public KhrSwapchain? Swapchain { get; private set; } + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -50,9 +89,331 @@ protected override void Initialize(bool useValidationLayer, out Capabilities capabilities, out CommandQueue graphicsQueue, out CommandQueue computeQueue, - out CommandQueue copyQueue, + out CommandQueue transferQueue, out ValidationLayer? validationLayer) { + Version32 apiVersion = new(1, 4, 0); + + using ZenithMarshal.Scope scope = new(); + + // Create instance + { + uint extensionCount = 0; + Vk.EnumerateInstanceExtensionProperties(default(byte*), &extensionCount, default).Success(); + + ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); + Vk.EnumerateInstanceExtensionProperties(default(byte*), &extensionCount, extensions).Success(); + + string[] enabledExtensions = [.. new ReadOnlySpan(extensions, (int)extensionCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.ExtensionName, StringEncoding.UTF8))]; + enabledExtensions = [.. enabledExtensions.Intersect(InstanceExtensions)]; + + ApplicationInfo applicationInfo = new() + { + SType = StructureType.ApplicationInfo, + PApplicationName = (byte*)ZenithMarshal.StringToPointer(scope, AppDomain.CurrentDomain.FriendlyName, StringEncoding.UTF8), + PEngineName = (byte*)ZenithMarshal.StringToPointer(scope, "Zenith.NET", StringEncoding.UTF8), + ApiVersion = apiVersion + }; + + InstanceCreateInfo createInfo = new() + { + SType = StructureType.InstanceCreateInfo, + PApplicationInfo = &applicationInfo, + EnabledExtensionCount = (uint)enabledExtensions.Length, + PpEnabledExtensionNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledExtensions, StringEncoding.UTF8) + }; + + if (useValidationLayer) + { + uint layerCount = 0; + Vk.EnumerateInstanceLayerProperties(&layerCount, default).Success(); + + LayerProperties* layers = (LayerProperties*)ZenithMarshal.Allocate(scope, layerCount); + Vk.EnumerateInstanceLayerProperties(&layerCount, layers).Success(); + + string[] enabledLayers = [.. new ReadOnlySpan(layers, (int)layerCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.LayerName, StringEncoding.UTF8))]; + enabledLayers = [.. enabledLayers.Intersect(InstanceLayers)]; + + createInfo.EnabledLayerCount = (uint)enabledLayers.Length; + createInfo.PpEnabledLayerNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledLayers, StringEncoding.UTF8); + } + + Vk.CreateInstance(&createInfo, default, out Instance).Success(); + + LamdaNativeContext context = new(proc => Vk.GetInstanceProcAddr(Instance, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); + + DebugUtils = enabledExtensions.Contains(ExtDebugUtils.ExtensionName) ? new(context) : null; + MetalSurface = enabledExtensions.Contains(ExtMetalSurface.ExtensionName) ? new(context) : null; + AndroidSurface = enabledExtensions.Contains(KhrAndroidSurface.ExtensionName) ? new(context) : null; + Surface = enabledExtensions.Contains(KhrSurface.ExtensionName) ? new(context) : null; + WaylandSurface = enabledExtensions.Contains(KhrWaylandSurface.ExtensionName) ? new(context) : null; + Win32Surface = enabledExtensions.Contains(KhrWin32Surface.ExtensionName) ? new(context) : null; + XlibSurface = enabledExtensions.Contains(KhrXlibSurface.ExtensionName) ? new(context) : null; + } + + // Select physical device and create logical device + { + uint physicalDeviceCount = 0; + Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, default).Success(); + + PhysicalDevice* physicalDevices = (PhysicalDevice*)ZenithMarshal.Allocate(scope, physicalDeviceCount); + Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, physicalDevices).Success(); + + ulong bestScore = 0; + foreach (PhysicalDevice physicalDevice in new ReadOnlySpan(physicalDevices, (int)physicalDeviceCount)) + { + PhysicalDeviceProperties properties; + Vk.GetPhysicalDeviceProperties(physicalDevice, &properties); + + PhysicalDeviceFeatures features; + Vk.GetPhysicalDeviceFeatures(physicalDevice, &features); + + if (properties.ApiVersion < apiVersion) + { + continue; + } + + ulong score = properties.DeviceType switch + { + PhysicalDeviceType.DiscreteGpu => 100000, + PhysicalDeviceType.IntegratedGpu => 10000, + PhysicalDeviceType.VirtualGpu => 1000, + _ => 0 + }; + + score += properties.Limits.MaxImageDimension2D; + score += properties.Limits.MaxImageDimension3D / 16; + score += properties.Limits.MaxImageArrayLayers; + score += properties.Limits.MaxComputeSharedMemorySize / 1024; + score += properties.Limits.MaxComputeWorkGroupInvocations; + score += properties.Limits.MaxSamplerAllocationCount / 1024; + score += properties.Limits.MaxStorageBufferRange / (1024 * 1024); + score += properties.Limits.MaxUniformBufferRange / 1024; + score += properties.Limits.MaxPushConstantsSize; + + if (features.SamplerAnisotropy) + { + score += 2000; + } + + if (features.MultiDrawIndirect) + { + score += 1000; + } + + if (features.DrawIndirectFirstInstance) + { + score += 1000; + } + + if (features.IndependentBlend) + { + score += 500; + } + + if (features.FillModeNonSolid) + { + score += 250; + } + + if (features.TextureCompressionBC) + { + score += 500; + } + + if (features.ShaderInt64) + { + score += 250; + } + + if (score > bestScore) + { + bestScore = score; + + PhysicalDevice = physicalDevice; + } + } + + if (PhysicalDevice.Handle is 0) + { + throw new NotSupportedException("This device does not support Vulkan 1.4 or higher."); + } + + uint graphicsQueueFamilyIndex = 0; + uint graphicsQueueFamilyCount = 0; + + uint computeQueueFamilyIndex = 0; + uint computeQueueFamilyCount = 0; + + uint transferQueueFamilyIndex = 0; + uint transferQueueFamilyCount = 0; + + uint queueFamilyCount = 0; + Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, default); + + QueueFamilyProperties* queueFamilies = (QueueFamilyProperties*)ZenithMarshal.Allocate(scope, queueFamilyCount); + Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, queueFamilies); + + uint index = 0; + foreach (QueueFamilyProperties queueFamilyProperties in new ReadOnlySpan(queueFamilies, (int)queueFamilyCount)) + { + if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.GraphicsBit) && queueFamilyProperties.QueueCount > graphicsQueueFamilyCount) + { + graphicsQueueFamilyIndex = index; + graphicsQueueFamilyCount = queueFamilyProperties.QueueCount; + } + else if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.ComputeBit) && queueFamilyProperties.QueueCount > computeQueueFamilyCount) + { + computeQueueFamilyIndex = index; + computeQueueFamilyCount = queueFamilyProperties.QueueCount; + } + else if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.TransferBit) && queueFamilyProperties.QueueCount > transferQueueFamilyCount) + { + transferQueueFamilyIndex = index; + transferQueueFamilyCount = queueFamilyProperties.QueueCount; + } + + index++; + } + + HashSet queueFamilyIndices = [graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex]; + + uint queueCreateInfoCount; + DeviceQueueCreateInfo* queueCreateInfos; + Func<(Queue GraphicsQueue, Queue ComputeQueue, Queue TransferQueue, uint[] QueueFamilyIndices)> getQueues; + if (queueFamilyIndices.Count is 3) + { + queueCreateInfoCount = 3; + + float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); + queuePriorities[0] = 1.0f; + + queueCreateInfos = (DeviceQueueCreateInfo*)ZenithMarshal.Allocate(scope, 3); + + queueCreateInfos[0] = new() + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = graphicsQueueFamilyIndex, + QueueCount = 1, + PQueuePriorities = queuePriorities + }; + + queueCreateInfos[1] = new() + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = computeQueueFamilyIndex, + QueueCount = 1, + PQueuePriorities = queuePriorities + }; + + queueCreateInfos[2] = new() + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = transferQueueFamilyIndex, + QueueCount = 1, + PQueuePriorities = queuePriorities + }; + + getQueues = () => + { + Queue graphicsQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); + + Queue computeQueue = default; + Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); + + Queue transferQueue = default; + Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); + + return (graphicsQueue, computeQueue, transferQueue, [graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex]); + }; + } + else if (graphicsQueueFamilyCount >= 3) + { + queueCreateInfoCount = 1; + + float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 3); + queuePriorities[0] = 1.0f; + queuePriorities[1] = 1.0f; + queuePriorities[2] = 1.0f; + + queueCreateInfos = (DeviceQueueCreateInfo*)ZenithMarshal.Allocate(scope, 1); + queueCreateInfos[0] = new() + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = graphicsQueueFamilyIndex, + QueueCount = 3, + PQueuePriorities = queuePriorities + }; + + getQueues = () => + { + Queue graphicsQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); + + Queue computeQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); + + Queue transferQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); + + return (graphicsQueue, computeQueue, transferQueue, [graphicsQueueFamilyIndex]); + }; + } + else + { + queueCreateInfoCount = 1; + + float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); + queuePriorities[0] = 1.0f; + + queueCreateInfos = (DeviceQueueCreateInfo*)ZenithMarshal.Allocate(scope, 1); + queueCreateInfos[0] = new() + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = graphicsQueueFamilyIndex, + QueueCount = 1, + PQueuePriorities = queuePriorities + }; + + getQueues = () => + { + Queue graphicsQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); + + return (graphicsQueue, graphicsQueue, graphicsQueue, [graphicsQueueFamilyIndex]); + }; + } + + uint extensionCount = 0; + Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, default(byte*), &extensionCount, default).Success(); + + ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); + Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, default(byte*), &extensionCount, extensions).Success(); + + string[] enabledExtensions = [.. new ReadOnlySpan(extensions, (int)extensionCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.ExtensionName, StringEncoding.UTF8))]; + enabledExtensions = [.. enabledExtensions.Intersect(DeviceExtensions)]; + + DeviceCreateInfo createInfo = new() + { + SType = StructureType.DeviceCreateInfo, + QueueCreateInfoCount = queueCreateInfoCount, + PQueueCreateInfos = queueCreateInfos, + EnabledExtensionCount = (uint)enabledExtensions.Length, + PpEnabledExtensionNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledExtensions, StringEncoding.UTF8) + }; + + createInfo.AddNext(out PhysicalDeviceFeatures2 features2); + createInfo.AddNext(out PhysicalDeviceVulkan11Features _); + createInfo.AddNext(out PhysicalDeviceVulkan12Features _); + createInfo.AddNext(out PhysicalDeviceVulkan13Features _); + createInfo.AddNext(out PhysicalDeviceVulkan14Features _); + + Vk.GetPhysicalDeviceFeatures2(PhysicalDevice, &features2); + + Vk.CreateDevice(PhysicalDevice, &createInfo, default, out Device).Success(); + } + throw new NotImplementedException(); } diff --git a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj index 56ad22a6..476a45fb 100644 --- a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj +++ b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj @@ -10,6 +10,7 @@ + diff --git a/sources/Zenith.NET/Buffer.cs b/sources/Zenith.NET/Buffer.cs index f1a01ad9..39fee364 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -31,7 +31,7 @@ public void Upload(uint offsetInBytes, BufferData data) } else { - CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); + CommandBuffer commandBuffer = Context.TransferQueue.CommandBuffer(); commandBuffer.Upload(this, offsetInBytes, data); commandBuffer.Submit().Wait(); @@ -53,7 +53,7 @@ public void Download(uint offsetInBytes, BufferData data) } else { - CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); + CommandBuffer commandBuffer = Context.TransferQueue.CommandBuffer(); commandBuffer.Download(this, offsetInBytes, data); commandBuffer.Submit().Wait(); diff --git a/sources/Zenith.NET/Enums/CommandQueueType.cs b/sources/Zenith.NET/Enums/CommandQueueType.cs index 806bb567..f0654b03 100644 --- a/sources/Zenith.NET/Enums/CommandQueueType.cs +++ b/sources/Zenith.NET/Enums/CommandQueueType.cs @@ -6,5 +6,5 @@ public enum CommandQueueType Compute, - Copy + Transfer } diff --git a/sources/Zenith.NET/GraphicsContext.cs b/sources/Zenith.NET/GraphicsContext.cs index 31ef7663..52cded76 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -10,13 +10,13 @@ protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) out Capabilities capabilities, out CommandQueue graphicsQueue, out CommandQueue computeQueue, - out CommandQueue copyQueue, + out CommandQueue transferQueue, out ValidationLayer? validationLayer); Capabilities = capabilities; GraphicsQueue = graphicsQueue; ComputeQueue = computeQueue; - CopyQueue = copyQueue; + TransferQueue = transferQueue; ValidationLayer = validationLayer; Uploader = new(this); @@ -31,7 +31,7 @@ protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) public CommandQueue ComputeQueue { get; } - public CommandQueue CopyQueue { get; } + public CommandQueue TransferQueue { get; } internal ValidationLayer? ValidationLayer { get; } @@ -122,7 +122,7 @@ protected override void Destroy() { GraphicsQueue.Dispose(); ComputeQueue.Dispose(); - CopyQueue.Dispose(); + TransferQueue.Dispose(); ValidationLayer?.Dispose(); Uploader.Dispose(); @@ -133,7 +133,7 @@ protected abstract void Initialize(bool useValidationLayer, out Capabilities capabilities, out CommandQueue graphicsQueue, out CommandQueue computeQueue, - out CommandQueue copyQueue, + out CommandQueue transferQueue, out ValidationLayer? validationLayer); protected abstract SwapChain CreateSwapChainImpl(SwapChainDesc desc); diff --git a/sources/Zenith.NET/Texture.cs b/sources/Zenith.NET/Texture.cs index 29e60ceb..b5b8e914 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -20,7 +20,7 @@ internal TextureLayout this[TextureSubresource subresource] public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); + CommandBuffer commandBuffer = Context.TransferQueue.CommandBuffer(); commandBuffer.Upload(this, subresource, offset, extent, data); commandBuffer.Submit().Wait(); @@ -28,7 +28,7 @@ public void Upload(TextureSubresource subresource, Offset3D offset, Extent3D ext public void Download(TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - CommandBuffer commandBuffer = Context.CopyQueue.CommandBuffer(); + CommandBuffer commandBuffer = Context.TransferQueue.CommandBuffer(); commandBuffer.Download(this, subresource, offset, extent, data); commandBuffer.Submit().Wait(); From 8a784d9cb5b990ab72f294bd7990fa107b71ccce Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 17:32:13 +0800 Subject: [PATCH 292/466] Add KHR_fragment_shading_rate extension support Integrate KHR_fragment_shading_rate into VKGraphicsContext by updating enabled extensions, adding a property, and initializing related features. Also refactor extension initialization logic for improved extensibility. --- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 2be26ced..262fb8a5 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -35,6 +35,7 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte KhrDeferredHostOperations.ExtensionName, KhrExternalFenceFd.ExtensionName, KhrExternalMemoryWin32.ExtensionName, + KhrFragmentShadingRate.ExtensionName, KhrRayQuery.ExtensionName, KhrShaderUntypedPointers.ExtensionName, KhrSwapchain.ExtensionName @@ -78,6 +79,8 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrExternalMemoryWin32? ExternalMemoryWin32 { get; private set; } + public KhrFragmentShadingRate? FragmentShadingRate { get; private set; } + public KhrSwapchain? Swapchain { get; private set; } public override nint GetNativeObject(NativeObjectType type) @@ -409,9 +412,52 @@ protected override void Initialize(bool useValidationLayer, createInfo.AddNext(out PhysicalDeviceVulkan13Features _); createInfo.AddNext(out PhysicalDeviceVulkan14Features _); + if (enabledExtensions.Contains(ExtDescriptorHeap.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceDescriptorHeapFeaturesEXT _); + } + + if (enabledExtensions.Contains(ExtMeshShader.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceMeshShaderFeaturesEXT _); + } + + if (enabledExtensions.Contains(KhrAccelerationStructure.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR _); + } + + if (enabledExtensions.Contains(KhrFragmentShadingRate.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceFragmentShadingRateFeaturesKHR _); + } + + if (enabledExtensions.Contains(KhrRayQuery.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceRayQueryFeaturesKHR _); + } + + if (enabledExtensions.Contains(KhrShaderUntypedPointers.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceShaderUntypedPointersFeaturesKHR _); + } + Vk.GetPhysicalDeviceFeatures2(PhysicalDevice, &features2); Vk.CreateDevice(PhysicalDevice, &createInfo, default, out Device).Success(); + + LamdaNativeContext context = new((proc) => Vk.GetDeviceProcAddr(Device, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); + + ExternalMemoryAndroidHardwareBuffer = enabledExtensions.Contains(AndroidExternalMemoryAndroidHardwareBuffer.ExtensionName) ? new(context) : null; + DescriptorHeap = enabledExtensions.Contains(ExtDescriptorHeap.ExtensionName) ? new(context) : null; + MeshShader = enabledExtensions.Contains(ExtMeshShader.ExtensionName) ? new(context) : null; + MetalObjects = enabledExtensions.Contains(ExtMetalObjects.ExtensionName) ? new(context) : null; + AccelerationStructure = enabledExtensions.Contains(KhrAccelerationStructure.ExtensionName) ? new(context) : null; + DeferredHostOperations = enabledExtensions.Contains(KhrDeferredHostOperations.ExtensionName) ? new(context) : null; + ExternalFenceFd = enabledExtensions.Contains(KhrExternalFenceFd.ExtensionName) ? new(context) : null; + ExternalMemoryWin32 = enabledExtensions.Contains(KhrExternalMemoryWin32.ExtensionName) ? new(context) : null; + FragmentShadingRate = enabledExtensions.Contains(KhrFragmentShadingRate.ExtensionName) ? new(context) : null; + Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; } throw new NotImplementedException(); From dd878fef9eb2c7a43f71aa802398b8805fb31ce1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 17:54:02 +0800 Subject: [PATCH 293/466] Add VKCapabilities and stub extension methods Added VKCapabilities to query device properties and extension support (ray tracing, mesh shading). Stub extension methods for graphics types were added to Extensions.cs. VKGraphicsContext now initializes VKCapabilities. --- sources/Zenith.NET.Vulkan/Extensions.cs | 64 +++++++++++++++++++ sources/Zenith.NET.Vulkan/VKCapabilities.cs | 33 ++++++++++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 + 3 files changed, 99 insertions(+) create mode 100644 sources/Zenith.NET.Vulkan/VKCapabilities.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 3bd90b22..abfc644f 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -41,4 +41,68 @@ internal void AddNext(out TNext next) current->PNext = (BaseInStructure*)Unsafe.AsPointer(ref next); } } + + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + { + } + + extension(Buffer buffer) + { + } + + extension(BufferView bufferView) + { + } + + extension(CommandBuffer commandBuffer) + { + } + + extension(CommandQueue commandQueue) + { + } + + extension(ComputePipeline computePipeline) + { + } + + extension(GraphicsPipeline graphicsPipeline) + { + } + + extension(Heap heap) + { + } + + extension(MeshShadingPipeline meshShadingPipeline) + { + } + + extension(QueryHeap queryHeap) + { + } + + extension(Sampler sampler) + { + } + + extension(Shader shader) + { + } + + extension(SwapChain swapChain) + { + } + + extension(Texture texture) + { + } + + extension(TextureView textureView) + { + } + + extension(TopLevelAccelerationStructure topLevelAccelerationStructure) + { + } } diff --git a/sources/Zenith.NET.Vulkan/VKCapabilities.cs b/sources/Zenith.NET.Vulkan/VKCapabilities.cs new file mode 100644 index 00000000..48681f10 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKCapabilities.cs @@ -0,0 +1,33 @@ +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKCapabilities : Capabilities +{ + public VKCapabilities(VKGraphicsContext context) + { + using ZenithMarshal.Scope scope = new(); + + PhysicalDeviceProperties properties; + context.Vk.GetPhysicalDeviceProperties(context.PhysicalDevice, &properties); + + uint extensionCount = 0; + context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, default(byte*), &extensionCount, default).Success(); + + ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); + context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, default(byte*), &extensionCount, extensions).Success(); + + string[] supportedExtensions = [.. new ReadOnlySpan(extensions, (int)extensionCount).ToArray().Select(static item => ZenithMarshal.StringFromPointer((nint)item.ExtensionName, StringEncoding.UTF8))]; + + DeviceName = ZenithMarshal.StringFromPointer((nint)properties.DeviceName, StringEncoding.UTF8); + RayTracingSupported = supportedExtensions.Contains(KhrRayQuery.ExtensionName); + MeshShadingSupported = supportedExtensions.Contains(ExtMeshShader.ExtensionName); + } + + public override string DeviceName { get; } + + public override bool RayTracingSupported { get; } + + public override bool MeshShadingSupported { get; } +} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 262fb8a5..0ab1479a 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -460,6 +460,8 @@ protected override void Initialize(bool useValidationLayer, Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; } + capabilities = new VKCapabilities(this); + throw new NotImplementedException(); } From 467e797810b81a92d5ec9399a37277780f7609d1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:30:19 +0800 Subject: [PATCH 294/466] Refactor Vulkan queue retrieval and storage logic Replaced Func-based queue retrieval with an Action that sets a queues tuple field, now including both queue handles and their family indices. This improves clarity and ensures queues are initialized and stored together after device creation. --- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 0ab1479a..2873f25a 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -154,6 +154,8 @@ protected override void Initialize(bool useValidationLayer, XlibSurface = enabledExtensions.Contains(KhrXlibSurface.ExtensionName) ? new(context) : null; } + (Queue GraphicsQueue, uint GraphicsQueueFamilyIndex, Queue ComputeQueue, uint ComputeQueueFamilyIndex, Queue TransferQueue, uint TransferQueueFamilyIndex) queues = default; + // Select physical device and create logical device { uint physicalDeviceCount = 0; @@ -283,7 +285,7 @@ protected override void Initialize(bool useValidationLayer, uint queueCreateInfoCount; DeviceQueueCreateInfo* queueCreateInfos; - Func<(Queue GraphicsQueue, Queue ComputeQueue, Queue TransferQueue, uint[] QueueFamilyIndices)> getQueues; + Action loadQueues; if (queueFamilyIndices.Count is 3) { queueCreateInfoCount = 3; @@ -317,7 +319,7 @@ protected override void Initialize(bool useValidationLayer, PQueuePriorities = queuePriorities }; - getQueues = () => + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); @@ -328,7 +330,7 @@ protected override void Initialize(bool useValidationLayer, Queue transferQueue = default; Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); - return (graphicsQueue, computeQueue, transferQueue, [graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex]); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, computeQueueFamilyIndex, transferQueue, transferQueueFamilyIndex); }; } else if (graphicsQueueFamilyCount >= 3) @@ -349,7 +351,7 @@ protected override void Initialize(bool useValidationLayer, PQueuePriorities = queuePriorities }; - getQueues = () => + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); @@ -360,7 +362,7 @@ protected override void Initialize(bool useValidationLayer, Queue transferQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); - return (graphicsQueue, computeQueue, transferQueue, [graphicsQueueFamilyIndex]); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, graphicsQueueFamilyIndex, transferQueue, graphicsQueueFamilyIndex); }; } else @@ -379,12 +381,12 @@ protected override void Initialize(bool useValidationLayer, PQueuePriorities = queuePriorities }; - getQueues = () => + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - return (graphicsQueue, graphicsQueue, graphicsQueue, [graphicsQueueFamilyIndex]); + queues = (graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex); }; } @@ -446,6 +448,8 @@ protected override void Initialize(bool useValidationLayer, Vk.CreateDevice(PhysicalDevice, &createInfo, default, out Device).Success(); + loadQueues(); + LamdaNativeContext context = new((proc) => Vk.GetDeviceProcAddr(Device, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); ExternalMemoryAndroidHardwareBuffer = enabledExtensions.Contains(AndroidExternalMemoryAndroidHardwareBuffer.ExtensionName) ? new(context) : null; @@ -546,4 +550,4 @@ protected override void Destroy() Vk.Dispose(); } -} +} \ No newline at end of file From 927158b36f7f0e0cbad4dfcb4985d230dfce0cc8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:38:20 +0800 Subject: [PATCH 295/466] Refactor queue setup; add VKCommandQueue class Refactored VKGraphicsContext to encapsulate queue retrieval logic using a loadQueues Action, clarifying handling of different queue family configurations. Introduced VKCommandQueue class with stubbed methods and properties for Vulkan queue and family index. VKGraphicsContext now instantiates VKCommandQueue objects for graphics, compute, and transfer queues after device creation. --- sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 49 +++++++++++++++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 61 ++++++++++--------- 2 files changed, 81 insertions(+), 29 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKCommandQueue.cs diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs new file mode 100644 index 00000000..1d1db80f --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -0,0 +1,49 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal class VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : CommandQueue(context, type) +{ + public Queue Queue = queue; + + public uint QueueFamilyIndex = queueFamilyIndex; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override ulong GetCompletedValue() + { + throw new NotImplementedException(); + } + + protected override CommandBuffer CreateCommandBuffer() + { + throw new NotImplementedException(); + } + + protected override void SignalImpl(ulong signalValue) + { + throw new NotImplementedException(); + } + + protected override void SubmitImpl(CommandBuffer commandBuffer) + { + throw new NotImplementedException(); + } + + protected override void WaitImpl(ulong waitValue) + { + throw new NotImplementedException(); + } + + protected override void WaitImpl(ReadOnlySpan submissions) + { + throw new NotImplementedException(); + } + + protected override void SetResourceName(string name) + { + } +} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 2873f25a..bb936785 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -283,11 +283,25 @@ protected override void Initialize(bool useValidationLayer, HashSet queueFamilyIndices = [graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex]; + Action loadQueues; uint queueCreateInfoCount; DeviceQueueCreateInfo* queueCreateInfos; - Action loadQueues; if (queueFamilyIndices.Count is 3) { + loadQueues = () => + { + Queue graphicsQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); + + Queue computeQueue = default; + Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); + + Queue transferQueue = default; + Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); + + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, computeQueueFamilyIndex, transferQueue, transferQueueFamilyIndex); + }; + queueCreateInfoCount = 3; float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); @@ -318,23 +332,23 @@ protected override void Initialize(bool useValidationLayer, QueueCount = 1, PQueuePriorities = queuePriorities }; - + } + else if (graphicsQueueFamilyCount >= 3) + { loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); Queue computeQueue = default; - Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); Queue transferQueue = default; - Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); - queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, computeQueueFamilyIndex, transferQueue, transferQueueFamilyIndex); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, graphicsQueueFamilyIndex, transferQueue, graphicsQueueFamilyIndex); }; - } - else if (graphicsQueueFamilyCount >= 3) - { + queueCreateInfoCount = 1; float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 3); @@ -350,23 +364,17 @@ protected override void Initialize(bool useValidationLayer, QueueCount = 3, PQueuePriorities = queuePriorities }; - + } + else + { loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - Queue computeQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); - - Queue transferQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); - - queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, graphicsQueueFamilyIndex, transferQueue, graphicsQueueFamilyIndex); + queues = (graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex); }; - } - else - { + queueCreateInfoCount = 1; float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); @@ -380,14 +388,6 @@ protected override void Initialize(bool useValidationLayer, QueueCount = 1, PQueuePriorities = queuePriorities }; - - loadQueues = () => - { - Queue graphicsQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - - queues = (graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex); - }; } uint extensionCount = 0; @@ -448,8 +448,6 @@ protected override void Initialize(bool useValidationLayer, Vk.CreateDevice(PhysicalDevice, &createInfo, default, out Device).Success(); - loadQueues(); - LamdaNativeContext context = new((proc) => Vk.GetDeviceProcAddr(Device, (byte*)ZenithMarshal.StringToPointer(scope, proc, StringEncoding.UTF8))); ExternalMemoryAndroidHardwareBuffer = enabledExtensions.Contains(AndroidExternalMemoryAndroidHardwareBuffer.ExtensionName) ? new(context) : null; @@ -462,9 +460,14 @@ protected override void Initialize(bool useValidationLayer, ExternalMemoryWin32 = enabledExtensions.Contains(KhrExternalMemoryWin32.ExtensionName) ? new(context) : null; FragmentShadingRate = enabledExtensions.Contains(KhrFragmentShadingRate.ExtensionName) ? new(context) : null; Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; + + loadQueues(); } capabilities = new VKCapabilities(this); + graphicsQueue = new VKCommandQueue(this, CommandQueueType.Graphics, queues.GraphicsQueue, queues.GraphicsQueueFamilyIndex); + computeQueue = new VKCommandQueue(this, CommandQueueType.Compute, queues.ComputeQueue, queues.ComputeQueueFamilyIndex); + transferQueue = new VKCommandQueue(this, CommandQueueType.Transfer, queues.TransferQueue, queues.TransferQueueFamilyIndex); throw new NotImplementedException(); } From 7be260ffe71f9507b02ef58d92de67163ce04478 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:41:02 +0800 Subject: [PATCH 296/466] Refactor Vulkan queue creation and loading logic Refactored VKGraphicsContext to determine queue creation and loading based on unique queue family indices. Ensured correct assignment of graphics, compute, and transfer queues and their respective family indices, supporting cases where queues are from different or the same families. --- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index bb936785..d72714b3 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -283,25 +283,11 @@ protected override void Initialize(bool useValidationLayer, HashSet queueFamilyIndices = [graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex]; - Action loadQueues; uint queueCreateInfoCount; DeviceQueueCreateInfo* queueCreateInfos; + Action loadQueues; if (queueFamilyIndices.Count is 3) { - loadQueues = () => - { - Queue graphicsQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - - Queue computeQueue = default; - Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); - - Queue transferQueue = default; - Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); - - queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, computeQueueFamilyIndex, transferQueue, transferQueueFamilyIndex); - }; - queueCreateInfoCount = 3; float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); @@ -332,23 +318,23 @@ protected override void Initialize(bool useValidationLayer, QueueCount = 1, PQueuePriorities = queuePriorities }; - } - else if (graphicsQueueFamilyCount >= 3) - { + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); Queue computeQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); + Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); Queue transferQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); + Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); - queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, graphicsQueueFamilyIndex, transferQueue, graphicsQueueFamilyIndex); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, computeQueueFamilyIndex, transferQueue, transferQueueFamilyIndex); }; - + } + else if (graphicsQueueFamilyCount >= 3) + { queueCreateInfoCount = 1; float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 3); @@ -364,17 +350,23 @@ protected override void Initialize(bool useValidationLayer, QueueCount = 3, PQueuePriorities = queuePriorities }; - } - else - { + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); - queues = (graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex); - }; + Queue computeQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); + + Queue transferQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, graphicsQueueFamilyIndex, transferQueue, graphicsQueueFamilyIndex); + }; + } + else + { queueCreateInfoCount = 1; float* queuePriorities = (float*)ZenithMarshal.Allocate(scope, 1); @@ -388,6 +380,14 @@ protected override void Initialize(bool useValidationLayer, QueueCount = 1, PQueuePriorities = queuePriorities }; + + loadQueues = () => + { + Queue graphicsQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); + + queues = (graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex, graphicsQueue, graphicsQueueFamilyIndex); + }; } uint extensionCount = 0; From 0119f423032d8d9ae0c795b6396182e11c61f595 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:50:00 +0800 Subject: [PATCH 297/466] Add Vulkan validation layer support and minor DX refactor Implemented VKValidationLayer for Vulkan debug messaging, initializing it in VKGraphicsContext when validation is enabled. Also, refactored DXValidationLayer callback parameter for naming consistency. --- .../Zenith.NET.DirectX12/DXValidationLayer.cs | 4 +- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 3 +- .../Zenith.NET.Vulkan/VKValidationLayer.cs | 57 +++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKValidationLayer.cs diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index fcaf0dd9..5bb14255 100644 --- a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs +++ b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs @@ -36,13 +36,13 @@ protected override void Destroy() callback.Dispose(); } - private void Callback(MessageCategory category, DxMessageSeverity severity, MessageID messageID, byte* pDescription, void* context) + private void Callback(MessageCategory category, DxMessageSeverity severity, MessageID messageID, byte* description, void* context) { Report(severity switch { DxMessageSeverity.Error => MessageSeverity.Error, DxMessageSeverity.Warning => MessageSeverity.Warning, _ => MessageSeverity.Info - }, ZenithMarshal.StringFromPointer((nint)pDescription, StringEncoding.UTF8)); + }, ZenithMarshal.StringFromPointer((nint)description, StringEncoding.UTF8)); } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index d72714b3..75a5dde0 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -468,8 +468,7 @@ protected override void Initialize(bool useValidationLayer, graphicsQueue = new VKCommandQueue(this, CommandQueueType.Graphics, queues.GraphicsQueue, queues.GraphicsQueueFamilyIndex); computeQueue = new VKCommandQueue(this, CommandQueueType.Compute, queues.ComputeQueue, queues.ComputeQueueFamilyIndex); transferQueue = new VKCommandQueue(this, CommandQueueType.Transfer, queues.TransferQueue, queues.TransferQueueFamilyIndex); - - throw new NotImplementedException(); + validationLayer = useValidationLayer ? new VKValidationLayer(this) : null; } protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs new file mode 100644 index 00000000..91c4445b --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs @@ -0,0 +1,57 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKValidationLayer : ValidationLayer +{ + private readonly PfnDebugUtilsMessengerCallbackEXT callback; + private readonly DebugUtilsMessengerEXT messenger; + + public VKValidationLayer(VKGraphicsContext context) : base(context) + { + DebugUtilsMessengerCreateInfoEXT createInfo = new() + { + SType = StructureType.DebugUtilsMessengerCreateInfoExt, + MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt + | DebugUtilsMessageSeverityFlagsEXT.InfoBitExt + | DebugUtilsMessageSeverityFlagsEXT.WarningBitExt + | DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt, + MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt + | DebugUtilsMessageTypeFlagsEXT.ValidationBitExt + | DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt, + PfnUserCallback = callback = new(Callback) + }; + + context.DebugUtils?.CreateDebugUtilsMessenger(context.Instance, &createInfo, default, out messenger).Success(); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + Context.DebugUtils?.DestroyDebugUtilsMessenger(Context.Instance, messenger, null); + + callback.Dispose(); + } + + private uint Callback(DebugUtilsMessageSeverityFlagsEXT severity, DebugUtilsMessageTypeFlagsEXT types, DebugUtilsMessengerCallbackDataEXT* callbackData, void* userData) + { + Report(severity switch + { + DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => MessageSeverity.Error, + DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => MessageSeverity.Warning, + _ => MessageSeverity.Info + }, ZenithMarshal.StringFromPointer((nint)callbackData->PMessage, StringEncoding.UTF8)); + + return Vk.False; + } +} From 49ee3b73ff1d7113f997ca4e6f1de565ec89c3f3 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:51:00 +0800 Subject: [PATCH 298/466] Replace null with default in DestroyDebugUtilsMessenger call Updated the Destroy method in VKValidationLayer to use default instead of null for the third argument in DestroyDebugUtilsMessenger. This change improves type safety and aligns with API expectations for value types. --- sources/Zenith.NET.Vulkan/VKValidationLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs index 91c4445b..1757cc43 100644 --- a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs +++ b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs @@ -38,7 +38,7 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.DebugUtils?.DestroyDebugUtilsMessenger(Context.Instance, messenger, null); + Context.DebugUtils?.DestroyDebugUtilsMessenger(Context.Instance, messenger, default); callback.Dispose(); } From 4bf250ef8fa662dfb05e638bdda42c809df656b4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:56:44 +0800 Subject: [PATCH 299/466] Destroy Vulkan device and instance in VKGraphicsContext Explicitly call Vk.DestroyDevice and Vk.DestroyInstance in the Destroy() method to ensure proper cleanup of Vulkan resources before disposing of the Vk object. --- sources/Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 75a5dde0..9ba00364 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -550,6 +550,8 @@ protected override void Destroy() { base.Destroy(); + Vk.DestroyDevice(Device, null); + Vk.DestroyInstance(Instance, null); Vk.Dispose(); } } \ No newline at end of file From dd8d16e647e54c5f81295b6748cd0e64d5f15896 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 26 Jun 2026 18:57:11 +0800 Subject: [PATCH 300/466] Use default instead of null in Vulkan destroy calls Replaced null with default for Vk.DestroyDevice and Vk.DestroyInstance parameters to improve type safety and clarity, following modern C# practices for unmanaged types. --- sources/Zenith.NET.Vulkan/VKGraphicsContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 9ba00364..a2b81d7a 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -550,8 +550,8 @@ protected override void Destroy() { base.Destroy(); - Vk.DestroyDevice(Device, null); - Vk.DestroyInstance(Instance, null); + Vk.DestroyDevice(Device, default); + Vk.DestroyInstance(Instance, default); Vk.Dispose(); } } \ No newline at end of file From 04edc08b3f4b8f1515ee2ae6d7eb466a969ca55f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 29 Jun 2026 15:55:07 +0800 Subject: [PATCH 301/466] Add VKHeap class and integrate with VKGraphicsContext Introduced the VKHeap class for Vulkan-specific heap management, including resource creation stubs, debug name assignment, and destruction. Updated VKGraphicsContext to instantiate VKHeap in CreateHeapImpl. Added a Vulkan() extension method for Heap to enable safe VKHeap casting. --- sources/Zenith.NET.Vulkan/Extensions.cs | 4 ++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 +- sources/Zenith.NET.Vulkan/VKHeap.cs | 50 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 sources/Zenith.NET.Vulkan/VKHeap.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index abfc644f..91fef274 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -72,6 +72,10 @@ internal void AddNext(out TNext next) extension(Heap heap) { + internal VKHeap Vulkan() + { + return (VKHeap)heap; + } } extension(MeshShadingPipeline meshShadingPipeline) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index a2b81d7a..7395d554 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -478,7 +478,7 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) protected override Heap CreateHeapImpl(HeapDesc desc) { - throw new NotImplementedException(); + return new VKHeap(this, desc); } protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs new file mode 100644 index 00000000..700995ee --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -0,0 +1,50 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKHeap : Heap +{ + public DeviceMemory DeviceMemory; + + public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) + { + throw new NotImplementedException(); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + throw new NotImplementedException(); + } + + protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) + { + throw new NotImplementedException(); + } + + protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) + { + throw new NotImplementedException(); + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.DeviceMemory, + ObjectHandle = DeviceMemory.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.FreeMemory(Context.Device, DeviceMemory, default); + } +} From 579aadeeaa6ddc871cab04b5f32db4dcc651fe4b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 16:00:54 +0800 Subject: [PATCH 302/466] Use heap residency in CreateBufferImpl Updated CreateBufferImpl to use Desc.Residency instead of desc.Residency when creating buffers, ensuring residency is sourced from the heap descriptor. --- sources/Zenith.NET.Metal/MTLHeap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs index 27a4a38f..2580a20c 100644 --- a/sources/Zenith.NET.Metal/MTLHeap.cs +++ b/sources/Zenith.NET.Metal/MTLHeap.cs @@ -25,7 +25,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { - return new MTLBuffer(Context, desc, Heap.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(desc.Residency), (nuint)offsetInBytes)); + return new MTLBuffer(Context, desc, Heap.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(Desc.Residency), (nuint)offsetInBytes)); } protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) From ee1b56cf2724c058589d669acc6871e5e5f7ddda Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 17:33:36 +0800 Subject: [PATCH 303/466] Add Vulkan usage mapping and memory allocation helpers Introduce utility methods for mapping buffer and texture usages to Vulkan flags, selecting memory types, and determining sharing modes. Implement device memory allocation in VKHeap using these helpers. Add internal extension for VKCommandQueue access. --- sources/Zenith.NET.Vulkan/Extensions.cs | 4 + sources/Zenith.NET.Vulkan/VKFormats.cs | 82 ++++++++++++++++++- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 57 +++++++++++++ sources/Zenith.NET.Vulkan/VKHeap.cs | 12 ++- 4 files changed, 153 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 91fef274..800b731e 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -60,6 +60,10 @@ internal void AddNext(out TNext next) extension(CommandQueue commandQueue) { + internal VKCommandQueue Vulkan() + { + return (VKCommandQueue)commandQueue; + } } extension(ComputePipeline computePipeline) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 281d17b9..17089ccf 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -1,5 +1,85 @@ -namespace Zenith.NET.Vulkan; +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; internal static class VKFormats { + public static BufferUsageFlags Vulkan(BufferUsages bufferUsages) + { + BufferUsageFlags result = BufferUsageFlags.ShaderDeviceAddressBit; + + if (bufferUsages.HasFlag(BufferUsages.Vertex)) + { + result |= BufferUsageFlags.VertexBufferBit; + } + + if (bufferUsages.HasFlag(BufferUsages.Index)) + { + result |= BufferUsageFlags.IndexBufferBit; + } + + if (bufferUsages.HasFlag(BufferUsages.Indirect)) + { + result |= BufferUsageFlags.IndirectBufferBit; + } + + if (bufferUsages.HasFlag(BufferUsages.Constant)) + { + result |= BufferUsageFlags.UniformBufferBit; + } + + if (bufferUsages.HasFlag(BufferUsages.StorageReadOnly) || bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) + { + result |= BufferUsageFlags.StorageBufferBit; + } + + if (bufferUsages.HasFlag(BufferUsages.CopySrc)) + { + result |= BufferUsageFlags.TransferSrcBit; + } + + if (bufferUsages.HasFlag(BufferUsages.CopyDst)) + { + result |= BufferUsageFlags.TransferDstBit; + } + + return result; + } + + public static ImageUsageFlags Vulkan(TextureUsages textureUsages) + { + ImageUsageFlags result = default; + + if (textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= ImageUsageFlags.SampledBit; + } + + if (textureUsages.HasFlag(TextureUsages.Storage)) + { + result |= ImageUsageFlags.StorageBit; + } + + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) + { + result |= ImageUsageFlags.ColorAttachmentBit; + } + + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) + { + result |= ImageUsageFlags.DepthStencilAttachmentBit; + } + + if (textureUsages.HasFlag(TextureUsages.CopySrc)) + { + result |= ImageUsageFlags.TransferSrcBit; + } + + if (textureUsages.HasFlag(TextureUsages.CopyDst)) + { + result |= ImageUsageFlags.TransferDstBit; + } + + return result; + } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 7395d554..015b2402 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -83,6 +83,63 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrSwapchain? Swapchain { get; private set; } + public (SharingMode SharingMode, uint QueueFamilyIndexCount, nint PQueueFamilyIndices) GetSharingModeInfo(ZenithMarshal.Scope scope) + { + HashSet queueFamilyIndices = + [ + GraphicsQueue.Vulkan().QueueFamilyIndex, + ComputeQueue.Vulkan().QueueFamilyIndex, + TransferQueue.Vulkan().QueueFamilyIndex + ]; + + if (queueFamilyIndices.Count is 1) + { + return (SharingMode.Exclusive, 0, 0); + } + else + { + return (SharingMode.Concurrent, (uint)queueFamilyIndices.Count, ZenithMarshal.AllocateAndFill(scope, [.. queueFamilyIndices])); + } + } + + public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryResidency residency) + { + PhysicalDeviceMemoryProperties properties; + Vk.GetPhysicalDeviceMemoryProperties(PhysicalDevice, &properties); + + MemoryPropertyFlags[] candidates = residency switch + { + MemoryResidency.GpuOnly => + [ + MemoryPropertyFlags.DeviceLocalBit + ], + MemoryResidency.CpuWriteOnly => + [ + MemoryPropertyFlags.DeviceLocalBit | MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, + MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit + ], + MemoryResidency.CpuReadOnly => + [ + MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit | MemoryPropertyFlags.HostCachedBit, + MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit + ], + _ => [] + }; + + foreach (MemoryPropertyFlags propertyFlags in candidates) + { + for (uint index = 0; index < properties.MemoryTypeCount; index++) + { + if ((memoryTypeBits & (1u << (int)index)) is not 0 && (properties.MemoryTypes[(int)index].PropertyFlags & propertyFlags) == propertyFlags) + { + return index; + } + } + } + + return 0; + } + public override nint GetNativeObject(NativeObjectType type) { return 0; diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index 700995ee..32b2f8cb 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -8,7 +8,17 @@ internal unsafe class VKHeap : Heap public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) { - throw new NotImplementedException(); + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = desc.SizeInBytes, + MemoryTypeIndex = context.FindMemoryTypeIndex(uint.MaxValue, desc.Residency) + }; + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); + allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; From 9fb705b827b141382e2928a645caa43e88da3b19 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 17:54:47 +0800 Subject: [PATCH 304/466] Replace CopySrc/CopyDst with TransferSrc/TransferDst Update all buffer and texture usage flags from CopySrc/CopyDst to TransferSrc/TransferDst for consistency with modern graphics API conventions. This includes changes to enums, descriptor initializations, and Vulkan format mappings across the codebase. --- .../CornellBox/Renderers/PathTracingRenderer.cs | 4 ++-- sources/Views/Zenith.NET.Views.Avalonia/Surface.cs | 2 +- .../Platforms/Windows/Surface.cs | 2 +- sources/Views/Zenith.NET.Views.WPF/Surface.cs | 2 +- .../Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs | 2 +- .../Zenith.NET.Views.WinUI/ZenithView.WinUI.cs | 2 +- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 2 +- sources/Zenith.NET.Metal/MTLSwapChain.cs | 2 +- sources/Zenith.NET.Vulkan/VKFormats.cs | 8 ++++---- sources/Zenith.NET/Downloader.cs | 2 +- sources/Zenith.NET/Enums/BufferUsages.cs | 4 ++-- sources/Zenith.NET/Enums/TextureUsages.cs | 4 ++-- sources/Zenith.NET/Structs/BufferDesc.cs | 14 +++++++------- sources/Zenith.NET/Structs/TextureDesc.cs | 14 +++++++------- sources/Zenith.NET/Uploader.cs | 2 +- 15 files changed, 33 insertions(+), 33 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index e05e9aa8..9a7a1bbb 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -32,7 +32,7 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), StrideInBytes = (uint)sizeof(Vertex), - Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst, + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }); @@ -49,7 +49,7 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(uint) * indices.Length), StrideInBytes = sizeof(uint), - Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst, + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }); diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 6be17788..88083a22 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -16,7 +16,7 @@ internal class Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }); public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), DrawableFormat(), AlphaFormat.Premul); 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 10184344..52163433 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -69,7 +69,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } diff --git a/sources/Views/Zenith.NET.Views.WPF/Surface.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs index ddebb675..332cd3c7 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Surface.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -76,7 +76,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs index 6266b47f..46782e30 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -64,7 +64,7 @@ internal unsafe class Surface(GraphicsContext context, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }); public WriteableBitmap Bitmap { get; } = new((int)width, (int)height); diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index ff46b8ad..1b674c81 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -162,7 +162,7 @@ public Surface(GraphicsContext graphicsContext, uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index ec0b7bb0..5c0483e4 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -104,7 +104,7 @@ private void CreateTextures() MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }; for (int i = 0; i < textures.Length; i++) diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index adac5218..06a69f31 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -33,7 +33,7 @@ public override Texture Drawable MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.ColorAttachment | TextureUsages.CopyDst + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst }; drawable?.Dispose(); diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 17089ccf..397cfdc9 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -33,12 +33,12 @@ public static BufferUsageFlags Vulkan(BufferUsages bufferUsages) result |= BufferUsageFlags.StorageBufferBit; } - if (bufferUsages.HasFlag(BufferUsages.CopySrc)) + if (bufferUsages.HasFlag(BufferUsages.TransferSrc)) { result |= BufferUsageFlags.TransferSrcBit; } - if (bufferUsages.HasFlag(BufferUsages.CopyDst)) + if (bufferUsages.HasFlag(BufferUsages.TransferDst)) { result |= BufferUsageFlags.TransferDstBit; } @@ -70,12 +70,12 @@ public static ImageUsageFlags Vulkan(TextureUsages textureUsages) result |= ImageUsageFlags.DepthStencilAttachmentBit; } - if (textureUsages.HasFlag(TextureUsages.CopySrc)) + if (textureUsages.HasFlag(TextureUsages.TransferSrc)) { result |= ImageUsageFlags.TransferSrcBit; } - if (textureUsages.HasFlag(TextureUsages.CopyDst)) + if (textureUsages.HasFlag(TextureUsages.TransferDst)) { result |= ImageUsageFlags.TransferDstBit; } diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs index 75cfb04d..c9955133 100644 --- a/sources/Zenith.NET/Downloader.cs +++ b/sources/Zenith.NET/Downloader.cs @@ -22,7 +22,7 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, TransferLayo lease = new(context.CreateBuffer(new() { SizeInBytes = sizeInBytes, - Usages = BufferUsages.CopyDst, + Usages = BufferUsages.TransferDst, Residency = MemoryResidency.CpuReadOnly })); } diff --git a/sources/Zenith.NET/Enums/BufferUsages.cs b/sources/Zenith.NET/Enums/BufferUsages.cs index fa8eb33d..025507ce 100644 --- a/sources/Zenith.NET/Enums/BufferUsages.cs +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -17,7 +17,7 @@ public enum BufferUsages StorageReadWrite = 1 << 5, - CopySrc = 1 << 6, + TransferSrc = 1 << 6, - CopyDst = 1 << 7 + TransferDst = 1 << 7 } diff --git a/sources/Zenith.NET/Enums/TextureUsages.cs b/sources/Zenith.NET/Enums/TextureUsages.cs index c098c78f..3ada0a1e 100644 --- a/sources/Zenith.NET/Enums/TextureUsages.cs +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -13,7 +13,7 @@ public enum TextureUsages DepthStencilAttachment = 1 << 3, - CopySrc = 1 << 4, + TransferSrc = 1 << 4, - CopyDst = 1 << 5 + TransferDst = 1 << 5 } diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index 73a727a8..594f9317 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -16,7 +16,7 @@ public static BufferDesc Vertex(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Usages = BufferUsages.Vertex | BufferUsages.CopyDst, + Usages = BufferUsages.Vertex | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }; } @@ -27,7 +27,7 @@ public static BufferDesc Index(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Usages = BufferUsages.Index | BufferUsages.CopyDst, + Usages = BufferUsages.Index | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }; } @@ -38,7 +38,7 @@ public static BufferDesc Indirect(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Usages = BufferUsages.Indirect | BufferUsages.CopyDst, + Usages = BufferUsages.Indirect | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }; } @@ -49,7 +49,7 @@ public static BufferDesc Constant(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Usages = BufferUsages.Constant | BufferUsages.CopyDst, + Usages = BufferUsages.Constant | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }; } @@ -60,7 +60,7 @@ public static BufferDesc StorageReadOnly(uint sizeInBytes, uint strideInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, - Usages = BufferUsages.StorageReadOnly | BufferUsages.CopyDst, + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }; } @@ -71,7 +71,7 @@ public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = strideInBytes, - Usages = BufferUsages.StorageReadWrite | BufferUsages.CopyDst, + Usages = BufferUsages.StorageReadWrite | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }; } @@ -82,7 +82,7 @@ public static BufferDesc Staging(uint sizeInBytes) { SizeInBytes = sizeInBytes, StrideInBytes = 0, - Usages = BufferUsages.CopySrc, + Usages = BufferUsages.TransferSrc, Residency = MemoryResidency.CpuWriteOnly }; } diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index e26d07a5..3b1e01c0 100644 --- a/sources/Zenith.NET/Structs/TextureDesc.cs +++ b/sources/Zenith.NET/Structs/TextureDesc.cs @@ -32,7 +32,7 @@ public static TextureDesc Texture1D(PixelFormat format, uint width, uint mipLeve MipLevels = mipLevels, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } @@ -48,7 +48,7 @@ public static TextureDesc Texture1DArray(PixelFormat format, uint width, uint ar MipLevels = mipLevels, ArrayLayers = arrayLayers, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } @@ -64,7 +64,7 @@ public static TextureDesc Texture2D(PixelFormat format, uint width, uint height, MipLevels = mipLevels, ArrayLayers = 1, SampleCount = sampleCount, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } @@ -80,7 +80,7 @@ public static TextureDesc Texture2DArray(PixelFormat format, uint width, uint he MipLevels = mipLevels, ArrayLayers = arrayLayers, SampleCount = sampleCount, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } @@ -96,7 +96,7 @@ public static TextureDesc Texture3D(PixelFormat format, uint width, uint height, MipLevels = mipLevels, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } @@ -112,7 +112,7 @@ public static TextureDesc TextureCube(PixelFormat format, uint size, uint mipLev MipLevels = mipLevels, ArrayLayers = 6, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } @@ -128,7 +128,7 @@ public static TextureDesc TextureCubeArray(PixelFormat format, uint size, uint c MipLevels = mipLevels, ArrayLayers = cubeCount * 6, SampleCount = SampleCount.Count1, - Usages = TextureUsages.Sampled | TextureUsages.CopyDst + Usages = TextureUsages.Sampled | TextureUsages.TransferDst }; } diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index e3048d7f..e5e1ecaa 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -22,7 +22,7 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, TransferLayo lease = new(context.CreateBuffer(new() { SizeInBytes = sizeInBytes, - Usages = BufferUsages.CopySrc, + Usages = BufferUsages.TransferSrc, Residency = MemoryResidency.CpuWriteOnly })); } From cab7b726a4b177978e97eca76c4bd65ea911b98f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 18:00:14 +0800 Subject: [PATCH 305/466] Implement debug naming and update GetNativeObject methods VKCommandQueue now supports debug naming via Vulkan DebugUtils. GetNativeObject in VKCommandQueue and VKHeap returns 0 instead of throwing. Added strongly-typed Context property to VKCommandQueue. --- sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 15 ++++++++++++++- sources/Zenith.NET.Vulkan/VKHeap.cs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 1d1db80f..2ac5974b 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -2,12 +2,14 @@ namespace Zenith.NET.Vulkan; -internal class VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : CommandQueue(context, type) +internal unsafe class VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : CommandQueue(context, type) { public Queue Queue = queue; public uint QueueFamilyIndex = queueFamilyIndex; + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -45,5 +47,16 @@ protected override void WaitImpl(ReadOnlySpan submissions) protected override void SetResourceName(string name) { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Queue, + ObjectHandle = (ulong)Queue.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } } diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index 32b2f8cb..d9e267a7 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -25,7 +25,7 @@ public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) public override nint GetNativeObject(NativeObjectType type) { - throw new NotImplementedException(); + return 0; } protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) From 103879f9a03aa485c03a32df3110f1cc39579fad Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 18:24:49 +0800 Subject: [PATCH 306/466] Add VKBuffer class for Vulkan buffer management Introduced VKBuffer in Zenith.NET.Vulkan to handle Vulkan buffer creation, memory allocation, mapping, and destruction. The class manages device memory, supports dedicated allocations, device address retrieval, and debug naming, with logic for both owning and non-owning memory. Added a global using alias for VkBuffer in Usings.cs. --- sources/Zenith.NET.Vulkan/Usings.cs | 2 +- sources/Zenith.NET.Vulkan/VKBuffer.cs | 147 ++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 sources/Zenith.NET.Vulkan/VKBuffer.cs diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 5f282702..51a7c2f6 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -1 +1 @@ - \ No newline at end of file +global using VkBuffer = Silk.NET.Vulkan.Buffer; \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs new file mode 100644 index 00000000..c5a03c69 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -0,0 +1,147 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKBuffer : Buffer +{ + private readonly bool ownsMemory; + + public VkBuffer Buffer; + + public DeviceMemory DeviceMemory; + + public ulong OffsetInBytes; + + public ulong DeviceAddress; + + public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) + { + ownsMemory = true; + + using ZenithMarshal.Scope scope = new(); + + (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); + + BufferCreateInfo createInfo = new() + { + SType = StructureType.BufferCreateInfo, + Size = desc.SizeInBytes, + Usage = VKFormats.Vulkan(desc.Usages), + SharingMode = sharingMode, + QueueFamilyIndexCount = queueFamilyIndexCount, + PQueueFamilyIndices = (uint*)pQueueFamilyIndices + }; + + context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); + + BufferMemoryRequirementsInfo2 requirementsInfo2 = new() + { + SType = StructureType.BufferMemoryRequirementsInfo2, + Buffer = Buffer + }; + + MemoryRequirements2 requirements2 = new() + { + SType = StructureType.MemoryRequirements2 + }; + + requirements2.AddNext(out MemoryDedicatedRequirements requirements); + + context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); + + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) + }; + + if (requirements.PrefersDedicatedAllocation || requirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Buffer = Buffer; + } + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); + allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); + context.Vk.BindBufferMemory(context.Device, Buffer, DeviceMemory, 0).Success(); + + BufferDeviceAddressInfo deviceAddressInfo = new() + { + SType = StructureType.BufferDeviceAddressInfo, + Buffer = Buffer + }; + + DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); + } + + public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, DeviceMemory deviceMemory, ulong offsetInBytes) : base(context, desc) + { + ownsMemory = false; + + Buffer = buffer; + DeviceMemory = deviceMemory; + OffsetInBytes = offsetInBytes; + + BufferDeviceAddressInfo deviceAddressInfo = new() + { + SType = StructureType.BufferDeviceAddressInfo, + Buffer = Buffer + }; + + DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override ResourceHandle ConstantHandle { get; } + + public override ResourceHandle StorageReadOnlyHandle { get; } + + public override ResourceHandle StorageReadWriteHandle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + public override nint Map() + { + void* pointer; + Context.Vk.MapMemory(Context.Device, DeviceMemory, OffsetInBytes, Desc.SizeInBytes, MemoryMapFlags.None, &pointer).Success(); + + return (nint)pointer; + } + + public override void Unmap() + { + Context.Vk.UnmapMemory(Context.Device, DeviceMemory); + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Buffer, + ObjectHandle = Buffer.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyBuffer(Context.Device, Buffer, default); + + if (ownsMemory) + { + Context.Vk.FreeMemory(Context.Device, DeviceMemory, default); + } + } +} From d26384bed95b850b80bde605039bf9a3d5f16779 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 22:00:08 +0800 Subject: [PATCH 307/466] Refactor Vulkan buffer and memory management VKBuffer now delegates memory allocation and binding to VKHeap, removing direct memory management from VKBuffer. Introduce VKSharing struct for sharing mode info, centralize BufferCreateInfo construction, and implement buffer creation logic in VKGraphicsContext. Add extension for Buffer-to-VKBuffer casting and mark memory mapping as not implemented. These changes modularize resource management and improve sharing mode handling. --- sources/Zenith.NET.Vulkan/Extensions.cs | 4 + sources/Zenith.NET.Vulkan/VKBuffer.cs | 87 +++++-------------- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 12 +-- sources/Zenith.NET.Vulkan/VKHeap.cs | 42 ++++++++- sources/Zenith.NET.Vulkan/VKSharing.cs | 12 +++ 5 files changed, 86 insertions(+), 71 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKSharing.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 800b731e..c705638d 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -48,6 +48,10 @@ internal void AddNext(out TNext next) extension(Buffer buffer) { + internal VKBuffer Vulkan() + { + return (VKBuffer)buffer; + } } extension(BufferView bufferView) diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index c5a03c69..d29aaa20 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -4,69 +4,23 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKBuffer : Buffer { - private readonly bool ownsMemory; - public VkBuffer Buffer; - public DeviceMemory DeviceMemory; - - public ulong OffsetInBytes; - public ulong DeviceAddress; public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) { - ownsMemory = true; - using ZenithMarshal.Scope scope = new(); - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); - - BufferCreateInfo createInfo = new() - { - SType = StructureType.BufferCreateInfo, - Size = desc.SizeInBytes, - Usage = VKFormats.Vulkan(desc.Usages), - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices - }; + BufferCreateInfo createInfo = CreateInfo(desc, context.GetSharing(scope)); context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); - BufferMemoryRequirementsInfo2 requirementsInfo2 = new() - { - SType = StructureType.BufferMemoryRequirementsInfo2, - Buffer = Buffer - }; - - MemoryRequirements2 requirements2 = new() - { - SType = StructureType.MemoryRequirements2 - }; - - requirements2.AddNext(out MemoryDedicatedRequirements requirements); - - context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - - MemoryAllocateInfo allocateInfo = new() - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = requirements2.MemoryRequirements.Size, - MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) - }; - - if (requirements.PrefersDedicatedAllocation || requirements.RequiresDedicatedAllocation) + Heap = new(context, new() { - allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); - dedicatedAllocateInfo.Buffer = Buffer; - } - - allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); - allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; - - context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); - context.Vk.BindBufferMemory(context.Device, Buffer, DeviceMemory, 0).Success(); + SizeInBytes = desc.SizeInBytes, + Residency = desc.Residency + }, Buffer); BufferDeviceAddressInfo deviceAddressInfo = new() { @@ -77,13 +31,9 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); } - public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, DeviceMemory deviceMemory, ulong offsetInBytes) : base(context, desc) + public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer) : base(context, desc) { - ownsMemory = false; - Buffer = buffer; - DeviceMemory = deviceMemory; - OffsetInBytes = offsetInBytes; BufferDeviceAddressInfo deviceAddressInfo = new() { @@ -96,6 +46,8 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, Dev public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + public VKHeap? Heap { get; } + public override ResourceHandle ConstantHandle { get; } public override ResourceHandle StorageReadOnlyHandle { get; } @@ -109,15 +61,12 @@ public override nint GetNativeObject(NativeObjectType type) public override nint Map() { - void* pointer; - Context.Vk.MapMemory(Context.Device, DeviceMemory, OffsetInBytes, Desc.SizeInBytes, MemoryMapFlags.None, &pointer).Success(); - - return (nint)pointer; + throw new NotImplementedException(); } public override void Unmap() { - Context.Vk.UnmapMemory(Context.Device, DeviceMemory); + throw new NotImplementedException(); } protected override void SetResourceName(string name) @@ -139,9 +88,19 @@ protected override void Destroy() { Context.Vk.DestroyBuffer(Context.Device, Buffer, default); - if (ownsMemory) + Heap?.Dispose(); + } + + public static BufferCreateInfo CreateInfo(BufferDesc desc, VKSharing sharing) + { + return new() { - Context.Vk.FreeMemory(Context.Device, DeviceMemory, default); - } + SType = StructureType.BufferCreateInfo, + Size = desc.SizeInBytes, + Usage = VKFormats.Vulkan(desc.Usages), + SharingMode = sharing.Mode, + QueueFamilyIndexCount = sharing.Count, + PQueueFamilyIndices = sharing.Indices + }; } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 015b2402..0c8a42d5 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -83,22 +83,22 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrSwapchain? Swapchain { get; private set; } - public (SharingMode SharingMode, uint QueueFamilyIndexCount, nint PQueueFamilyIndices) GetSharingModeInfo(ZenithMarshal.Scope scope) + public VKSharing GetSharing(ZenithMarshal.Scope scope) { - HashSet queueFamilyIndices = + HashSet indices = [ GraphicsQueue.Vulkan().QueueFamilyIndex, ComputeQueue.Vulkan().QueueFamilyIndex, TransferQueue.Vulkan().QueueFamilyIndex ]; - if (queueFamilyIndices.Count is 1) + if (indices.Count is 1) { - return (SharingMode.Exclusive, 0, 0); + return new(SharingMode.Exclusive, null, 0); } else { - return (SharingMode.Concurrent, (uint)queueFamilyIndices.Count, ZenithMarshal.AllocateAndFill(scope, [.. queueFamilyIndices])); + return new(SharingMode.Concurrent, (uint*)ZenithMarshal.AllocateAndFill(scope, [.. indices]), (uint)indices.Count); } } @@ -550,7 +550,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) protected override Buffer CreateBufferImpl(BufferDesc desc) { - throw new NotImplementedException(); + return new VKBuffer(this, desc); } protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index d9e267a7..c5d1723b 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -21,6 +21,39 @@ public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); } + public VKHeap(VKGraphicsContext context, HeapDesc desc, VkBuffer buffer) : base(context, desc) + { + BufferMemoryRequirementsInfo2 requirementsInfo2 = new() + { + SType = StructureType.BufferMemoryRequirementsInfo2, + Buffer = buffer + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); + + context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); + + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) + }; + + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Buffer = buffer; + } + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); + allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); + context.Vk.BindBufferMemory(context.Device, buffer, DeviceMemory, 0).Success(); + } + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; public override nint GetNativeObject(NativeObjectType type) @@ -30,7 +63,14 @@ public override nint GetNativeObject(NativeObjectType type) protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { - throw new NotImplementedException(); + using ZenithMarshal.Scope scope = new(); + + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Context.GetSharing(scope)); + + Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); + Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); + + return new VKBuffer(Context, desc, buffer); } protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKSharing.cs b/sources/Zenith.NET.Vulkan/VKSharing.cs new file mode 100644 index 00000000..dc9fd6ff --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKSharing.cs @@ -0,0 +1,12 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe readonly struct VKSharing(SharingMode mode, uint* indices, uint count) +{ + public readonly SharingMode Mode = mode; + + public readonly uint* Indices = indices; + + public readonly uint Count = count; +} From 1250281c644640caaa44e31810c6eafc23acfa8a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 22:15:30 +0800 Subject: [PATCH 308/466] Refactor Vulkan buffer sharing, remove VKSharing struct Simplify buffer sharing by removing VKSharing and managing queue family indices directly in VKGraphicsContext. Buffer creation now uses queue family index count and pointer, with sharing mode determined inline. Centralizes and streamlines queue family management. --- sources/Zenith.NET.Vulkan/VKBuffer.cs | 10 ++++----- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 21 +++---------------- sources/Zenith.NET.Vulkan/VKHeap.cs | 2 +- sources/Zenith.NET.Vulkan/VKSharing.cs | 12 ----------- 4 files changed, 9 insertions(+), 36 deletions(-) delete mode 100644 sources/Zenith.NET.Vulkan/VKSharing.cs diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index d29aaa20..ee7501d8 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -12,7 +12,7 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc { using ZenithMarshal.Scope scope = new(); - BufferCreateInfo createInfo = CreateInfo(desc, context.GetSharing(scope)); + BufferCreateInfo createInfo = CreateInfo(desc, (uint)context.QueueFamilyIndices.Length, (uint*)ZenithMarshal.AllocateAndFill(scope, [.. context.QueueFamilyIndices])); context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); @@ -91,16 +91,16 @@ protected override void Destroy() Heap?.Dispose(); } - public static BufferCreateInfo CreateInfo(BufferDesc desc, VKSharing sharing) + public static BufferCreateInfo CreateInfo(BufferDesc desc, uint queueFamilyIndexCount, uint* pQueueFamilyIndices) { return new() { SType = StructureType.BufferCreateInfo, Size = desc.SizeInBytes, Usage = VKFormats.Vulkan(desc.Usages), - SharingMode = sharing.Mode, - QueueFamilyIndexCount = sharing.Count, - PQueueFamilyIndices = sharing.Indices + SharingMode = queueFamilyIndexCount is 1 ? SharingMode.Exclusive : SharingMode.Concurrent, + QueueFamilyIndexCount = queueFamilyIndexCount, + PQueueFamilyIndices = pQueueFamilyIndices }; } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 0c8a42d5..4e5c2bf5 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -83,24 +83,7 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrSwapchain? Swapchain { get; private set; } - public VKSharing GetSharing(ZenithMarshal.Scope scope) - { - HashSet indices = - [ - GraphicsQueue.Vulkan().QueueFamilyIndex, - ComputeQueue.Vulkan().QueueFamilyIndex, - TransferQueue.Vulkan().QueueFamilyIndex - ]; - - if (indices.Count is 1) - { - return new(SharingMode.Exclusive, null, 0); - } - else - { - return new(SharingMode.Concurrent, (uint*)ZenithMarshal.AllocateAndFill(scope, [.. indices]), (uint)indices.Count); - } - } + public uint[] QueueFamilyIndices { get; private set; } = []; public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryResidency residency) { @@ -519,6 +502,8 @@ protected override void Initialize(bool useValidationLayer, Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; loadQueues(); + + QueueFamilyIndices = [.. new HashSet() { queues.GraphicsQueueFamilyIndex, queues.ComputeQueueFamilyIndex, queues.TransferQueueFamilyIndex }]; } capabilities = new VKCapabilities(this); diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index c5d1723b..4f150fbe 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -65,7 +65,7 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { using ZenithMarshal.Scope scope = new(); - BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Context.GetSharing(scope)); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, (uint)Context.QueueFamilyIndices.Length, (uint*)ZenithMarshal.AllocateAndFill(scope, [.. Context.QueueFamilyIndices])); Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKSharing.cs b/sources/Zenith.NET.Vulkan/VKSharing.cs deleted file mode 100644 index dc9fd6ff..00000000 --- a/sources/Zenith.NET.Vulkan/VKSharing.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe readonly struct VKSharing(SharingMode mode, uint* indices, uint count) -{ - public readonly SharingMode Mode = mode; - - public readonly uint* Indices = indices; - - public readonly uint Count = count; -} From 669fe3e35050b57503b11b8bba0553d6c3e0ebca Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 23:14:54 +0800 Subject: [PATCH 309/466] Refactor VKBuffer memory management, add VKAllocation Refactored VKBuffer to manage its own memory allocation using the new VKAllocation struct, removing buffer-specific logic from VKHeap. Implemented Map/Unmap for direct memory access and improved resource cleanup with allocation ownership checks. --- sources/Zenith.NET.Vulkan/VKAllocation.cs | 12 +++++ sources/Zenith.NET.Vulkan/VKBuffer.cs | 53 ++++++++++++++++++----- sources/Zenith.NET.Vulkan/VKHeap.cs | 35 +-------------- 3 files changed, 56 insertions(+), 44 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKAllocation.cs diff --git a/sources/Zenith.NET.Vulkan/VKAllocation.cs b/sources/Zenith.NET.Vulkan/VKAllocation.cs new file mode 100644 index 00000000..6dc2b252 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKAllocation.cs @@ -0,0 +1,12 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal readonly struct VKAllocation(DeviceMemory deviceMemory, ulong offsetInBytes, bool isOwned) +{ + public readonly DeviceMemory DeviceMemory = deviceMemory; + + public readonly ulong OffsetInBytes = offsetInBytes; + + public readonly bool IsOwned = isOwned; +} diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index ee7501d8..5f975da6 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -6,6 +6,8 @@ internal unsafe class VKBuffer : Buffer { public VkBuffer Buffer; + public VKAllocation Allocation; + public ulong DeviceAddress; public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) @@ -16,11 +18,37 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); - Heap = new(context, new() + BufferMemoryRequirementsInfo2 requirementsInfo2 = new() + { + SType = StructureType.BufferMemoryRequirementsInfo2, + Buffer = Buffer + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); + + context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); + + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) + }; + + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) { - SizeInBytes = desc.SizeInBytes, - Residency = desc.Residency - }, Buffer); + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Buffer = Buffer; + } + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); + allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); + context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); + + Allocation = new(deviceMemory, 0, true); BufferDeviceAddressInfo deviceAddressInfo = new() { @@ -31,9 +59,10 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); } - public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer) : base(context, desc) + public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, VKAllocation allocation) : base(context, desc) { Buffer = buffer; + Allocation = allocation; BufferDeviceAddressInfo deviceAddressInfo = new() { @@ -46,8 +75,6 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer) : b public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public VKHeap? Heap { get; } - public override ResourceHandle ConstantHandle { get; } public override ResourceHandle StorageReadOnlyHandle { get; } @@ -61,12 +88,15 @@ public override nint GetNativeObject(NativeObjectType type) public override nint Map() { - throw new NotImplementedException(); + void* pointer; + Context.Vk.MapMemory(Context.Device, Allocation.DeviceMemory, Allocation.OffsetInBytes, Desc.SizeInBytes, MemoryMapFlags.None, &pointer).Success(); + + return (nint)pointer; } public override void Unmap() { - throw new NotImplementedException(); + Context.Vk.UnmapMemory(Context.Device, Allocation.DeviceMemory); } protected override void SetResourceName(string name) @@ -88,7 +118,10 @@ protected override void Destroy() { Context.Vk.DestroyBuffer(Context.Device, Buffer, default); - Heap?.Dispose(); + if (Allocation.IsOwned) + { + Context.Vk.FreeMemory(Context.Device, Allocation.DeviceMemory, default); + } } public static BufferCreateInfo CreateInfo(BufferDesc desc, uint queueFamilyIndexCount, uint* pQueueFamilyIndices) diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index 4f150fbe..a01ee212 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -21,39 +21,6 @@ public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); } - public VKHeap(VKGraphicsContext context, HeapDesc desc, VkBuffer buffer) : base(context, desc) - { - BufferMemoryRequirementsInfo2 requirementsInfo2 = new() - { - SType = StructureType.BufferMemoryRequirementsInfo2, - Buffer = buffer - }; - - MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; - requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); - - context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - - MemoryAllocateInfo allocateInfo = new() - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = requirements2.MemoryRequirements.Size, - MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) - }; - - if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) - { - allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); - dedicatedAllocateInfo.Buffer = buffer; - } - - allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); - allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; - - context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); - context.Vk.BindBufferMemory(context.Device, buffer, DeviceMemory, 0).Success(); - } - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; public override nint GetNativeObject(NativeObjectType type) @@ -70,7 +37,7 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); - return new VKBuffer(Context, desc, buffer); + return new VKBuffer(Context, desc, buffer, new(DeviceMemory, offsetInBytes, false)); } protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) From 2d42d13f1d6fa683d252c2209cd9acf71678ff57 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 30 Jun 2026 23:20:03 +0800 Subject: [PATCH 310/466] Update Metal.NET and MAUI.Controls package versions Updated Metal.NET to version 2.4.0 and Microsoft.Maui.Controls to version 10.0.80 in Directory.Packages.props for improved features and bug fixes. --- sources/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index b8cd1e99..9ef207ee 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + From 868e5ce5dc84038296524bc5ceb5479cbe68fade Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 1 Jul 2026 19:26:20 +0800 Subject: [PATCH 311/466] Add Vulkan texture support and QueueSharing struct Introduce VKTexture for Vulkan image management, extend VKFormats with texture/sample/pixel format mappings, and replace array-based queue family handling with the QueueSharing struct. Update buffer/texture creation and memory queries to use QueueSharing. Improve resource cleanup and sharing logic. --- sources/Zenith.NET.Vulkan/QueueSharing.cs | 26 +++ sources/Zenith.NET.Vulkan/VKBuffer.cs | 12 +- sources/Zenith.NET.Vulkan/VKFormats.cs | 152 ++++++++++++++++++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 39 ++++- sources/Zenith.NET.Vulkan/VKHeap.cs | 11 +- sources/Zenith.NET.Vulkan/VKTexture.cs | 112 +++++++++++++ 6 files changed, 336 insertions(+), 16 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/QueueSharing.cs create mode 100644 sources/Zenith.NET.Vulkan/VKTexture.cs diff --git a/sources/Zenith.NET.Vulkan/QueueSharing.cs b/sources/Zenith.NET.Vulkan/QueueSharing.cs new file mode 100644 index 00000000..138dfead --- /dev/null +++ b/sources/Zenith.NET.Vulkan/QueueSharing.cs @@ -0,0 +1,26 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal readonly unsafe struct QueueSharing : IDisposable +{ + private readonly ZenithMarshal.Scope scope = new(); + + public readonly SharingMode Mode; + + public readonly uint Count; + + public readonly uint* Indices; + + public QueueSharing(ReadOnlySpan indices) + { + Mode = indices.Length is 1 ? SharingMode.Exclusive : SharingMode.Concurrent; + Count = (uint)indices.Length; + Indices = (uint*)ZenithMarshal.AllocateAndFill(scope, indices); + } + + public void Dispose() + { + scope.Dispose(); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 5f975da6..07b86400 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -12,9 +12,7 @@ internal unsafe class VKBuffer : Buffer public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) { - using ZenithMarshal.Scope scope = new(); - - BufferCreateInfo createInfo = CreateInfo(desc, (uint)context.QueueFamilyIndices.Length, (uint*)ZenithMarshal.AllocateAndFill(scope, [.. context.QueueFamilyIndices])); + BufferCreateInfo createInfo = CreateInfo(context.QueueSharing, desc); context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); @@ -124,16 +122,16 @@ protected override void Destroy() } } - public static BufferCreateInfo CreateInfo(BufferDesc desc, uint queueFamilyIndexCount, uint* pQueueFamilyIndices) + public static BufferCreateInfo CreateInfo(QueueSharing queueSharing, BufferDesc desc) { return new() { SType = StructureType.BufferCreateInfo, Size = desc.SizeInBytes, Usage = VKFormats.Vulkan(desc.Usages), - SharingMode = queueFamilyIndexCount is 1 ? SharingMode.Exclusive : SharingMode.Concurrent, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = pQueueFamilyIndices + SharingMode = queueSharing.Mode, + QueueFamilyIndexCount = queueSharing.Count, + PQueueFamilyIndices = queueSharing.Indices }; } } diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 397cfdc9..ca0e045a 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -4,6 +4,158 @@ namespace Zenith.NET.Vulkan; internal static class VKFormats { + public static ImageType Vulkan(TextureType textureType) + { + return textureType switch + { + TextureType.Texture1D or + TextureType.Texture1DArray => ImageType.Type1D, + + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ImageType.Type2D, + + TextureType.Texture3D => ImageType.Type3D, + + _ => default + }; + } + + public static SampleCountFlags Vulkan(SampleCount sampleCount) + { + return sampleCount switch + { + SampleCount.Count1 => SampleCountFlags.Count1Bit, + SampleCount.Count2 => SampleCountFlags.Count2Bit, + SampleCount.Count4 => SampleCountFlags.Count4Bit, + SampleCount.Count8 => SampleCountFlags.Count8Bit, + SampleCount.Count16 => SampleCountFlags.Count16Bit, + SampleCount.Count32 => SampleCountFlags.Count32Bit, + _ => default + }; + } + + public static (Format Format, ImageAspectFlags Aspect) Vulkan(PixelFormat pixelFormat) + { + ImageAspectFlags aspect = default; + + if (ZenithHelper.HasDepth(pixelFormat)) + { + aspect |= ImageAspectFlags.DepthBit; + } + + if (ZenithHelper.HasStencil(pixelFormat)) + { + aspect |= ImageAspectFlags.StencilBit; + } + + if (aspect is 0) + { + aspect = ImageAspectFlags.ColorBit; + } + + Format format = pixelFormat switch + { + PixelFormat.R8UNorm => Format.R8Unorm, + PixelFormat.R8SNorm => Format.R8SNorm, + PixelFormat.R8UInt => Format.R8Uint, + PixelFormat.R8SInt => Format.R8Sint, + + PixelFormat.R16UNorm => Format.R16Unorm, + PixelFormat.R16SNorm => Format.R16SNorm, + PixelFormat.R16UInt => Format.R16Uint, + PixelFormat.R16SInt => Format.R16Sint, + PixelFormat.R16Float => Format.R16Sfloat, + + PixelFormat.R32UInt => Format.R32Uint, + PixelFormat.R32SInt => Format.R32Sint, + PixelFormat.R32Float => Format.R32Sfloat, + + PixelFormat.R8G8UNorm => Format.R8G8Unorm, + PixelFormat.R8G8SNorm => Format.R8G8SNorm, + PixelFormat.R8G8UInt => Format.R8G8Uint, + PixelFormat.R8G8SInt => Format.R8G8Sint, + + PixelFormat.R16G16UNorm => Format.R16G16Unorm, + PixelFormat.R16G16SNorm => Format.R16G16SNorm, + PixelFormat.R16G16UInt => Format.R16G16Uint, + PixelFormat.R16G16SInt => Format.R16G16Sint, + PixelFormat.R16G16Float => Format.R16G16Sfloat, + + PixelFormat.R32G32UInt => Format.R32G32Uint, + PixelFormat.R32G32SInt => Format.R32G32Sint, + PixelFormat.R32G32Float => Format.R32G32Sfloat, + + PixelFormat.R32G32B32UInt => Format.R32G32B32Uint, + PixelFormat.R32G32B32SInt => Format.R32G32B32Sint, + PixelFormat.R32G32B32Float => Format.R32G32B32Sfloat, + + PixelFormat.R8G8B8A8UNorm => Format.R8G8B8A8Unorm, + PixelFormat.R8G8B8A8SNorm => Format.R8G8B8A8SNorm, + PixelFormat.R8G8B8A8UInt => Format.R8G8B8A8Uint, + PixelFormat.R8G8B8A8SInt => Format.R8G8B8A8Sint, + PixelFormat.R8G8B8A8SRgb => Format.R8G8B8A8Srgb, + + PixelFormat.R16G16B16A16UNorm => Format.R16G16B16A16Unorm, + PixelFormat.R16G16B16A16SNorm => Format.R16G16B16A16SNorm, + PixelFormat.R16G16B16A16UInt => Format.R16G16B16A16Uint, + PixelFormat.R16G16B16A16SInt => Format.R16G16B16A16Sint, + PixelFormat.R16G16B16A16Float => Format.R16G16B16A16Sfloat, + + PixelFormat.R32G32B32A32UInt => Format.R32G32B32A32Uint, + PixelFormat.R32G32B32A32SInt => Format.R32G32B32A32Sint, + PixelFormat.R32G32B32A32Float => Format.R32G32B32A32Sfloat, + + PixelFormat.B8G8R8A8UNorm => Format.B8G8R8A8Unorm, + PixelFormat.B8G8R8A8SRgb => Format.B8G8R8A8Srgb, + + PixelFormat.D16UNorm => Format.D16Unorm, + PixelFormat.D24UNormS8UInt => Format.D24UnormS8Uint, + PixelFormat.D32Float => Format.D32Sfloat, + PixelFormat.D32FloatS8UInt => Format.D32SfloatS8Uint, + + PixelFormat.BC4UNorm => Format.BC4UnormBlock, + PixelFormat.BC4SNorm => Format.BC4SNormBlock, + PixelFormat.BC5UNorm => Format.BC5UnormBlock, + PixelFormat.BC5SNorm => Format.BC5SNormBlock, + PixelFormat.BC6HUFloat => Format.BC6HUfloatBlock, + PixelFormat.BC6HSFloat => Format.BC6HSfloatBlock, + PixelFormat.BC7UNorm => Format.BC7UnormBlock, + PixelFormat.BC7SRgb => Format.BC7SrgbBlock, + + PixelFormat.ETC2UNorm => Format.Etc2R8G8B8UnormBlock, + PixelFormat.ETC2SRgb => Format.Etc2R8G8B8SrgbBlock, + PixelFormat.ETC2A1UNorm => Format.Etc2R8G8B8A1UnormBlock, + PixelFormat.ETC2A1SRgb => Format.Etc2R8G8B8A1SrgbBlock, + PixelFormat.ETC2A8UNorm => Format.Etc2R8G8B8A8UnormBlock, + PixelFormat.ETC2A8SRgb => Format.Etc2R8G8B8A8SrgbBlock, + + PixelFormat.ASTC4x4UNorm => Format.Astc4x4UnormBlock, + PixelFormat.ASTC4x4SRgb => Format.Astc4x4SrgbBlock, + PixelFormat.ASTC4x4Float => Format.Astc4x4SfloatBlock, + PixelFormat.ASTC5x5UNorm => Format.Astc5x5UnormBlock, + PixelFormat.ASTC5x5SRgb => Format.Astc5x5SrgbBlock, + PixelFormat.ASTC5x5Float => Format.Astc5x5SfloatBlock, + PixelFormat.ASTC6x6UNorm => Format.Astc6x6UnormBlock, + PixelFormat.ASTC6x6SRgb => Format.Astc6x6SrgbBlock, + PixelFormat.ASTC6x6Float => Format.Astc6x6SfloatBlock, + PixelFormat.ASTC8x8UNorm => Format.Astc8x8UnormBlock, + PixelFormat.ASTC8x8SRgb => Format.Astc8x8SrgbBlock, + PixelFormat.ASTC8x8Float => Format.Astc8x8SfloatBlock, + PixelFormat.ASTC10x10UNorm => Format.Astc10x10UnormBlock, + PixelFormat.ASTC10x10SRgb => Format.Astc10x10SrgbBlock, + PixelFormat.ASTC10x10Float => Format.Astc10x10SfloatBlock, + PixelFormat.ASTC12x12UNorm => Format.Astc12x12UnormBlock, + PixelFormat.ASTC12x12SRgb => Format.Astc12x12SrgbBlock, + PixelFormat.ASTC12x12Float => Format.Astc12x12SfloatBlock, + + _ => default + }; + + return (format, aspect); + } + public static BufferUsageFlags Vulkan(BufferUsages bufferUsages) { BufferUsageFlags result = BufferUsageFlags.ShaderDeviceAddressBit; diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 4e5c2bf5..177fbd3b 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -83,7 +83,7 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrSwapchain? Swapchain { get; private set; } - public uint[] QueueFamilyIndices { get; private set; } = []; + public QueueSharing QueueSharing { get; private set; } public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryResidency residency) { @@ -503,7 +503,7 @@ protected override void Initialize(bool useValidationLayer, loadQueues(); - QueueFamilyIndices = [.. new HashSet() { queues.GraphicsQueueFamilyIndex, queues.ComputeQueueFamilyIndex, queues.TransferQueueFamilyIndex }]; + QueueSharing = new([.. new HashSet() { queues.GraphicsQueueFamilyIndex, queues.ComputeQueueFamilyIndex, queues.TransferQueueFamilyIndex }]); } capabilities = new VKCapabilities(this); @@ -525,12 +525,39 @@ protected override Heap CreateHeapImpl(HeapDesc desc) protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - throw new NotImplementedException(); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(QueueSharing, desc); + + DeviceBufferMemoryRequirements deviceRequirements = new() + { + SType = StructureType.DeviceBufferMemoryRequirements, + PCreateInfo = &createInfo + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + + Vk.GetDeviceBufferMemoryRequirements(Device, &deviceRequirements, &requirements2); + + return new(requirements2.MemoryRequirements.Size, requirements2.MemoryRequirements.Alignment); } protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { - throw new NotImplementedException(); + ImageCreateInfo createInfo = VKTexture.CreateInfo(QueueSharing, desc); + + DeviceImageMemoryRequirements deviceRequirements = new() + { + SType = StructureType.DeviceImageMemoryRequirements, + PCreateInfo = &createInfo + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + + Vk.GetDeviceImageMemoryRequirements(Device, &deviceRequirements, &requirements2); + + PhysicalDeviceProperties properties; + Vk.GetPhysicalDeviceProperties(PhysicalDevice, &properties); + + return new(requirements2.MemoryRequirements.Size, Math.Max(requirements2.MemoryRequirements.Alignment, properties.Limits.BufferImageGranularity)); } protected override Buffer CreateBufferImpl(BufferDesc desc) @@ -545,7 +572,7 @@ protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc) { - throw new NotImplementedException(); + return new VKTexture(this, desc); } protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) @@ -592,6 +619,8 @@ protected override void Destroy() { base.Destroy(); + QueueSharing.Dispose(); + Vk.DestroyDevice(Device, default); Vk.DestroyInstance(Instance, default); Vk.Dispose(); diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index a01ee212..2a0428bd 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -30,9 +30,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { - using ZenithMarshal.Scope scope = new(); - - BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, (uint)Context.QueueFamilyIndices.Length, (uint*)ZenithMarshal.AllocateAndFill(scope, [.. Context.QueueFamilyIndices])); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(Context.QueueSharing, desc); Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); @@ -42,7 +40,12 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) { - throw new NotImplementedException(); + ImageCreateInfo createInfo = VKTexture.CreateInfo(Context.QueueSharing, desc); + + Context.Vk.CreateImage(Context.Device, &createInfo, default, out Image image).Success(); + Context.Vk.BindImageMemory(Context.Device, image, DeviceMemory, offsetInBytes).Success(); + + return new VKTexture(Context, desc, image, new(DeviceMemory, offsetInBytes, false)); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs new file mode 100644 index 00000000..f3692a1b --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -0,0 +1,112 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKTexture : Texture +{ + public Image Image; + + public VKAllocation Allocation; + + public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, desc) + { + ImageCreateInfo createInfo = CreateInfo(context.QueueSharing, desc); + + context.Vk.CreateImage(context.Device, &createInfo, default, out Image).Success(); + + ImageMemoryRequirementsInfo2 requirementsInfo2 = new() + { + SType = StructureType.ImageMemoryRequirementsInfo2, + Image = Image + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); + + context.Vk.GetImageMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); + + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, MemoryResidency.GpuOnly) + }; + + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Image = Image; + } + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); + context.Vk.BindImageMemory(context.Device, Image, deviceMemory, 0).Success(); + + Allocation = new(deviceMemory, 0, true); + } + + public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image, VKAllocation allocation) : base(context, desc) + { + Image = image; + Allocation = allocation; + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override ResourceHandle SampledHandle { get; } + + public override ResourceHandle StorageHandle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Image, + ObjectHandle = Image.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyImage(Context.Device, Image, default); + + if (Allocation.IsOwned) + { + Context.Vk.FreeMemory(Context.Device, Allocation.DeviceMemory, default); + } + } + + public static ImageCreateInfo CreateInfo(QueueSharing queueSharing, TextureDesc desc) + { + return new() + { + SType = StructureType.ImageCreateInfo, + Flags = desc.Type is TextureType.TextureCube or TextureType.TextureCubeArray ? ImageCreateFlags.CreateCubeCompatibleBit : ImageCreateFlags.None, + ImageType = VKFormats.Vulkan(desc.Type), + Format = VKFormats.Vulkan(desc.Format).Format, + Extent = new() + { + Width = desc.Width, + Height = desc.Height, + Depth = desc.Depth + }, + MipLevels = desc.MipLevels, + ArrayLayers = desc.ArrayLayers, + Samples = VKFormats.Vulkan(desc.SampleCount), + Usage = VKFormats.Vulkan(desc.Usages), + SharingMode = queueSharing.Mode, + QueueFamilyIndexCount = queueSharing.Count, + PQueueFamilyIndices = queueSharing.Indices + }; + } +} From fa5ed1a9bcf80c00cd23f55981d6a05f40652be2 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 2 Jul 2026 10:46:59 +0800 Subject: [PATCH 312/466] Refactor Vulkan queue sharing to use QueueFamilies struct Replace QueueSharing with QueueFamilies for managing queue family indices and sharing mode in Vulkan resource creation. Update all related method signatures and usages. Refactor VKFormats.Vulkan for clearer aspect flag assignment. --- sources/Zenith.NET.Vulkan/QueueFamilies.cs | 26 +++++++++++++ sources/Zenith.NET.Vulkan/QueueSharing.cs | 26 ------------- sources/Zenith.NET.Vulkan/VKBuffer.cs | 10 ++--- sources/Zenith.NET.Vulkan/VKFormats.cs | 38 +++++++++---------- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 10 ++--- sources/Zenith.NET.Vulkan/VKHeap.cs | 4 +- sources/Zenith.NET.Vulkan/VKTexture.cs | 10 ++--- 7 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/QueueFamilies.cs delete mode 100644 sources/Zenith.NET.Vulkan/QueueSharing.cs diff --git a/sources/Zenith.NET.Vulkan/QueueFamilies.cs b/sources/Zenith.NET.Vulkan/QueueFamilies.cs new file mode 100644 index 00000000..f54cb348 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/QueueFamilies.cs @@ -0,0 +1,26 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal readonly unsafe struct QueueFamilies : IDisposable +{ + private readonly ZenithMarshal.Scope scope = new(); + + public readonly SharingMode SharingMode; + + public readonly uint IndexCount; + + public readonly uint* Indices; + + public QueueFamilies(ReadOnlySpan indices) + { + SharingMode = indices.Length is 1 ? SharingMode.Exclusive : SharingMode.Concurrent; + IndexCount = (uint)indices.Length; + Indices = (uint*)ZenithMarshal.AllocateAndFill(scope, indices); + } + + public void Dispose() + { + scope.Dispose(); + } +} diff --git a/sources/Zenith.NET.Vulkan/QueueSharing.cs b/sources/Zenith.NET.Vulkan/QueueSharing.cs deleted file mode 100644 index 138dfead..00000000 --- a/sources/Zenith.NET.Vulkan/QueueSharing.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal readonly unsafe struct QueueSharing : IDisposable -{ - private readonly ZenithMarshal.Scope scope = new(); - - public readonly SharingMode Mode; - - public readonly uint Count; - - public readonly uint* Indices; - - public QueueSharing(ReadOnlySpan indices) - { - Mode = indices.Length is 1 ? SharingMode.Exclusive : SharingMode.Concurrent; - Count = (uint)indices.Length; - Indices = (uint*)ZenithMarshal.AllocateAndFill(scope, indices); - } - - public void Dispose() - { - scope.Dispose(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 07b86400..ca3ef4b7 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -12,7 +12,7 @@ internal unsafe class VKBuffer : Buffer public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) { - BufferCreateInfo createInfo = CreateInfo(context.QueueSharing, desc); + BufferCreateInfo createInfo = CreateInfo(desc, context.QueueFamilies); context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); @@ -122,16 +122,16 @@ protected override void Destroy() } } - public static BufferCreateInfo CreateInfo(QueueSharing queueSharing, BufferDesc desc) + public static BufferCreateInfo CreateInfo(BufferDesc desc, QueueFamilies queueFamilies) { return new() { SType = StructureType.BufferCreateInfo, Size = desc.SizeInBytes, Usage = VKFormats.Vulkan(desc.Usages), - SharingMode = queueSharing.Mode, - QueueFamilyIndexCount = queueSharing.Count, - PQueueFamilyIndices = queueSharing.Indices + SharingMode = queueFamilies.SharingMode, + QueueFamilyIndexCount = queueFamilies.IndexCount, + PQueueFamilyIndices = queueFamilies.Indices }; } } diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index ca0e045a..38c4e4f6 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -36,25 +36,8 @@ public static SampleCountFlags Vulkan(SampleCount sampleCount) }; } - public static (Format Format, ImageAspectFlags Aspect) Vulkan(PixelFormat pixelFormat) + public static (Format Format, ImageAspectFlags AspectFlags) Vulkan(PixelFormat pixelFormat) { - ImageAspectFlags aspect = default; - - if (ZenithHelper.HasDepth(pixelFormat)) - { - aspect |= ImageAspectFlags.DepthBit; - } - - if (ZenithHelper.HasStencil(pixelFormat)) - { - aspect |= ImageAspectFlags.StencilBit; - } - - if (aspect is 0) - { - aspect = ImageAspectFlags.ColorBit; - } - Format format = pixelFormat switch { PixelFormat.R8UNorm => Format.R8Unorm, @@ -153,7 +136,24 @@ public static (Format Format, ImageAspectFlags Aspect) Vulkan(PixelFormat pixelF _ => default }; - return (format, aspect); + ImageAspectFlags aspectFlags = default; + + if (ZenithHelper.HasDepth(pixelFormat)) + { + aspectFlags |= ImageAspectFlags.DepthBit; + } + + if (ZenithHelper.HasStencil(pixelFormat)) + { + aspectFlags |= ImageAspectFlags.StencilBit; + } + + if (aspectFlags is 0) + { + aspectFlags = ImageAspectFlags.ColorBit; + } + + return (format, aspectFlags); } public static BufferUsageFlags Vulkan(BufferUsages bufferUsages) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 177fbd3b..d1b7cf53 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -83,7 +83,7 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrSwapchain? Swapchain { get; private set; } - public QueueSharing QueueSharing { get; private set; } + public QueueFamilies QueueFamilies { get; private set; } public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryResidency residency) { @@ -503,7 +503,7 @@ protected override void Initialize(bool useValidationLayer, loadQueues(); - QueueSharing = new([.. new HashSet() { queues.GraphicsQueueFamilyIndex, queues.ComputeQueueFamilyIndex, queues.TransferQueueFamilyIndex }]); + QueueFamilies = new([.. new HashSet() { queues.GraphicsQueueFamilyIndex, queues.ComputeQueueFamilyIndex, queues.TransferQueueFamilyIndex }]); } capabilities = new VKCapabilities(this); @@ -525,7 +525,7 @@ protected override Heap CreateHeapImpl(HeapDesc desc) protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - BufferCreateInfo createInfo = VKBuffer.CreateInfo(QueueSharing, desc); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, QueueFamilies); DeviceBufferMemoryRequirements deviceRequirements = new() { @@ -542,7 +542,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { - ImageCreateInfo createInfo = VKTexture.CreateInfo(QueueSharing, desc); + ImageCreateInfo createInfo = VKTexture.CreateInfo(desc, QueueFamilies); DeviceImageMemoryRequirements deviceRequirements = new() { @@ -619,7 +619,7 @@ protected override void Destroy() { base.Destroy(); - QueueSharing.Dispose(); + QueueFamilies.Dispose(); Vk.DestroyDevice(Device, default); Vk.DestroyInstance(Instance, default); diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index 2a0428bd..c1b9586a 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -30,7 +30,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { - BufferCreateInfo createInfo = VKBuffer.CreateInfo(Context.QueueSharing, desc); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Context.QueueFamilies); Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); @@ -40,7 +40,7 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) { - ImageCreateInfo createInfo = VKTexture.CreateInfo(Context.QueueSharing, desc); + ImageCreateInfo createInfo = VKTexture.CreateInfo(desc, Context.QueueFamilies); Context.Vk.CreateImage(Context.Device, &createInfo, default, out Image image).Success(); Context.Vk.BindImageMemory(Context.Device, image, DeviceMemory, offsetInBytes).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index f3692a1b..8a4ed3d4 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -10,7 +10,7 @@ internal unsafe class VKTexture : Texture public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, desc) { - ImageCreateInfo createInfo = CreateInfo(context.QueueSharing, desc); + ImageCreateInfo createInfo = CreateInfo(desc, context.QueueFamilies); context.Vk.CreateImage(context.Device, &createInfo, default, out Image).Success(); @@ -86,7 +86,7 @@ protected override void Destroy() } } - public static ImageCreateInfo CreateInfo(QueueSharing queueSharing, TextureDesc desc) + public static ImageCreateInfo CreateInfo(TextureDesc desc, QueueFamilies queueFamilies) { return new() { @@ -104,9 +104,9 @@ public static ImageCreateInfo CreateInfo(QueueSharing queueSharing, TextureDesc ArrayLayers = desc.ArrayLayers, Samples = VKFormats.Vulkan(desc.SampleCount), Usage = VKFormats.Vulkan(desc.Usages), - SharingMode = queueSharing.Mode, - QueueFamilyIndexCount = queueSharing.Count, - PQueueFamilyIndices = queueSharing.Indices + SharingMode = queueFamilies.SharingMode, + QueueFamilyIndexCount = queueFamilies.IndexCount, + PQueueFamilyIndices = queueFamilies.Indices }; } } From f87339707130b87903602828808c558019095906 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 2 Jul 2026 16:47:32 +0800 Subject: [PATCH 313/466] Add VKBuffer ctor with custom BufferUsageFlags support Added a new VKBuffer constructor that accepts BufferUsageFlags, handling buffer creation, memory requirements, allocation (including dedicated allocation), memory binding, and device address retrieval. This enables more flexible buffer creation with custom usage flags. --- sources/Zenith.NET.Vulkan/VKBuffer.cs | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index ca3ef4b7..992a01a1 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -57,6 +57,54 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); } + public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usage) : base(context, desc) + { + BufferCreateInfo createInfo = CreateInfo(desc, context.QueueFamilies); + createInfo.Usage |= usage; + + context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); + + BufferMemoryRequirementsInfo2 requirementsInfo2 = new() + { + SType = StructureType.BufferMemoryRequirementsInfo2, + Buffer = Buffer + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); + + context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); + + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) + }; + + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Buffer = Buffer; + } + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); + allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); + context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); + + Allocation = new(deviceMemory, 0, true); + + BufferDeviceAddressInfo deviceAddressInfo = new() + { + SType = StructureType.BufferDeviceAddressInfo, + Buffer = Buffer + }; + + DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); + } + public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, VKAllocation allocation) : base(context, desc) { Buffer = buffer; From d806745408d6996c307249710061532b5177106f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 2 Jul 2026 18:29:11 +0800 Subject: [PATCH 314/466] Add Vulkan descriptor heap management system Introduce VKDescriptorHeap, VKDescriptorRegion, and VKDescriptorToken for efficient resource and sampler descriptor allocation and recycling. Update VKGraphicsContext to manage and dispose of descriptor heaps, improving descriptor tracking and resource management in the Vulkan backend. --- sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs | 109 ++++++++++++++++++ .../Zenith.NET.Vulkan/VKDescriptorRegion.cs | 34 ++++++ .../Zenith.NET.Vulkan/VKDescriptorToken.cs | 13 +++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 7 ++ 4 files changed, 163 insertions(+) create mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs create mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs create mode 100644 sources/Zenith.NET.Vulkan/VKDescriptorToken.cs diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs new file mode 100644 index 00000000..3966ab76 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -0,0 +1,109 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKDescriptorHeap(VKGraphicsContext context, + VKBuffer buffer, + ulong reservedRange, + VKDescriptorRegion? bufferRegion, + VKDescriptorRegion? imageRegion, + VKDescriptorRegion? samplerRegion) : DisposableObject +{ + public nint HostAddress { get; } = buffer.Map(); + + public ulong DeviceAddress => buffer.DeviceAddress; + + public ulong ReservedRange => reservedRange; + + public VKDescriptorToken Allocate(ResourceDescriptorInfoEXT info) + { + VKDescriptorRegion? region = info.Type switch + { + DescriptorType.UniformBuffer or DescriptorType.StorageBuffer => bufferRegion, + DescriptorType.SampledImage or DescriptorType.StorageImage => imageRegion, + _ => null + }; + + if (region is null) + { + return default; + } + + VKDescriptorToken token = region.Allocate(HostAddress, out HostAddressRangeEXT target); + + context.DescriptorHeap?.WriteResourceDescriptors(context.Device, 1, &info, &target).Success(); + + return token; + } + + public VKDescriptorToken Allocate(SamplerCreateInfo info) + { + if (samplerRegion is null) + { + return default; + } + + VKDescriptorToken token = samplerRegion.Allocate(HostAddress, out HostAddressRangeEXT target); + + context.DescriptorHeap?.WriteSamplerDescriptors(context.Device, 1, &info, &target).Success(); + + return token; + } + + protected override void Destroy() + { + buffer.Unmap(); + buffer.Dispose(); + } + + public static VKDescriptorHeap CreateResourceHeap(VKGraphicsContext context, uint bufferCapacity, uint imageCapacity) + { + PhysicalDeviceProperties2 properties2 = new() { SType = StructureType.PhysicalDeviceProperties2 }; + properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT heapProperties); + + context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); + + ulong bufferOffset = ZenithHelper.Align(heapProperties.MinResourceHeapReservedRange, heapProperties.BufferDescriptorSize); + uint bufferBaseIndex = (uint)(bufferOffset / heapProperties.BufferDescriptorSize); + + ulong imageOffset = ZenithHelper.Align(bufferOffset + (bufferCapacity * heapProperties.BufferDescriptorSize), heapProperties.ImageDescriptorSize); + uint imageBaseIndex = (uint)(imageOffset / heapProperties.ImageDescriptorSize); + + VKBuffer buffer = new(context, new() + { + SizeInBytes = (uint)ZenithHelper.Align(imageOffset + (imageCapacity * heapProperties.ImageDescriptorSize), heapProperties.ResourceHeapAlignment), + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.DescriptorHeapBitExt()); + + return new(context, + buffer, + heapProperties.MinResourceHeapReservedRange, + new(bufferBaseIndex, (nuint)heapProperties.BufferDescriptorSize), + new(imageBaseIndex, (nuint)heapProperties.ImageDescriptorSize), + null); + } + + public static VKDescriptorHeap CreateSamplerHeap(VKGraphicsContext context, uint samplerCapacity) + { + PhysicalDeviceProperties2 properties2 = new() { SType = StructureType.PhysicalDeviceProperties2 }; + properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT heapProperties); + + context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); + + ulong samplerOffset = ZenithHelper.Align(heapProperties.MinSamplerHeapReservedRange, heapProperties.SamplerDescriptorSize); + uint samplerBaseIndex = (uint)(samplerOffset / heapProperties.SamplerDescriptorSize); + + VKBuffer buffer = new(context, new() + { + SizeInBytes = (uint)ZenithHelper.Align(samplerOffset + (samplerCapacity * heapProperties.SamplerDescriptorSize), heapProperties.SamplerHeapAlignment), + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.DescriptorHeapBitExt()); + + return new(context, + buffer, + heapProperties.MinSamplerHeapReservedRange, + null, + null, + new(samplerBaseIndex, (nuint)heapProperties.SamplerDescriptorSize)); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs new file mode 100644 index 00000000..cf35f654 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs @@ -0,0 +1,34 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKDescriptorRegion(uint baseIndex, nuint stride) +{ + private readonly Lock @lock = new(); + private readonly Stack recycled = []; + + private uint head; + + public VKDescriptorToken Allocate(nint address, out HostAddressRangeEXT target) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (!recycled.TryPop(out uint index)) + { + index = head++; + } + + index += baseIndex; + + target = new((void*)(address + (nint)(stride * index)), stride); + + return new(this, index); + } + + public void Free(VKDescriptorToken token) + { + using Lock.Scope _ = @lock.EnterScope(); + + recycled.Push(token.Index - baseIndex); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs b/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs new file mode 100644 index 00000000..f11b7aa8 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs @@ -0,0 +1,13 @@ +namespace Zenith.NET.Vulkan; + +internal readonly struct VKDescriptorToken(VKDescriptorRegion region, uint index) : IDisposable +{ + public readonly uint Index = index; + + public readonly ResourceHandle ResourceHandle = new(index, 0); + + public void Dispose() + { + region.Free(this); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index d1b7cf53..0f64648e 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -85,6 +85,10 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public QueueFamilies QueueFamilies { get; private set; } + public VKDescriptorHeap ResourceHeap => field ??= VKDescriptorHeap.CreateResourceHeap(this, 200000, 800000); + + public VKDescriptorHeap SamplerHeap => field ??= VKDescriptorHeap.CreateSamplerHeap(this, 2048); + public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryResidency residency) { PhysicalDeviceMemoryProperties properties; @@ -619,6 +623,9 @@ protected override void Destroy() { base.Destroy(); + SamplerHeap.Dispose(); + ResourceHeap.Dispose(); + QueueFamilies.Dispose(); Vk.DestroyDevice(Device, default); From 9791a274d65e8018caff435b21117202e0aa9221 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 09:35:31 +0800 Subject: [PATCH 315/466] Add AccelerationStructureKhr to bufferRegion mapping DescriptorType.AccelerationStructureKhr is now handled by bufferRegion in the Allocate method, aligning its behavior with UniformBuffer and StorageBuffer types. The switch statement was also reformatted for improved clarity. --- sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs index 3966ab76..27f20c26 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -19,8 +19,13 @@ public VKDescriptorToken Allocate(ResourceDescriptorInfoEXT info) { VKDescriptorRegion? region = info.Type switch { - DescriptorType.UniformBuffer or DescriptorType.StorageBuffer => bufferRegion, - DescriptorType.SampledImage or DescriptorType.StorageImage => imageRegion, + DescriptorType.UniformBuffer or + DescriptorType.StorageBuffer or + DescriptorType.AccelerationStructureKhr => bufferRegion, + + DescriptorType.SampledImage or + DescriptorType.StorageImage => imageRegion, + _ => null }; From 593c60194d88a16c89820fb86c54570d9c0c2744 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 09:50:40 +0800 Subject: [PATCH 316/466] Refactor buffer address handling in descriptor classes Replaced the public HostAddress property with a private readonly pointer field in VKDescriptorHeap to store the mapped buffer address. Updated all allocation methods to use pointer instead of HostAddress. Renamed the parameter in VKDescriptorRegion.Allocate from address to pointer for consistency. --- sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs | 6 +++--- sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs index 27f20c26..0148430a 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -9,7 +9,7 @@ internal unsafe class VKDescriptorHeap(VKGraphicsContext context, VKDescriptorRegion? imageRegion, VKDescriptorRegion? samplerRegion) : DisposableObject { - public nint HostAddress { get; } = buffer.Map(); + private readonly nint pointer = buffer.Map(); public ulong DeviceAddress => buffer.DeviceAddress; @@ -34,7 +34,7 @@ DescriptorType.SampledImage or return default; } - VKDescriptorToken token = region.Allocate(HostAddress, out HostAddressRangeEXT target); + VKDescriptorToken token = region.Allocate(pointer, out HostAddressRangeEXT target); context.DescriptorHeap?.WriteResourceDescriptors(context.Device, 1, &info, &target).Success(); @@ -48,7 +48,7 @@ public VKDescriptorToken Allocate(SamplerCreateInfo info) return default; } - VKDescriptorToken token = samplerRegion.Allocate(HostAddress, out HostAddressRangeEXT target); + VKDescriptorToken token = samplerRegion.Allocate(pointer, out HostAddressRangeEXT target); context.DescriptorHeap?.WriteSamplerDescriptors(context.Device, 1, &info, &target).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs index cf35f654..c4d1027a 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs @@ -9,7 +9,7 @@ internal unsafe class VKDescriptorRegion(uint baseIndex, nuint stride) private uint head; - public VKDescriptorToken Allocate(nint address, out HostAddressRangeEXT target) + public VKDescriptorToken Allocate(nint pointer, out HostAddressRangeEXT target) { using Lock.Scope _ = @lock.EnterScope(); @@ -20,7 +20,7 @@ public VKDescriptorToken Allocate(nint address, out HostAddressRangeEXT target) index += baseIndex; - target = new((void*)(address + (nint)(stride * index)), stride); + target = new((void*)(pointer + (nint)(stride * index)), stride); return new(this, index); } From 4173423271593cf44510215c242a0ace29d517e8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 10:40:27 +0800 Subject: [PATCH 317/466] Refactor descriptor heap stride and range handling Clarify parameter names from reservedRange to reservedRangeSize. Change VKDescriptorRegion stride type from nuint to ulong. Refactor offset, stride, and size calculations for clarity and alignment. Update VKDescriptorRegion construction to use ulong stride. Range property now returns DeviceAddressRangeEXT. Minor code cleanup and improved variable naming. --- sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs | 43 ++++++++++++------- .../Zenith.NET.Vulkan/VKDescriptorRegion.cs | 4 +- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs index 0148430a..8d6cd6c8 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -4,16 +4,16 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKDescriptorHeap(VKGraphicsContext context, VKBuffer buffer, - ulong reservedRange, + ulong reservedRangeSize, VKDescriptorRegion? bufferRegion, VKDescriptorRegion? imageRegion, VKDescriptorRegion? samplerRegion) : DisposableObject { private readonly nint pointer = buffer.Map(); - public ulong DeviceAddress => buffer.DeviceAddress; + public DeviceAddressRangeEXT Range => new(buffer.DeviceAddress, buffer.Desc.SizeInBytes); - public ulong ReservedRange => reservedRange; + public ulong ReservedRangeSize => reservedRangeSize; public VKDescriptorToken Allocate(ResourceDescriptorInfoEXT info) { @@ -68,23 +68,29 @@ public static VKDescriptorHeap CreateResourceHeap(VKGraphicsContext context, uin context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); - ulong bufferOffset = ZenithHelper.Align(heapProperties.MinResourceHeapReservedRange, heapProperties.BufferDescriptorSize); - uint bufferBaseIndex = (uint)(bufferOffset / heapProperties.BufferDescriptorSize); + ulong reservedRangeSize = heapProperties.MinResourceHeapReservedRange; + ulong bufferStride = heapProperties.BufferDescriptorSize; + ulong imageStride = heapProperties.ImageDescriptorSize; - ulong imageOffset = ZenithHelper.Align(bufferOffset + (bufferCapacity * heapProperties.BufferDescriptorSize), heapProperties.ImageDescriptorSize); - uint imageBaseIndex = (uint)(imageOffset / heapProperties.ImageDescriptorSize); + ulong bufferOffset = ZenithHelper.Align(reservedRangeSize, bufferStride); + uint bufferBaseIndex = (uint)(bufferOffset / bufferStride); + + ulong imageOffset = ZenithHelper.Align(bufferOffset + (bufferCapacity * bufferStride), imageStride); + uint imageBaseIndex = (uint)(imageOffset / imageStride); + + ulong sizeInBytes = ZenithHelper.Align(imageOffset + (imageCapacity * imageStride), heapProperties.ResourceHeapAlignment); VKBuffer buffer = new(context, new() { - SizeInBytes = (uint)ZenithHelper.Align(imageOffset + (imageCapacity * heapProperties.ImageDescriptorSize), heapProperties.ResourceHeapAlignment), + SizeInBytes = (uint)sizeInBytes, Residency = MemoryResidency.CpuWriteOnly }, BufferUsageFlags.DescriptorHeapBitExt()); return new(context, buffer, - heapProperties.MinResourceHeapReservedRange, - new(bufferBaseIndex, (nuint)heapProperties.BufferDescriptorSize), - new(imageBaseIndex, (nuint)heapProperties.ImageDescriptorSize), + reservedRangeSize, + new(bufferBaseIndex, bufferStride), + new(imageBaseIndex, imageStride), null); } @@ -95,20 +101,25 @@ public static VKDescriptorHeap CreateSamplerHeap(VKGraphicsContext context, uint context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); - ulong samplerOffset = ZenithHelper.Align(heapProperties.MinSamplerHeapReservedRange, heapProperties.SamplerDescriptorSize); - uint samplerBaseIndex = (uint)(samplerOffset / heapProperties.SamplerDescriptorSize); + ulong reservedRangeSize = heapProperties.MinSamplerHeapReservedRange; + ulong samplerStride = heapProperties.SamplerDescriptorSize; + + ulong samplerOffset = ZenithHelper.Align(reservedRangeSize, samplerStride); + uint samplerBaseIndex = (uint)(samplerOffset / samplerStride); + + ulong sizeInBytes = ZenithHelper.Align(samplerOffset + (samplerCapacity * samplerStride), heapProperties.SamplerHeapAlignment); VKBuffer buffer = new(context, new() { - SizeInBytes = (uint)ZenithHelper.Align(samplerOffset + (samplerCapacity * heapProperties.SamplerDescriptorSize), heapProperties.SamplerHeapAlignment), + SizeInBytes = (uint)sizeInBytes, Residency = MemoryResidency.CpuWriteOnly }, BufferUsageFlags.DescriptorHeapBitExt()); return new(context, buffer, - heapProperties.MinSamplerHeapReservedRange, + reservedRangeSize, null, null, - new(samplerBaseIndex, (nuint)heapProperties.SamplerDescriptorSize)); + new(samplerBaseIndex, samplerStride)); } } diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs index c4d1027a..80fc7464 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs @@ -2,7 +2,7 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKDescriptorRegion(uint baseIndex, nuint stride) +internal unsafe class VKDescriptorRegion(uint baseIndex, ulong stride) { private readonly Lock @lock = new(); private readonly Stack recycled = []; @@ -20,7 +20,7 @@ public VKDescriptorToken Allocate(nint pointer, out HostAddressRangeEXT target) index += baseIndex; - target = new((void*)(pointer + (nint)(stride * index)), stride); + target = new((void*)(pointer + (nint)(stride * index)), (nuint)stride); return new(this, index); } From 47d2f2d298a3b8d05dac62c25bd2859f22427561 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 10:50:00 +0800 Subject: [PATCH 318/466] Refactor descriptor heap naming and stride types Renames reservedRangeSize to reservedBytes for clarity and changes VKDescriptorRegion stride from ulong to uint for type consistency. Updates related calculations and buffer size logic to match new naming and types. --- sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs | 39 ++++++++----------- .../Zenith.NET.Vulkan/VKDescriptorRegion.cs | 4 +- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs index 8d6cd6c8..57f50a84 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -4,7 +4,7 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKDescriptorHeap(VKGraphicsContext context, VKBuffer buffer, - ulong reservedRangeSize, + ulong reservedBytes, VKDescriptorRegion? bufferRegion, VKDescriptorRegion? imageRegion, VKDescriptorRegion? samplerRegion) : DisposableObject @@ -13,7 +13,7 @@ internal unsafe class VKDescriptorHeap(VKGraphicsContext context, public DeviceAddressRangeEXT Range => new(buffer.DeviceAddress, buffer.Desc.SizeInBytes); - public ulong ReservedRangeSize => reservedRangeSize; + public ulong ReservedBytes => reservedBytes; public VKDescriptorToken Allocate(ResourceDescriptorInfoEXT info) { @@ -68,29 +68,25 @@ public static VKDescriptorHeap CreateResourceHeap(VKGraphicsContext context, uin context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); - ulong reservedRangeSize = heapProperties.MinResourceHeapReservedRange; - ulong bufferStride = heapProperties.BufferDescriptorSize; - ulong imageStride = heapProperties.ImageDescriptorSize; + ulong reservedBytes = heapProperties.MinResourceHeapReservedRange; - ulong bufferOffset = ZenithHelper.Align(reservedRangeSize, bufferStride); - uint bufferBaseIndex = (uint)(bufferOffset / bufferStride); + ulong bufferStride = heapProperties.BufferDescriptorSize; + ulong bufferOffset = ZenithHelper.Align(reservedBytes, bufferStride); + ulong imageStride = heapProperties.ImageDescriptorSize; ulong imageOffset = ZenithHelper.Align(bufferOffset + (bufferCapacity * bufferStride), imageStride); - uint imageBaseIndex = (uint)(imageOffset / imageStride); - - ulong sizeInBytes = ZenithHelper.Align(imageOffset + (imageCapacity * imageStride), heapProperties.ResourceHeapAlignment); VKBuffer buffer = new(context, new() { - SizeInBytes = (uint)sizeInBytes, + SizeInBytes = (uint)ZenithHelper.Align(imageOffset + (imageCapacity * imageStride), heapProperties.ResourceHeapAlignment), Residency = MemoryResidency.CpuWriteOnly }, BufferUsageFlags.DescriptorHeapBitExt()); return new(context, buffer, - reservedRangeSize, - new(bufferBaseIndex, bufferStride), - new(imageBaseIndex, imageStride), + reservedBytes, + new((uint)(bufferOffset / bufferStride), (uint)bufferStride), + new((uint)(imageOffset / imageStride), (uint)imageStride), null); } @@ -101,25 +97,22 @@ public static VKDescriptorHeap CreateSamplerHeap(VKGraphicsContext context, uint context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); - ulong reservedRangeSize = heapProperties.MinSamplerHeapReservedRange; - ulong samplerStride = heapProperties.SamplerDescriptorSize; - - ulong samplerOffset = ZenithHelper.Align(reservedRangeSize, samplerStride); - uint samplerBaseIndex = (uint)(samplerOffset / samplerStride); + ulong reservedBytes = heapProperties.MinSamplerHeapReservedRange; - ulong sizeInBytes = ZenithHelper.Align(samplerOffset + (samplerCapacity * samplerStride), heapProperties.SamplerHeapAlignment); + ulong samplerStride = heapProperties.SamplerDescriptorSize; + ulong samplerOffset = ZenithHelper.Align(reservedBytes, samplerStride); VKBuffer buffer = new(context, new() { - SizeInBytes = (uint)sizeInBytes, + SizeInBytes = (uint)ZenithHelper.Align(samplerOffset + (samplerCapacity * samplerStride), heapProperties.SamplerHeapAlignment), Residency = MemoryResidency.CpuWriteOnly }, BufferUsageFlags.DescriptorHeapBitExt()); return new(context, buffer, - reservedRangeSize, + reservedBytes, null, null, - new(samplerBaseIndex, samplerStride)); + new((uint)(samplerOffset / samplerStride), (uint)samplerStride)); } } diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs index 80fc7464..626ac59b 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs @@ -2,7 +2,7 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKDescriptorRegion(uint baseIndex, ulong stride) +internal unsafe class VKDescriptorRegion(uint baseIndex, uint stride) { private readonly Lock @lock = new(); private readonly Stack recycled = []; @@ -20,7 +20,7 @@ public VKDescriptorToken Allocate(nint pointer, out HostAddressRangeEXT target) index += baseIndex; - target = new((void*)(pointer + (nint)(stride * index)), (nuint)stride); + target = new((void*)(pointer + (nint)(stride * index)), stride); return new(this, index); } From cf64c21e43e3c8ba10fcf8ecceb10ec806380593 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 11:24:45 +0800 Subject: [PATCH 319/466] Add Vulkan buffer and texture view support Implemented VKBufferView and VKTextureView classes to manage Vulkan descriptor tokens for buffer and texture resources. Extended VKFormats to return both image and view types. Updated VKGraphicsContext to instantiate these new view types and added extension methods for type casting. VKTexture now uses the updated VKFormats return value. --- sources/Zenith.NET.Vulkan/Extensions.cs | 8 +++ sources/Zenith.NET.Vulkan/VKBufferView.cs | 47 ++++++++++++++ sources/Zenith.NET.Vulkan/VKFormats.cs | 44 ++++++++----- .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 4 +- sources/Zenith.NET.Vulkan/VKTexture.cs | 2 +- sources/Zenith.NET.Vulkan/VKTextureView.cs | 64 +++++++++++++++++++ 6 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKBufferView.cs create mode 100644 sources/Zenith.NET.Vulkan/VKTextureView.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index c705638d..71fc3448 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -56,6 +56,10 @@ internal VKBuffer Vulkan() extension(BufferView bufferView) { + internal VKBufferView Vulkan() + { + return (VKBufferView)bufferView; + } } extension(CommandBuffer commandBuffer) @@ -108,6 +112,10 @@ internal VKHeap Vulkan() extension(Texture texture) { + internal VKTexture Vulkan() + { + return (VKTexture)texture; + } } extension(TextureView textureView) diff --git a/sources/Zenith.NET.Vulkan/VKBufferView.cs b/sources/Zenith.NET.Vulkan/VKBufferView.cs new file mode 100644 index 00000000..6b6a54e3 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKBufferView.cs @@ -0,0 +1,47 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKBufferView(VKGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) +{ + private VKDescriptorToken? constantToken; + private VKDescriptorToken? storageReadOnlyToken; + private VKDescriptorToken? storageReadWriteToken; + + public override ResourceHandle ConstantHandle => (constantToken ??= CreateToken(DescriptorType.UniformBuffer)).ResourceHandle; + + public override ResourceHandle StorageReadOnlyHandle => (storageReadOnlyToken ??= CreateToken(DescriptorType.StorageBuffer)).ResourceHandle; + + public override ResourceHandle StorageReadWriteHandle => (storageReadWriteToken ??= CreateToken(DescriptorType.StorageBuffer)).ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + storageReadWriteToken?.Dispose(); + storageReadOnlyToken?.Dispose(); + constantToken?.Dispose(); + } + + private VKDescriptorToken CreateToken(DescriptorType type) + { + DeviceAddressRangeEXT addressRange = new(Desc.Buffer.Vulkan().DeviceAddress + Desc.OffsetInBytes, Desc.SizeInBytes); + + return context.ResourceHeap.Allocate(new ResourceDescriptorInfoEXT() + { + SType = StructureType.ResourceDescriptorInfoExt(), + Type = type, + Data = new() + { + PAddressRange = &addressRange + } + }); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 38c4e4f6..70448af0 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -4,22 +4,36 @@ namespace Zenith.NET.Vulkan; internal static class VKFormats { - public static ImageType Vulkan(TextureType textureType) + public static (ImageType Type, ImageViewType ViewType) Vulkan(TextureType textureType) { - return textureType switch - { - TextureType.Texture1D or - TextureType.Texture1DArray => ImageType.Type1D, - - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ImageType.Type2D, - - TextureType.Texture3D => ImageType.Type3D, - - _ => default - }; + return + ( + textureType switch + { + TextureType.Texture1D or + TextureType.Texture1DArray => ImageType.Type1D, + + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ImageType.Type2D, + + TextureType.Texture3D => ImageType.Type3D, + + _ => default + }, + textureType switch + { + TextureType.Texture1D => ImageViewType.Type1D, + TextureType.Texture1DArray => ImageViewType.Type1DArray, + TextureType.Texture2D => ImageViewType.Type2D, + TextureType.Texture2DArray => ImageViewType.Type2DArray, + TextureType.Texture3D => ImageViewType.Type3D, + TextureType.TextureCube => ImageViewType.TypeCube, + TextureType.TextureCubeArray => ImageViewType.TypeCubeArray, + _ => default + } + ); } public static SampleCountFlags Vulkan(SampleCount sampleCount) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 0f64648e..b235958e 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -571,7 +571,7 @@ protected override Buffer CreateBufferImpl(BufferDesc desc) protected override BufferView CreateBufferViewImpl(BufferViewDesc desc) { - throw new NotImplementedException(); + return new VKBufferView(this, desc); } protected override Texture CreateTextureImpl(TextureDesc desc) @@ -586,7 +586,7 @@ protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { - throw new NotImplementedException(); + return new VKTextureView(this, desc); } protected override Sampler CreateSamplerImpl(SamplerDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index 8a4ed3d4..c88c79b5 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -92,7 +92,7 @@ public static ImageCreateInfo CreateInfo(TextureDesc desc, QueueFamilies queueFa { SType = StructureType.ImageCreateInfo, Flags = desc.Type is TextureType.TextureCube or TextureType.TextureCubeArray ? ImageCreateFlags.CreateCubeCompatibleBit : ImageCreateFlags.None, - ImageType = VKFormats.Vulkan(desc.Type), + ImageType = VKFormats.Vulkan(desc.Type).Type, Format = VKFormats.Vulkan(desc.Format).Format, Extent = new() { diff --git a/sources/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Zenith.NET.Vulkan/VKTextureView.cs new file mode 100644 index 00000000..02e38a0f --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKTextureView.cs @@ -0,0 +1,64 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKTextureView(VKGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) +{ + private VKDescriptorToken? sampledToken; + private VKDescriptorToken? storageToken; + + public override ResourceHandle SampledHandle => (sampledToken ??= CreateToken(DescriptorType.SampledImage, ImageLayout.ShaderReadOnlyOptimal)).ResourceHandle; + + public override ResourceHandle StorageHandle => (storageToken ??= CreateToken(DescriptorType.StorageImage, ImageLayout.General)).ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + storageToken?.Dispose(); + sampledToken?.Dispose(); + } + + private VKDescriptorToken CreateToken(DescriptorType type, ImageLayout layout) + { + ImageViewCreateInfo view = new() + { + SType = StructureType.ImageViewCreateInfo, + Image = Desc.Texture.Vulkan().Image, + ViewType = VKFormats.Vulkan(Desc.Type).ViewType, + Format = VKFormats.Vulkan(Desc.Format).Format, + SubresourceRange = new() + { + AspectMask = VKFormats.Vulkan(Desc.Format).AspectFlags & ~ImageAspectFlags.StencilBit, + BaseMipLevel = Desc.Range.BaseMipLevel, + LevelCount = Desc.Range.LevelCount, + BaseArrayLayer = Desc.Range.BaseArrayLayer, + LayerCount = Desc.Range.LayerCount + } + }; + + ImageDescriptorInfoEXT image = new() + { + SType = StructureType.ImageDescriptorInfoExt(), + PView = &view, + Layout = layout + }; + + return context.ResourceHeap.Allocate(new ResourceDescriptorInfoEXT() + { + SType = StructureType.ResourceDescriptorInfoExt(), + Type = type, + Data = new() + { + PImage = &image + } + }); + } +} From bdf5ad05e36ebec27cbe87ea6d2e9bed001d3d20 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 13:55:50 +0800 Subject: [PATCH 320/466] Add View properties to VKBuffer and VKTexture Introduce VKBufferView and VKTextureView instances in VKBuffer and VKTexture. Update resource handle properties to delegate to View objects and ensure View disposal in Destroy for proper cleanup. --- sources/Zenith.NET.Vulkan/VKBuffer.cs | 31 +++++++++++++++++++++++--- sources/Zenith.NET.Vulkan/VKTexture.cs | 24 ++++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 992a01a1..976e184c 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -55,6 +55,13 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc }; DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); + + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); } public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usage) : base(context, desc) @@ -103,6 +110,13 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usa }; DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); + + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); } public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, VKAllocation allocation) : base(context, desc) @@ -117,15 +131,24 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, VKA }; DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); + + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public override ResourceHandle ConstantHandle { get; } + public VKBufferView View { get; } - public override ResourceHandle StorageReadOnlyHandle { get; } + public override ResourceHandle ConstantHandle => View.ConstantHandle; - public override ResourceHandle StorageReadWriteHandle { get; } + public override ResourceHandle StorageReadOnlyHandle => View.StorageReadOnlyHandle; + + public override ResourceHandle StorageReadWriteHandle => View.StorageReadWriteHandle; public override nint GetNativeObject(NativeObjectType type) { @@ -162,6 +185,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + View.Dispose(); + Context.Vk.DestroyBuffer(Context.Device, Buffer, default); if (Allocation.IsOwned) diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index c88c79b5..34f138f5 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -42,19 +42,37 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, de context.Vk.BindImageMemory(context.Device, Image, deviceMemory, 0).Success(); Allocation = new(deviceMemory, 0, true); + + View = new(context, new() + { + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) + }); } public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image, VKAllocation allocation) : base(context, desc) { Image = image; Allocation = allocation; + + View = new(context, new() + { + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) + }); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public override ResourceHandle SampledHandle { get; } + public VKTextureView View { get; } + + public override ResourceHandle SampledHandle => View.SampledHandle; - public override ResourceHandle StorageHandle { get; } + public override ResourceHandle StorageHandle => View.StorageHandle; public override nint GetNativeObject(NativeObjectType type) { @@ -78,6 +96,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + View.Dispose(); + Context.Vk.DestroyImage(Context.Device, Image, default); if (Allocation.IsOwned) From ad9cc3d4fd0bb9e9db5bc3e191c085cebb17200d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 14:21:25 +0800 Subject: [PATCH 321/466] Add Vulkan sampler and shader resource support Implemented Vulkan-specific conversions in VKFormats for filter, address, compare, and border color enums. Added VKSampler and VKShader classes for resource creation and management. Updated VKGraphicsContext to use these new implementations. Added global usings for VkBorderColor and VkCompareOp. --- sources/Zenith.NET.Vulkan/Usings.cs | 4 +- sources/Zenith.NET.Vulkan/VKFormats.cs | 58 +++++++++++++++++++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 4 +- sources/Zenith.NET.Vulkan/VKSampler.cs | 48 +++++++++++++++ sources/Zenith.NET.Vulkan/VKShader.cs | 29 ++++++++++ 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKSampler.cs create mode 100644 sources/Zenith.NET.Vulkan/VKShader.cs diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 51a7c2f6..751d591e 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -1 +1,3 @@ -global using VkBuffer = Silk.NET.Vulkan.Buffer; \ No newline at end of file +global using VkBorderColor = Silk.NET.Vulkan.BorderColor; +global using VkBuffer = Silk.NET.Vulkan.Buffer; +global using VkCompareOp = Silk.NET.Vulkan.CompareOp; diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 70448af0..6ae69cf9 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -36,6 +36,64 @@ TextureType.TextureCube or ); } + public static (Filter Filter, SamplerMipmapMode MipmapMode) Vulkan(FilterMode filterMode) + { + return + ( + filterMode switch + { + FilterMode.Point => Filter.Nearest, + FilterMode.Linear => Filter.Linear, + _ => default + }, + filterMode switch + { + FilterMode.Point => SamplerMipmapMode.Nearest, + FilterMode.Linear => SamplerMipmapMode.Linear, + _ => default + } + ); + } + + public static SamplerAddressMode Vulkan(AddressMode addressMode) + { + return addressMode switch + { + AddressMode.Wrap => SamplerAddressMode.Repeat, + AddressMode.Mirror => SamplerAddressMode.MirroredRepeat, + AddressMode.Clamp => SamplerAddressMode.ClampToEdge, + AddressMode.Border => SamplerAddressMode.ClampToBorder, + _ => default + }; + } + + public static VkCompareOp Vulkan(CompareOp compareOp) + { + return compareOp switch + { + CompareOp.Never => VkCompareOp.Never, + CompareOp.Less => VkCompareOp.Less, + CompareOp.Equal => VkCompareOp.Equal, + CompareOp.LessEqual => VkCompareOp.LessOrEqual, + CompareOp.Greater => VkCompareOp.Greater, + CompareOp.NotEqual => VkCompareOp.NotEqual, + CompareOp.GreaterEqual => VkCompareOp.GreaterOrEqual, + CompareOp.Always => VkCompareOp.Always, + _ => default + }; + } + + public static VkBorderColor Vulkan(BorderColor borderColor) + { + return borderColor switch + { + BorderColor.TransparentBlack => VkBorderColor.FloatTransparentBlack, + BorderColor.OpaqueBlack => VkBorderColor.FloatOpaqueBlack, + BorderColor.OpaqueWhite => VkBorderColor.FloatOpaqueWhite, + _ => default + }; + } + public static SampleCountFlags Vulkan(SampleCount sampleCount) { return sampleCount switch diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index b235958e..a670cd57 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -591,12 +591,12 @@ protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) protected override Sampler CreateSamplerImpl(SamplerDesc desc) { - throw new NotImplementedException(); + return new VKSampler(this, desc); } protected override Shader CreateShaderImpl(ShaderDesc desc) { - throw new NotImplementedException(); + return new VKShader(this, desc); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKSampler.cs b/sources/Zenith.NET.Vulkan/VKSampler.cs new file mode 100644 index 00000000..068adbb5 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKSampler.cs @@ -0,0 +1,48 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal class VKSampler : Sampler +{ + public VKDescriptorToken Token; + + public VKSampler(VKGraphicsContext context, SamplerDesc desc) : base(context, desc) + { + Token = context.SamplerHeap.Allocate(new SamplerCreateInfo() + { + SType = StructureType.SamplerCreateInfo, + MagFilter = VKFormats.Vulkan(desc.MagFilter).Filter, + MinFilter = VKFormats.Vulkan(desc.MinFilter).Filter, + MipmapMode = VKFormats.Vulkan(desc.MipFilter).MipmapMode, + AddressModeU = VKFormats.Vulkan(desc.AddressU), + AddressModeV = VKFormats.Vulkan(desc.AddressV), + AddressModeW = VKFormats.Vulkan(desc.AddressW), + MipLodBias = desc.LodBias, + AnisotropyEnable = desc.MaxAnisotropy > 1, + MaxAnisotropy = desc.MaxAnisotropy, + CompareEnable = desc.CompareOp is not CompareOp.Never, + CompareOp = VKFormats.Vulkan(desc.CompareOp), + MinLod = desc.MinLod, + MaxLod = desc.MaxLod, + BorderColor = VKFormats.Vulkan(desc.BorderColor) + }); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override ResourceHandle Handle => Token.ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + Token.Dispose(); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKShader.cs b/sources/Zenith.NET.Vulkan/VKShader.cs new file mode 100644 index 00000000..3ff8332d --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKShader.cs @@ -0,0 +1,29 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKShader(VKGraphicsContext context, ShaderDesc desc) : Shader(context, desc) +{ + public ShaderModuleCreateInfo GetShaderModuleCreateInfo(ZenithMarshal.Scope scope) + { + return new() + { + SType = StructureType.ShaderModuleCreateInfo, + CodeSize = (nuint)Desc.CodeBytes.Length, + PCode = (uint*)ZenithMarshal.AllocateAndFill(scope, Desc.CodeBytes) + }; + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + } +} From 41e9fd16e97b6c005de1336f28ccea5dafd4bd54 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 16:00:46 +0800 Subject: [PATCH 322/466] Add descriptor set/binding mapping to VKShader Implemented descriptor set and binding mapping in VKShader by initializing DescriptorSetAndBindingMappingEXT and ShaderDescriptorSetAndBindingMappingInfoEXT. Integrated mapping info into ShaderModuleCreateInfo via the PNext field using ZenithMarshal. --- sources/Zenith.NET.Vulkan/VKShader.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKShader.cs b/sources/Zenith.NET.Vulkan/VKShader.cs index 3ff8332d..58a553b1 100644 --- a/sources/Zenith.NET.Vulkan/VKShader.cs +++ b/sources/Zenith.NET.Vulkan/VKShader.cs @@ -6,9 +6,25 @@ internal unsafe class VKShader(VKGraphicsContext context, ShaderDesc desc) : Sha { public ShaderModuleCreateInfo GetShaderModuleCreateInfo(ZenithMarshal.Scope scope) { + DescriptorSetAndBindingMappingEXT mapping = new() + { + SType = StructureType.DescriptorSetAndBindingMappingExt(), + BindingCount = 1, + ResourceMask = SpirvResourceTypeFlagsEXT.UniformBufferBitExt, + Source = DescriptorMappingSourceEXT.PushAddressExt + }; + + ShaderDescriptorSetAndBindingMappingInfoEXT mappingInfo = new() + { + SType = StructureType.ShaderDescriptorSetAndBindingMappingInfoExt(), + MappingCount = 1, + PMappings = (DescriptorSetAndBindingMappingEXT*)ZenithMarshal.AllocateAndFill(scope, [mapping]) + }; + return new() { SType = StructureType.ShaderModuleCreateInfo, + PNext = (ShaderDescriptorSetAndBindingMappingInfoEXT*)ZenithMarshal.AllocateAndFill(scope, [mappingInfo]), CodeSize = (nuint)Desc.CodeBytes.Length, PCode = (uint*)ZenithMarshal.AllocateAndFill(scope, Desc.CodeBytes) }; From 4a247380add69e50f7fda081ebaf38ede94bc2ea Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 16:16:15 +0800 Subject: [PATCH 323/466] Add Vulkan pipeline classes and extension methods Introduce VKComputePipeline, VKGraphicsPipeline, and VKMeshShadingPipeline classes with resource management and naming support. Add Vulkan() extension methods for pipeline and resource types to enable safe casting. Update VKGraphicsContext to instantiate Vulkan pipeline classes. Add global alias for VkPipeline. --- sources/Zenith.NET.Vulkan/Extensions.cs | 24 ++++++++++++ sources/Zenith.NET.Vulkan/Usings.cs | 1 + .../Zenith.NET.Vulkan/VKComputePipeline.cs | 39 +++++++++++++++++++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 6 +-- .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 39 +++++++++++++++++++ .../VKMeshShadingPipeline.cs | 39 +++++++++++++++++++ 6 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKComputePipeline.cs create mode 100644 sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs create mode 100644 sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 71fc3448..38e89f52 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -76,10 +76,18 @@ internal VKCommandQueue Vulkan() extension(ComputePipeline computePipeline) { + internal VKComputePipeline Vulkan() + { + return (VKComputePipeline)computePipeline; + } } extension(GraphicsPipeline graphicsPipeline) { + internal VKGraphicsPipeline Vulkan() + { + return (VKGraphicsPipeline)graphicsPipeline; + } } extension(Heap heap) @@ -92,6 +100,10 @@ internal VKHeap Vulkan() extension(MeshShadingPipeline meshShadingPipeline) { + internal VKMeshShadingPipeline Vulkan() + { + return (VKMeshShadingPipeline)meshShadingPipeline; + } } extension(QueryHeap queryHeap) @@ -100,10 +112,18 @@ internal VKHeap Vulkan() extension(Sampler sampler) { + internal VKSampler Vulkan() + { + return (VKSampler)sampler; + } } extension(Shader shader) { + internal VKShader Vulkan() + { + return (VKShader)shader; + } } extension(SwapChain swapChain) @@ -120,6 +140,10 @@ internal VKTexture Vulkan() extension(TextureView textureView) { + internal VKTextureView Vulkan() + { + return (VKTextureView)textureView; + } } extension(TopLevelAccelerationStructure topLevelAccelerationStructure) diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 751d591e..66e91731 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -1,3 +1,4 @@ global using VkBorderColor = Silk.NET.Vulkan.BorderColor; global using VkBuffer = Silk.NET.Vulkan.Buffer; global using VkCompareOp = Silk.NET.Vulkan.CompareOp; +global using VkPipeline = Silk.NET.Vulkan.Pipeline; diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs new file mode 100644 index 00000000..b13b10e5 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -0,0 +1,39 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKComputePipeline : ComputePipeline +{ + public VkPipeline Pipeline; + + public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) + { + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Pipeline, + ObjectHandle = Pipeline.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyPipeline(Context.Device, Pipeline, default); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index a670cd57..1b69d84d 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -601,17 +601,17 @@ protected override Shader CreateShaderImpl(ShaderDesc desc) protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) { - throw new NotImplementedException(); + return new VKGraphicsPipeline(this, desc); } protected override ComputePipeline CreateComputePipelineImpl(ComputePipelineDesc desc) { - throw new NotImplementedException(); + return new VKComputePipeline(this, desc); } protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShadingPipelineDesc desc) { - throw new NotImplementedException(); + return new VKMeshShadingPipeline(this, desc); } protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs new file mode 100644 index 00000000..7c231909 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -0,0 +1,39 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKGraphicsPipeline : GraphicsPipeline +{ + public VkPipeline Pipeline; + + public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) + { + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Pipeline, + ObjectHandle = Pipeline.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyPipeline(Context.Device, Pipeline, default); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs new file mode 100644 index 00000000..5d460a4e --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -0,0 +1,39 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKMeshShadingPipeline : MeshShadingPipeline +{ + public VkPipeline Pipeline; + + public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) + { + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Pipeline, + ObjectHandle = Pipeline.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyPipeline(Context.Device, Pipeline, default); + } +} From 756a66df85b0e46cf59645dee17ca19ea4f2e4f7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 16:42:27 +0800 Subject: [PATCH 324/466] Refactor pipeline creation for DX12 and Vulkan Refactored attachment format and input layout handling in DXGraphicsPipeline and DXMeshShadingPipeline for clarity and correctness. Added a new VKGraphicsPipeline constructor to set up shader stages, input layouts, primitive topology, and attachment formats. Updated VKShader to provide PipelineShaderStageCreateInfo. Introduced helper stubs in VKFormats and added a global using for VkPrimitiveTopology. These changes improve pipeline extensibility and maintainability. --- .../DXGraphicsPipeline.cs | 5 +- .../DXMeshShadingPipeline.cs | 5 +- sources/Zenith.NET.Vulkan/Usings.cs | 1 + sources/Zenith.NET.Vulkan/VKFormats.cs | 10 +++ .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 83 +++++++++++++++++++ sources/Zenith.NET.Vulkan/VKShader.cs | 12 ++- 6 files changed, 108 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs index fc44cd3d..9e6ccd1e 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -1,6 +1,5 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; -using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -50,6 +49,8 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) // AttachmentFormats { + pipelineStateStream.DSVFormat = DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown); + pipelineStateStream.RTVFormats.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) @@ -57,8 +58,6 @@ public DXGraphicsPipeline(DXGraphicsContext context, GraphicsPipelineDesc desc) pipelineStateStream.RTVFormats.RTFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); } - pipelineStateStream.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; - pipelineStateStream.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); } diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index ef146a40..4b0329aa 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -1,6 +1,5 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; -using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -24,6 +23,8 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc // AttachmentFormats { + pipelineStateStream.DSVFormat = DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown); + pipelineStateStream.RTVFormats.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) @@ -31,8 +32,6 @@ public DXMeshShadingPipeline(DXGraphicsContext context, MeshShadingPipelineDesc pipelineStateStream.RTVFormats.RTFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); } - pipelineStateStream.DSVFormat = desc.AttachmentFormats.DepthStencilFormat.HasValue ? DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat.Value) : Format.FormatUnknown; - pipelineStateStream.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); } diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 66e91731..8a0dea00 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -2,3 +2,4 @@ global using VkBuffer = Silk.NET.Vulkan.Buffer; global using VkCompareOp = Silk.NET.Vulkan.CompareOp; global using VkPipeline = Silk.NET.Vulkan.Pipeline; +global using VkPrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology; diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 6ae69cf9..04d658cb 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -306,4 +306,14 @@ public static ImageUsageFlags Vulkan(TextureUsages textureUsages) return result; } + + public static Format Vulkan(ElementFormat elementFormat) + { + throw new NotImplementedException(); + } + + public static VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 7c231909..ab9efe63 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -8,6 +8,89 @@ internal unsafe class VKGraphicsPipeline : GraphicsPipeline public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) { + using ZenithMarshal.Scope scope = new(); + + GraphicsPipelineCreateInfo createInfo = new() + { + SType = StructureType.GraphicsPipelineCreateInfo, + StageCount = 2, + PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, + [ + desc.VertexShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.VertexBit), + desc.FragmentShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.FragmentBit) + ]) + }; + + // InputLayouts + { + uint vertexBindingDescriptionCount = (uint)desc.InputLayouts.Length; + uint vertexAttributeDescriptionCount = (uint)desc.InputLayouts.Sum(static item => item.Elements.Length); + + VertexInputBindingDescription* vertexBindingDescriptions = (VertexInputBindingDescription*)ZenithMarshal.Allocate(scope, vertexBindingDescriptionCount); + VertexInputAttributeDescription* vertexAttributeDescriptions = (VertexInputAttributeDescription*)ZenithMarshal.Allocate(scope, vertexAttributeDescriptionCount); + + uint binding = 0; + uint attribute = 0; + foreach (InputLayout inputLayout in desc.InputLayouts) + { + vertexBindingDescriptions[binding] = new() + { + Binding = binding, + Stride = inputLayout.StrideInBytes + }; + + foreach (InputElement element in inputLayout.Elements) + { + vertexAttributeDescriptions[attribute] = new() + { + Location = attribute, + Binding = binding, + Format = VKFormats.Vulkan(element.Format), + Offset = element.OffsetInBytes + }; + + attribute++; + } + + binding++; + } + + PipelineVertexInputStateCreateInfo vertexInputState = new() + { + SType = StructureType.PipelineVertexInputStateCreateInfo, + VertexBindingDescriptionCount = vertexBindingDescriptionCount, + PVertexBindingDescriptions = vertexBindingDescriptions, + VertexAttributeDescriptionCount = vertexAttributeDescriptionCount, + PVertexAttributeDescriptions = vertexAttributeDescriptions + }; + + createInfo.PVertexInputState = &vertexInputState; + } + + // PrimitiveTopology + { + PipelineInputAssemblyStateCreateInfo inputAssemblyState = new() + { + SType = StructureType.PipelineInputAssemblyStateCreateInfo, + Topology = VKFormats.Vulkan(desc.PrimitiveTopology) + }; + + createInfo.PInputAssemblyState = &inputAssemblyState; + } + + // AttachmentFormats + { + PipelineRenderingCreateInfo rendering = new() + { + SType = StructureType.PipelineRenderingCreateInfo, + ColorAttachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length, + PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item))]), + DepthAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format, + StencilAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format + }; + + createInfo.PNext = &rendering; + } } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; diff --git a/sources/Zenith.NET.Vulkan/VKShader.cs b/sources/Zenith.NET.Vulkan/VKShader.cs index 58a553b1..b8fadef9 100644 --- a/sources/Zenith.NET.Vulkan/VKShader.cs +++ b/sources/Zenith.NET.Vulkan/VKShader.cs @@ -4,7 +4,7 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKShader(VKGraphicsContext context, ShaderDesc desc) : Shader(context, desc) { - public ShaderModuleCreateInfo GetShaderModuleCreateInfo(ZenithMarshal.Scope scope) + public PipelineShaderStageCreateInfo GetPipelineShaderStageCreateInfo(ZenithMarshal.Scope scope, ShaderStageFlags stage) { DescriptorSetAndBindingMappingEXT mapping = new() { @@ -21,13 +21,21 @@ public ShaderModuleCreateInfo GetShaderModuleCreateInfo(ZenithMarshal.Scope scop PMappings = (DescriptorSetAndBindingMappingEXT*)ZenithMarshal.AllocateAndFill(scope, [mapping]) }; - return new() + ShaderModuleCreateInfo createInfo = new() { SType = StructureType.ShaderModuleCreateInfo, PNext = (ShaderDescriptorSetAndBindingMappingInfoEXT*)ZenithMarshal.AllocateAndFill(scope, [mappingInfo]), CodeSize = (nuint)Desc.CodeBytes.Length, PCode = (uint*)ZenithMarshal.AllocateAndFill(scope, Desc.CodeBytes) }; + + return new() + { + SType = StructureType.PipelineShaderStageCreateInfo, + PNext = (ShaderModuleCreateInfo*)ZenithMarshal.AllocateAndFill(scope, [createInfo]), + Stage = stage, + PName = (byte*)ZenithMarshal.StringToPointer(scope, Desc.Name, StringEncoding.UTF8) + }; } public override nint GetNativeObject(NativeObjectType type) From 76691c41ceabd27ddb27d9be836ca328b913ce62 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 16:44:40 +0800 Subject: [PATCH 325/466] Add dynamic state support to Vulkan pipeline creation Enable dynamic states for viewport, scissor, stencil reference, and blend constants in the Vulkan graphics pipeline. Introduce PipelineDynamicStateCreateInfo and link it to pipeline creation info. Add an empty RenderState block as a placeholder for future enhancements. --- sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index ab9efe63..62486930 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -91,6 +91,21 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) createInfo.PNext = &rendering; } + + // RenderState + { + } + + PipelineDynamicStateCreateInfo dynamicState = new() + { + SType = StructureType.PipelineDynamicStateCreateInfo, + DynamicStateCount = 4, + PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.StencilReference, DynamicState.BlendConstants]) + }; + + createInfo.PDynamicState = &dynamicState; + + context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; From 16e2afec442ae133ea44b46861d8c004b807dbcb Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 16:51:52 +0800 Subject: [PATCH 326/466] Reorder dynamic states in VKGraphicsPipeline creation Changed the order of dynamic states in the `PDynamicStates` array from `[Viewport, Scissor, StencilReference, BlendConstants]` to `[Viewport, Scissor, BlendConstants, StencilReference]` for consistency or compatibility. No other logic was affected. --- sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 62486930..67a7b664 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -100,7 +100,7 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) { SType = StructureType.PipelineDynamicStateCreateInfo, DynamicStateCount = 4, - PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.StencilReference, DynamicState.BlendConstants]) + PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.BlendConstants, DynamicState.StencilReference]) }; createInfo.PDynamicState = &dynamicState; From a338bb1605c56d89c88df79e86d61074460701a2 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:05:14 +0800 Subject: [PATCH 327/466] Refactor viewport, scissor, and stencil setup in CommandBuffer Reordered SetViewports and SetScissors calls in BeginRenderPass for correct state setup. Refactored related methods in CommandBuffer, DXCommandBuffer, and MTLCommandBuffer for clarity and consistency. Added or updated SetStencilReference handling to support deferred and immediate execution. Improved method ordering and separation of responsibilities. --- .../Zenith.NET.DirectX12/DXCommandBuffer.cs | 68 +++++++-------- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 86 +++++++++---------- sources/Zenith.NET/CommandBuffer.cs | 36 ++++---- 3 files changed, 95 insertions(+), 95 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 0fa402b2..da60e729 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -294,29 +294,20 @@ protected override void EndRenderPassImpl() CommandList.EndRenderPass(); } - protected override void SetScissorsImpl(ReadOnlySpan scissors) + protected override void SetPipelineImpl(GraphicsPipeline pipeline) { - Box2D* pRects = stackalloc Box2D[scissors.Length]; - for (int i = 0; i < scissors.Length; i++) - { - Scissor scissor = scissors[i]; + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + CommandList.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).Topology); + } - pRects[i] = new() - { - Min = new() - { - X = scissor.X, - Y = scissor.Y - }, - Max = new() - { - X = (int)(scissor.X + scissor.Width), - Y = (int)(scissor.Y + scissor.Height) - } - }; - } + protected override void SetPipelineImpl(ComputePipeline pipeline) + { + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + } - CommandList.RSSetScissorRects((uint)scissors.Length, pRects); + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + { + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); } protected override void SetViewportsImpl(ReadOnlySpan viewports) @@ -340,20 +331,34 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) CommandList.RSSetViewports((uint)viewports.Length, pViewports); } - protected override void SetPipelineImpl(GraphicsPipeline pipeline) + protected override void SetScissorsImpl(ReadOnlySpan scissors) { - CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); - CommandList.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).Topology); - } + Box2D* pRects = stackalloc Box2D[scissors.Length]; + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; - protected override void SetPipelineImpl(ComputePipeline pipeline) - { - CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + pRects[i] = new() + { + Min = new() + { + X = scissor.X, + Y = scissor.Y + }, + Max = new() + { + X = (int)(scissor.X + scissor.Width), + Y = (int)(scissor.Y + scissor.Height) + } + }; + } + + CommandList.RSSetScissorRects((uint)scissors.Length, pRects); } - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + protected override void SetBlendConstantImpl(Vector4 blendConstant) { - CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + CommandList.OMSetBlendFactor(&blendConstant.X); } protected override void SetStencilReferenceImpl(uint stencilReference) @@ -361,11 +366,6 @@ protected override void SetStencilReferenceImpl(uint stencilReference) CommandList.OMSetStencilRef(stencilReference); } - protected override void SetBlendConstantImpl(Vector4 blendConstant) - { - CommandList.OMSetBlendFactor(&blendConstant.X); - } - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { VertexBufferView view = new() diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 507e9877..6d8fd9dc 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -212,46 +212,6 @@ protected override void EndRenderPassImpl() BeginComputeEncoding(); } - protected override void SetScissorsImpl(ReadOnlySpan scissors) - { - if (Render is null) - { - todoScissors = [.. scissors]; - } - else - { - MTLScissorRect[] mtlScissors = new MTLScissorRect[scissors.Length]; - for (int i = 0; i < scissors.Length; i++) - { - Scissor scissor = scissors[i]; - - mtlScissors[i] = new((uint)scissor.X, (uint)scissor.Y, scissor.Width, scissor.Height); - } - - Render.SetScissorRects(mtlScissors); - } - } - - protected override void SetViewportsImpl(ReadOnlySpan viewports) - { - if (Render is null) - { - todoViewports = [.. viewports]; - } - else - { - MTLViewport[] mtlViewports = new MTLViewport[viewports.Length]; - for (int i = 0; i < viewports.Length; i++) - { - Viewport viewport = viewports[i]; - - mtlViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); - } - - Render.SetViewports(mtlViewports); - } - } - protected override void SetPipelineImpl(GraphicsPipeline pipeline) { if (Render is null) @@ -322,15 +282,43 @@ protected override void SetPipelineImpl(MeshShadingPipeline pipeline) } } - protected override void SetStencilReferenceImpl(uint stencilReference) + protected override void SetViewportsImpl(ReadOnlySpan viewports) { if (Render is null) { - todoStencilReference = stencilReference; + todoViewports = [.. viewports]; } else { - Render.SetStencilReferenceValue(stencilReference); + MTLViewport[] mtlViewports = new MTLViewport[viewports.Length]; + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; + + mtlViewports[i] = new(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); + } + + Render.SetViewports(mtlViewports); + } + } + + protected override void SetScissorsImpl(ReadOnlySpan scissors) + { + if (Render is null) + { + todoScissors = [.. scissors]; + } + else + { + MTLScissorRect[] mtlScissors = new MTLScissorRect[scissors.Length]; + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; + + mtlScissors[i] = new((uint)scissor.X, (uint)scissor.Y, scissor.Width, scissor.Height); + } + + Render.SetScissorRects(mtlScissors); } } @@ -346,6 +334,18 @@ protected override void SetBlendConstantImpl(Vector4 blendConstant) } } + protected override void SetStencilReferenceImpl(uint stencilReference) + { + if (Render is null) + { + todoStencilReference = stencilReference; + } + else + { + Render.SetStencilReferenceValue(stencilReference); + } + } + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { ArgumentTable.SetAddress(buffer.Metal().Buffer.GpuAddress + offsetInBytes, pipeline.Desc.InputLayouts[slot].StrideInBytes, 1 + slot); diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index aeccb89e..54ea8124 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -263,8 +263,8 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept }; } - SetScissorsImpl(scissors); SetViewportsImpl(viewports); + SetScissorsImpl(scissors); BeginRenderPassImpl(colorAttachments, depthStencilAttachment); } @@ -273,16 +273,6 @@ public void EndRenderPass() EndRenderPassImpl(); } - public void SetScissors(ReadOnlySpan scissors) - { - SetScissorsImpl(scissors); - } - - public void SetViewports(ReadOnlySpan viewports) - { - SetViewportsImpl(viewports); - } - public void SetPipeline(GraphicsPipeline pipeline) { SetPipelineImpl(pipeline); @@ -304,9 +294,14 @@ public void SetPipeline(MeshShadingPipeline pipeline) currentPipeline = pipeline; } - public void SetStencilReference(uint stencilReference) + public void SetViewports(ReadOnlySpan viewports) { - SetStencilReferenceImpl(stencilReference); + SetViewportsImpl(viewports); + } + + public void SetScissors(ReadOnlySpan scissors) + { + SetScissorsImpl(scissors); } public void SetBlendConstant(Vector4 blendConstant) @@ -314,6 +309,11 @@ public void SetBlendConstant(Vector4 blendConstant) SetBlendConstantImpl(blendConstant); } + public void SetStencilReference(uint stencilReference) + { + SetStencilReferenceImpl(stencilReference); + } + public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) { if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) @@ -536,20 +536,20 @@ private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPip protected abstract void EndRenderPassImpl(); - protected abstract void SetScissorsImpl(ReadOnlySpan scissors); - - protected abstract void SetViewportsImpl(ReadOnlySpan viewports); - protected abstract void SetPipelineImpl(GraphicsPipeline pipeline); protected abstract void SetPipelineImpl(ComputePipeline pipeline); protected abstract void SetPipelineImpl(MeshShadingPipeline pipeline); - protected abstract void SetStencilReferenceImpl(uint stencilReference); + protected abstract void SetViewportsImpl(ReadOnlySpan viewports); + + protected abstract void SetScissorsImpl(ReadOnlySpan scissors); protected abstract void SetBlendConstantImpl(Vector4 blendConstant); + protected abstract void SetStencilReferenceImpl(uint stencilReference); + protected abstract void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot); protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); From 7b1009e7759e0ee962170c13f1c34cd48ebaec9a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:11:05 +0800 Subject: [PATCH 328/466] Refactor deferred state application order in encoding Reorder and group deferred state application in BeginRenderEncoding to ensure pipelines are set before dependent states like scissors and blend constants. Also, move related field declarations for clarity and maintainability. --- sources/Zenith.NET.Metal/MTLCommandBuffer.cs | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 6d8fd9dc..664facdb 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -23,13 +23,13 @@ internal unsafe class MTLCommandBuffer : CommandBuffer private uint visibilityIndex; private IndexBinding indexBinding; - private Scissor[]? todoScissors; - private Viewport[]? todoViewports; private GraphicsPipeline? todoGraphicsPipeline; private ComputePipeline? todoComputePipeline; private MeshShadingPipeline? todoMeshShadingPipeline; - private uint? todoStencilReference; + private Viewport[]? todoViewports; + private Scissor[]? todoScissors; private Vector4? todoBlendConstant; + private uint? todoStencilReference; public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { @@ -551,11 +551,18 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) Render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); Render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Mesh); - if (todoScissors is not null) + if (todoGraphicsPipeline is not null) { - SetScissors(todoScissors); + SetPipeline(todoGraphicsPipeline); - todoScissors = null; + todoGraphicsPipeline = null; + } + + if (todoMeshShadingPipeline is not null) + { + SetPipeline(todoMeshShadingPipeline); + + todoMeshShadingPipeline = null; } if (todoViewports is not null) @@ -565,18 +572,18 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) todoViewports = null; } - if (todoGraphicsPipeline is not null) + if (todoScissors is not null) { - SetPipeline(todoGraphicsPipeline); + SetScissors(todoScissors); - todoGraphicsPipeline = null; + todoScissors = null; } - if (todoMeshShadingPipeline is not null) + if (todoBlendConstant is not null) { - SetPipeline(todoMeshShadingPipeline); + SetBlendConstant(todoBlendConstant.Value); - todoMeshShadingPipeline = null; + todoBlendConstant = null; } if (todoStencilReference is not null) @@ -586,13 +593,6 @@ private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) todoStencilReference = null; } - if (todoBlendConstant is not null) - { - SetBlendConstant(todoBlendConstant.Value); - - todoBlendConstant = null; - } - foreach (VisibilityBinding visibilityBinding in beginVisibilityBindings) { Render.SetVisibilityResultMode(MTLFormats.Metal(visibilityBinding.QueryHeap.Desc.Type), sizeof(ulong) * visibilityBinding.ScratchIndex); From 3e749e9ecd0e2cf1b62c5052ed2a3c643d395099 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:17:38 +0800 Subject: [PATCH 329/466] Initialize blend constant and stencil reference to zero Set blend constant to Vector4.Zero and stencil reference to 0 before starting a render pass in CommandBuffer to ensure consistent initial state. --- sources/Zenith.NET/CommandBuffer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 54ea8124..3bd08d48 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -265,6 +265,8 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept SetViewportsImpl(viewports); SetScissorsImpl(scissors); + SetBlendConstantImpl(Vector4.Zero); + SetStencilReferenceImpl(0); BeginRenderPassImpl(colorAttachments, depthStencilAttachment); } From 209d437ab1794944b7a5e412312750622bfc2f95 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:23:16 +0800 Subject: [PATCH 330/466] Change default blend constant to Vector4.One in CommandBuffer The default blend constant in the CommandBuffer constructor is now Vector4.One instead of Vector4.Zero, making the default blend color fully white/opaque. This affects how blending is performed in subsequent render passes. --- sources/Zenith.NET/CommandBuffer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 3bd08d48..3dd2c689 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -265,7 +265,7 @@ public void BeginRenderPass(ReadOnlySpan colorAttachments, Dept SetViewportsImpl(viewports); SetScissorsImpl(scissors); - SetBlendConstantImpl(Vector4.Zero); + SetBlendConstantImpl(Vector4.One); SetStencilReferenceImpl(0); BeginRenderPassImpl(colorAttachments, depthStencilAttachment); } From b787118d19ba59ebb085f267dbfa0836e30a90c4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:25:44 +0800 Subject: [PATCH 331/466] Refactor VKComputePipeline constructor for Vulkan API Refactored the VKComputePipeline constructor to use ZenithMarshal.Scope for memory management and explicitly construct ComputePipelineCreateInfo for Vulkan pipeline creation, improving clarity and structure of pipeline initialization. --- sources/Zenith.NET.Vulkan/VKComputePipeline.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index b13b10e5..67db5b58 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -8,6 +8,15 @@ internal unsafe class VKComputePipeline : ComputePipeline public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) { + using ZenithMarshal.Scope scope = new(); + + ComputePipelineCreateInfo createInfo = new() + { + SType = StructureType.ComputePipelineCreateInfo, + Stage = desc.ComputeShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.ComputeBit) + }; + + context.Vk.CreateComputePipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; From 29056635e0c132ffe63313c54b303d5777693a28 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:29:20 +0800 Subject: [PATCH 332/466] Expand VKMeshShadingPipeline Vulkan pipeline setup Refactor constructor to fully configure Vulkan mesh shading pipeline, including shader stages, input assembly, attachment formats, and dynamic state using ZenithMarshal.Scope. --- .../VKMeshShadingPipeline.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 5d460a4e..73c6e818 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -8,6 +8,69 @@ internal unsafe class VKMeshShadingPipeline : MeshShadingPipeline public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) { + using ZenithMarshal.Scope scope = new(); + + GraphicsPipelineCreateInfo createInfo = new() + { + SType = StructureType.GraphicsPipelineCreateInfo, + StageCount = 2, + PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, + [ + desc.MeshShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.MeshBitExt), + desc.FragmentShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.FragmentBit) + ]) + }; + + if (desc.TaskShader is not null) + { + createInfo.StageCount = 3; + createInfo.PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, + [ + desc.TaskShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.TaskBitExt), + desc.MeshShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.MeshBitExt), + desc.FragmentShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.FragmentBit) + ]); + } + + // PrimitiveTopology + { + PipelineInputAssemblyStateCreateInfo inputAssemblyState = new() + { + SType = StructureType.PipelineInputAssemblyStateCreateInfo, + Topology = VKFormats.Vulkan(desc.PrimitiveTopology) + }; + + createInfo.PInputAssemblyState = &inputAssemblyState; + } + + // AttachmentFormats + { + PipelineRenderingCreateInfo rendering = new() + { + SType = StructureType.PipelineRenderingCreateInfo, + ColorAttachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length, + PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item))]), + DepthAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format, + StencilAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format + }; + + createInfo.PNext = &rendering; + } + + // RenderState + { + } + + PipelineDynamicStateCreateInfo dynamicState = new() + { + SType = StructureType.PipelineDynamicStateCreateInfo, + DynamicStateCount = 4, + PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor, DynamicState.BlendConstants, DynamicState.StencilReference]) + }; + + createInfo.PDynamicState = &dynamicState; + + context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; From b3371060152c25d4780f49bc8ff2058fbc1310f6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:51:24 +0800 Subject: [PATCH 333/466] Add Vk2DescriptorHeapBitExt flag to pipeline creation Enable Vk2DescriptorHeapBitExt in compute, graphics, and mesh shading pipeline creation by appending PipelineCreateFlags2CreateInfo to the pNext chain. This prepares pipelines for VK_EXT_descriptor_buffer and related features. --- sources/Zenith.NET.Vulkan/VKComputePipeline.cs | 3 +++ sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 3 +++ sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index 67db5b58..5df5043c 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -16,6 +16,9 @@ public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : Stage = desc.ComputeShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.ComputeBit) }; + createInfo.AddNext(out PipelineCreateFlags2CreateInfo flags2CreateInfo); + flags2CreateInfo.Flags = PipelineCreateFlags2.Vk2DescriptorHeapBitExt(); + context.Vk.CreateComputePipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 67a7b664..9127bb71 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -105,6 +105,9 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) createInfo.PDynamicState = &dynamicState; + createInfo.AddNext(out PipelineCreateFlags2CreateInfo flags2CreateInfo); + flags2CreateInfo.Flags = PipelineCreateFlags2.Vk2DescriptorHeapBitExt(); + context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 73c6e818..bbde7b87 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -70,6 +70,9 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc createInfo.PDynamicState = &dynamicState; + createInfo.AddNext(out PipelineCreateFlags2CreateInfo flags2CreateInfo); + flags2CreateInfo.Flags = PipelineCreateFlags2.Vk2DescriptorHeapBitExt(); + context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } From a8918d30e893268851b81a3ec5537f5a969e05dc Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 17:54:30 +0800 Subject: [PATCH 334/466] Fix color attachment format assignment in VK pipelines Update VKGraphicsPipeline and VKMeshShadingPipeline to use the .Format property from VKFormats.Vulkan when assigning color attachment formats. This ensures only the correct Vulkan format values are passed to PColorAttachmentFormats. --- sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 2 +- sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 9127bb71..84eef608 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -84,7 +84,7 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) { SType = StructureType.PipelineRenderingCreateInfo, ColorAttachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length, - PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item))]), + PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item).Format)]), DepthAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format, StencilAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format }; diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index bbde7b87..89c694e4 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -49,7 +49,7 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc { SType = StructureType.PipelineRenderingCreateInfo, ColorAttachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length, - PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item))]), + PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item).Format)]), DepthAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format, StencilAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format }; From 962620f3c3381dbc709e92125093e11406012516 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 18:30:29 +0800 Subject: [PATCH 335/466] Rename local variables for consistency in VKGraphicsContext Renamed deviceRequirements variables to requirements in GetSizeAndAlignmentImpl methods for both buffers and images. This change improves naming consistency without affecting logic. --- sources/Zenith.NET.Vulkan/VKGraphicsContext.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 1b69d84d..f4e770be 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -531,7 +531,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, QueueFamilies); - DeviceBufferMemoryRequirements deviceRequirements = new() + DeviceBufferMemoryRequirements requirements = new() { SType = StructureType.DeviceBufferMemoryRequirements, PCreateInfo = &createInfo @@ -539,7 +539,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; - Vk.GetDeviceBufferMemoryRequirements(Device, &deviceRequirements, &requirements2); + Vk.GetDeviceBufferMemoryRequirements(Device, &requirements, &requirements2); return new(requirements2.MemoryRequirements.Size, requirements2.MemoryRequirements.Alignment); } @@ -548,7 +548,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) { ImageCreateInfo createInfo = VKTexture.CreateInfo(desc, QueueFamilies); - DeviceImageMemoryRequirements deviceRequirements = new() + DeviceImageMemoryRequirements requirements = new() { SType = StructureType.DeviceImageMemoryRequirements, PCreateInfo = &createInfo @@ -556,7 +556,7 @@ protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; - Vk.GetDeviceImageMemoryRequirements(Device, &deviceRequirements, &requirements2); + Vk.GetDeviceImageMemoryRequirements(Device, &requirements, &requirements2); PhysicalDeviceProperties properties; Vk.GetPhysicalDeviceProperties(PhysicalDevice, &properties); From 49e8481fd378998cdf0675c06b3af1156b2bff66 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 18:35:10 +0800 Subject: [PATCH 336/466] Rename local variables for clarity and consistency Renamed allocateFlagsInfo to flagsInfo in VKBuffer.cs and VKHeap.cs, and heapProperties to descriptorHeapProperties in VKDescriptorHeap.cs. These changes improve readability and maintain consistent naming without altering logic. --- sources/Zenith.NET.Vulkan/VKBuffer.cs | 8 ++++---- sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs | 18 +++++++++--------- sources/Zenith.NET.Vulkan/VKHeap.cs | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 976e184c..76d49745 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -40,8 +40,8 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc dedicatedAllocateInfo.Buffer = Buffer; } - allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); - allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + allocateInfo.AddNext(out MemoryAllocateFlagsInfo flagsInfo); + flagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); @@ -95,8 +95,8 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usa dedicatedAllocateInfo.Buffer = Buffer; } - allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); - allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + allocateInfo.AddNext(out MemoryAllocateFlagsInfo flagsInfo); + flagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs index 57f50a84..af46c090 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -64,21 +64,21 @@ protected override void Destroy() public static VKDescriptorHeap CreateResourceHeap(VKGraphicsContext context, uint bufferCapacity, uint imageCapacity) { PhysicalDeviceProperties2 properties2 = new() { SType = StructureType.PhysicalDeviceProperties2 }; - properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT heapProperties); + properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties); context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); - ulong reservedBytes = heapProperties.MinResourceHeapReservedRange; + ulong reservedBytes = descriptorHeapProperties.MinResourceHeapReservedRange; - ulong bufferStride = heapProperties.BufferDescriptorSize; + ulong bufferStride = descriptorHeapProperties.BufferDescriptorSize; ulong bufferOffset = ZenithHelper.Align(reservedBytes, bufferStride); - ulong imageStride = heapProperties.ImageDescriptorSize; + ulong imageStride = descriptorHeapProperties.ImageDescriptorSize; ulong imageOffset = ZenithHelper.Align(bufferOffset + (bufferCapacity * bufferStride), imageStride); VKBuffer buffer = new(context, new() { - SizeInBytes = (uint)ZenithHelper.Align(imageOffset + (imageCapacity * imageStride), heapProperties.ResourceHeapAlignment), + SizeInBytes = (uint)ZenithHelper.Align(imageOffset + (imageCapacity * imageStride), descriptorHeapProperties.ResourceHeapAlignment), Residency = MemoryResidency.CpuWriteOnly }, BufferUsageFlags.DescriptorHeapBitExt()); @@ -93,18 +93,18 @@ public static VKDescriptorHeap CreateResourceHeap(VKGraphicsContext context, uin public static VKDescriptorHeap CreateSamplerHeap(VKGraphicsContext context, uint samplerCapacity) { PhysicalDeviceProperties2 properties2 = new() { SType = StructureType.PhysicalDeviceProperties2 }; - properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT heapProperties); + properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties); context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); - ulong reservedBytes = heapProperties.MinSamplerHeapReservedRange; + ulong reservedBytes = descriptorHeapProperties.MinSamplerHeapReservedRange; - ulong samplerStride = heapProperties.SamplerDescriptorSize; + ulong samplerStride = descriptorHeapProperties.SamplerDescriptorSize; ulong samplerOffset = ZenithHelper.Align(reservedBytes, samplerStride); VKBuffer buffer = new(context, new() { - SizeInBytes = (uint)ZenithHelper.Align(samplerOffset + (samplerCapacity * samplerStride), heapProperties.SamplerHeapAlignment), + SizeInBytes = (uint)ZenithHelper.Align(samplerOffset + (samplerCapacity * samplerStride), descriptorHeapProperties.SamplerHeapAlignment), Residency = MemoryResidency.CpuWriteOnly }, BufferUsageFlags.DescriptorHeapBitExt()); diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index c1b9586a..3c07c5c0 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -15,8 +15,8 @@ public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) MemoryTypeIndex = context.FindMemoryTypeIndex(uint.MaxValue, desc.Residency) }; - allocateInfo.AddNext(out MemoryAllocateFlagsInfo allocateFlagsInfo); - allocateFlagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + allocateInfo.AddNext(out MemoryAllocateFlagsInfo flagsInfo); + flagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); } From d3e54420a26d7bf5330ebce9fd72326f01f6edcd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 22:54:25 +0800 Subject: [PATCH 337/466] Implement Vulkan enum mappings and expand pipeline setup Added global using aliases for Vulkan types to Usings.cs. Implemented mapping functions in VKFormats.cs for various graphics enums to Vulkan types. Updated VKGraphicsPipeline.cs to use these mappings, improved depth/stencil format handling, and fully configured Vulkan pipeline state structures, including support for independent blend states. --- sources/Zenith.NET.Vulkan/Usings.cs | 6 +- sources/Zenith.NET.Vulkan/VKFormats.cs | 173 +++++++++++++++++- .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 112 +++++++++++- 3 files changed, 285 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 8a0dea00..10c7b898 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -1,5 +1,9 @@ -global using VkBorderColor = Silk.NET.Vulkan.BorderColor; +global using VkBlendFactor = Silk.NET.Vulkan.BlendFactor; +global using VkBlendOp = Silk.NET.Vulkan.BlendOp; +global using VkBorderColor = Silk.NET.Vulkan.BorderColor; global using VkBuffer = Silk.NET.Vulkan.Buffer; global using VkCompareOp = Silk.NET.Vulkan.CompareOp; +global using VkFrontFace = Silk.NET.Vulkan.FrontFace; global using VkPipeline = Silk.NET.Vulkan.Pipeline; global using VkPrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology; +global using VkStencilOp = Silk.NET.Vulkan.StencilOp; diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 04d658cb..10e69427 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -309,11 +309,180 @@ public static ImageUsageFlags Vulkan(TextureUsages textureUsages) public static Format Vulkan(ElementFormat elementFormat) { - throw new NotImplementedException(); + return elementFormat switch + { + ElementFormat.UByte1 => Format.R8Uint, + ElementFormat.UByte2 => Format.R8G8Uint, + ElementFormat.UByte4 => Format.R8G8B8A8Uint, + + ElementFormat.Byte1 => Format.R8Sint, + ElementFormat.Byte2 => Format.R8G8Sint, + ElementFormat.Byte4 => Format.R8G8B8A8Sint, + + ElementFormat.UByte1UNorm => Format.R8Unorm, + ElementFormat.UByte2UNorm => Format.R8G8Unorm, + ElementFormat.UByte4UNorm => Format.R8G8B8A8Unorm, + + ElementFormat.Byte1SNorm => Format.R8SNorm, + ElementFormat.Byte2SNorm => Format.R8G8SNorm, + ElementFormat.Byte4SNorm => Format.R8G8B8A8SNorm, + + ElementFormat.UShort1 => Format.R16Uint, + ElementFormat.UShort2 => Format.R16G16Uint, + ElementFormat.UShort4 => Format.R16G16B16A16Uint, + + ElementFormat.Short1 => Format.R16Sint, + ElementFormat.Short2 => Format.R16G16Sint, + ElementFormat.Short4 => Format.R16G16B16A16Sint, + + ElementFormat.UShort1UNorm => Format.R16Unorm, + ElementFormat.UShort2UNorm => Format.R16G16Unorm, + ElementFormat.UShort4UNorm => Format.R16G16B16A16Unorm, + + ElementFormat.Short1SNorm => Format.R16SNorm, + ElementFormat.Short2SNorm => Format.R16G16SNorm, + ElementFormat.Short4SNorm => Format.R16G16B16A16SNorm, + + ElementFormat.Half1 => Format.R16Sfloat, + ElementFormat.Half2 => Format.R16G16Sfloat, + ElementFormat.Half4 => Format.R16G16B16A16Sfloat, + + ElementFormat.Float1 => Format.R32Sfloat, + ElementFormat.Float2 => Format.R32G32Sfloat, + ElementFormat.Float3 => Format.R32G32B32Sfloat, + ElementFormat.Float4 => Format.R32G32B32A32Sfloat, + + ElementFormat.UInt1 => Format.R32Uint, + ElementFormat.UInt2 => Format.R32G32Uint, + ElementFormat.UInt3 => Format.R32G32B32Uint, + ElementFormat.UInt4 => Format.R32G32B32A32Uint, + + ElementFormat.Int1 => Format.R32Sint, + ElementFormat.Int2 => Format.R32G32Sint, + ElementFormat.Int3 => Format.R32G32B32Sint, + ElementFormat.Int4 => Format.R32G32B32A32Sint, + + _ => default + }; } public static VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) { - throw new NotImplementedException(); + return primitiveTopology switch + { + PrimitiveTopology.PointList => VkPrimitiveTopology.PointList, + PrimitiveTopology.LineList => VkPrimitiveTopology.LineList, + PrimitiveTopology.LineStrip => VkPrimitiveTopology.LineStrip, + PrimitiveTopology.TriangleList => VkPrimitiveTopology.TriangleList, + PrimitiveTopology.TriangleStrip => VkPrimitiveTopology.TriangleStrip, + _ => default + }; + } + + public static PolygonMode Vulkan(FillMode fillMode) + { + return fillMode switch + { + FillMode.Solid => PolygonMode.Fill, + FillMode.Wireframe => PolygonMode.Line, + _ => default + }; + } + + public static CullModeFlags Vulkan(CullMode cullMode) + { + return cullMode switch + { + CullMode.None => CullModeFlags.None, + CullMode.Front => CullModeFlags.FrontBit, + CullMode.Back => CullModeFlags.BackBit, + _ => default + }; + } + + public static VkFrontFace Vulkan(FrontFace frontFace) + { + return frontFace switch + { + FrontFace.CounterClockwise => VkFrontFace.CounterClockwise, + FrontFace.Clockwise => VkFrontFace.Clockwise, + _ => default + }; + } + + public static VkBlendFactor Vulkan(BlendFactor blendFactor) + { + return blendFactor switch + { + BlendFactor.Zero => VkBlendFactor.Zero, + BlendFactor.One => VkBlendFactor.One, + BlendFactor.SrcColor => VkBlendFactor.SrcColor, + BlendFactor.OneMinusSrcColor => VkBlendFactor.OneMinusSrcColor, + BlendFactor.DstColor => VkBlendFactor.DstColor, + BlendFactor.OneMinusDstColor => VkBlendFactor.OneMinusDstColor, + BlendFactor.SrcAlpha => VkBlendFactor.SrcAlpha, + BlendFactor.OneMinusSrcAlpha => VkBlendFactor.OneMinusSrcAlpha, + BlendFactor.DstAlpha => VkBlendFactor.DstAlpha, + BlendFactor.OneMinusDstAlpha => VkBlendFactor.OneMinusDstAlpha, + BlendFactor.Constant => VkBlendFactor.ConstantColor, + BlendFactor.OneMinusConstant => VkBlendFactor.OneMinusConstantColor, + _ => default + }; + } + + public static VkBlendOp Vulkan(BlendOp blendOp) + { + return blendOp switch + { + BlendOp.Add => VkBlendOp.Add, + BlendOp.Subtract => VkBlendOp.Subtract, + BlendOp.ReverseSubtract => VkBlendOp.ReverseSubtract, + BlendOp.Min => VkBlendOp.Min, + BlendOp.Max => VkBlendOp.Max, + _ => default + }; + } + + public static ColorComponentFlags Vulkan(ColorWrites colorWrites) + { + ColorComponentFlags result = default; + + if (colorWrites.HasFlag(ColorWrites.Red)) + { + result |= ColorComponentFlags.RBit; + } + + if (colorWrites.HasFlag(ColorWrites.Green)) + { + result |= ColorComponentFlags.GBit; + } + + if (colorWrites.HasFlag(ColorWrites.Blue)) + { + result |= ColorComponentFlags.BBit; + } + + if (colorWrites.HasFlag(ColorWrites.Alpha)) + { + result |= ColorComponentFlags.ABit; + } + + return result; + } + + public static VkStencilOp Vulkan(StencilOp stencilOp) + { + return stencilOp switch + { + StencilOp.Keep => VkStencilOp.Keep, + StencilOp.Zero => VkStencilOp.Zero, + StencilOp.Replace => VkStencilOp.Replace, + StencilOp.IncrementAndClamp => VkStencilOp.IncrementAndClamp, + StencilOp.DecrementAndClamp => VkStencilOp.DecrementAndClamp, + StencilOp.Invert => VkStencilOp.Invert, + StencilOp.IncrementAndWrap => VkStencilOp.IncrementAndWrap, + StencilOp.DecrementAndWrap => VkStencilOp.DecrementAndWrap, + _ => default + }; } } diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 84eef608..19116be8 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -84,16 +84,122 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) { SType = StructureType.PipelineRenderingCreateInfo, ColorAttachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length, - PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item).Format)]), - DepthAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format, - StencilAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format + PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item).Format)]) }; + if (desc.AttachmentFormats.DepthStencilFormat.HasValue) + { + PixelFormat depthStencilFormat = desc.AttachmentFormats.DepthStencilFormat.Value; + + rendering.DepthAttachmentFormat = ZenithHelper.HasDepth(depthStencilFormat) ? VKFormats.Vulkan(depthStencilFormat).Format : Format.Undefined; + rendering.StencilAttachmentFormat = ZenithHelper.HasStencil(depthStencilFormat) ? VKFormats.Vulkan(depthStencilFormat).Format : Format.Undefined; + } + createInfo.PNext = &rendering; } // RenderState { + ColorAttachmentBlendState[] states = + [ + desc.RenderState.Blend.ColorAttachment0, + desc.RenderState.Blend.ColorAttachment1, + desc.RenderState.Blend.ColorAttachment2, + desc.RenderState.Blend.ColorAttachment3, + desc.RenderState.Blend.ColorAttachment4, + desc.RenderState.Blend.ColorAttachment5, + desc.RenderState.Blend.ColorAttachment6, + desc.RenderState.Blend.ColorAttachment7 + ]; + + uint attachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length; + PipelineColorBlendAttachmentState* attachments = (PipelineColorBlendAttachmentState*)ZenithMarshal.Allocate(scope, attachmentCount); + for (uint i = 0; i < attachmentCount; i++) + { + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[0]; + + attachments[i] = new() + { + BlendEnable = state.IsBlendingEnabled, + SrcColorBlendFactor = VKFormats.Vulkan(state.SrcRgbFactor), + DstColorBlendFactor = VKFormats.Vulkan(state.DstRgbFactor), + ColorBlendOp = VKFormats.Vulkan(state.RgbOp), + SrcAlphaBlendFactor = VKFormats.Vulkan(state.SrcAlphaFactor), + DstAlphaBlendFactor = VKFormats.Vulkan(state.DstAlphaFactor), + AlphaBlendOp = VKFormats.Vulkan(state.AlphaOp), + ColorWriteMask = VKFormats.Vulkan(state.ColorWrites) + }; + } + + PipelineRasterizationStateCreateInfo rasterizationState = new() + { + SType = StructureType.PipelineRasterizationStateCreateInfo, + DepthClampEnable = !desc.RenderState.Rasterizer.IsDepthClipEnabled, + PolygonMode = VKFormats.Vulkan(desc.RenderState.Rasterizer.FillMode), + CullMode = VKFormats.Vulkan(desc.RenderState.Rasterizer.CullMode), + FrontFace = VKFormats.Vulkan(desc.RenderState.Rasterizer.FrontFace), + DepthBiasEnable = desc.RenderState.Rasterizer.DepthBias is not 0 || desc.RenderState.Rasterizer.DepthBiasSlopeScale is not 0.0f, + DepthBiasConstantFactor = desc.RenderState.Rasterizer.DepthBias, + DepthBiasClamp = desc.RenderState.Rasterizer.DepthBiasClamp, + DepthBiasSlopeFactor = desc.RenderState.Rasterizer.DepthBiasSlopeScale, + LineWidth = 1.0f + }; + + PipelineDepthStencilStateCreateInfo depthStencilState = new() + { + SType = StructureType.PipelineDepthStencilStateCreateInfo, + DepthTestEnable = desc.RenderState.DepthStencil.IsDepthEnabled, + DepthWriteEnable = desc.RenderState.DepthStencil.IsDepthWriteEnabled, + DepthCompareOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.DepthCompareOp), + StencilTestEnable = desc.RenderState.DepthStencil.IsStencilEnabled, + Front = new() + { + FailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.FailOp), + PassOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.PassOp), + DepthFailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.DepthFailOp), + CompareOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.CompareOp), + CompareMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask + }, + Back = new() + { + FailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.FailOp), + PassOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.PassOp), + DepthFailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.DepthFailOp), + CompareOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.CompareOp), + CompareMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask + }, + MinDepthBounds = 0.0f, + MaxDepthBounds = 1.0f + }; + + PipelineColorBlendStateCreateInfo colorBlendState = new() + { + SType = StructureType.PipelineColorBlendStateCreateInfo, + AttachmentCount = attachmentCount, + PAttachments = attachments + }; + + PipelineViewportStateCreateInfo viewportState = new() + { + SType = StructureType.PipelineViewportStateCreateInfo, + ViewportCount = attachmentCount, + ScissorCount = attachmentCount + }; + + PipelineMultisampleStateCreateInfo multisampleState = new() + { + SType = StructureType.PipelineMultisampleStateCreateInfo, + RasterizationSamples = VKFormats.Vulkan(desc.AttachmentFormats.SampleCount), + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled + }; + + createInfo.PRasterizationState = &rasterizationState; + createInfo.PDepthStencilState = &depthStencilState; + createInfo.PColorBlendState = &colorBlendState; + createInfo.PViewportState = &viewportState; + createInfo.PMultisampleState = &multisampleState; } PipelineDynamicStateCreateInfo dynamicState = new() From 040878cee606b561fd648f3f81891efcc5e30209 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Fri, 3 Jul 2026 23:20:25 +0800 Subject: [PATCH 338/466] Refactor mesh shading pipeline state setup Refactor VKMeshShadingPipeline to conditionally set depth/stencil formats and explicitly initialize all major Vulkan pipeline state structures. This improves flexibility, supports independent blending, and aligns mesh shading pipeline configuration with the graphics pipeline. --- .../Zenith.NET.Vulkan/VKGraphicsPipeline.cs | 1 - .../VKMeshShadingPipeline.cs | 111 +++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 19116be8..93fce2b9 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -170,7 +170,6 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) CompareMask = desc.RenderState.DepthStencil.StencilReadMask, WriteMask = desc.RenderState.DepthStencil.StencilWriteMask }, - MinDepthBounds = 0.0f, MaxDepthBounds = 1.0f }; diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 89c694e4..8fcfe963 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -49,16 +49,121 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc { SType = StructureType.PipelineRenderingCreateInfo, ColorAttachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length, - PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item).Format)]), - DepthAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format, - StencilAttachmentFormat = VKFormats.Vulkan(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown).Format + PColorAttachmentFormats = (Format*)ZenithMarshal.AllocateAndFill(scope, [.. desc.AttachmentFormats.ColorFormats.Select(static item => VKFormats.Vulkan(item).Format)]) }; + if (desc.AttachmentFormats.DepthStencilFormat.HasValue) + { + PixelFormat depthStencilFormat = desc.AttachmentFormats.DepthStencilFormat.Value; + + rendering.DepthAttachmentFormat = ZenithHelper.HasDepth(depthStencilFormat) ? VKFormats.Vulkan(depthStencilFormat).Format : Format.Undefined; + rendering.StencilAttachmentFormat = ZenithHelper.HasStencil(depthStencilFormat) ? VKFormats.Vulkan(depthStencilFormat).Format : Format.Undefined; + } + createInfo.PNext = &rendering; } // RenderState { + ColorAttachmentBlendState[] states = + [ + desc.RenderState.Blend.ColorAttachment0, + desc.RenderState.Blend.ColorAttachment1, + desc.RenderState.Blend.ColorAttachment2, + desc.RenderState.Blend.ColorAttachment3, + desc.RenderState.Blend.ColorAttachment4, + desc.RenderState.Blend.ColorAttachment5, + desc.RenderState.Blend.ColorAttachment6, + desc.RenderState.Blend.ColorAttachment7 + ]; + + uint attachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length; + PipelineColorBlendAttachmentState* attachments = (PipelineColorBlendAttachmentState*)ZenithMarshal.Allocate(scope, attachmentCount); + for (uint i = 0; i < attachmentCount; i++) + { + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[0]; + + attachments[i] = new() + { + BlendEnable = state.IsBlendingEnabled, + SrcColorBlendFactor = VKFormats.Vulkan(state.SrcRgbFactor), + DstColorBlendFactor = VKFormats.Vulkan(state.DstRgbFactor), + ColorBlendOp = VKFormats.Vulkan(state.RgbOp), + SrcAlphaBlendFactor = VKFormats.Vulkan(state.SrcAlphaFactor), + DstAlphaBlendFactor = VKFormats.Vulkan(state.DstAlphaFactor), + AlphaBlendOp = VKFormats.Vulkan(state.AlphaOp), + ColorWriteMask = VKFormats.Vulkan(state.ColorWrites) + }; + } + + PipelineRasterizationStateCreateInfo rasterizationState = new() + { + SType = StructureType.PipelineRasterizationStateCreateInfo, + DepthClampEnable = !desc.RenderState.Rasterizer.IsDepthClipEnabled, + PolygonMode = VKFormats.Vulkan(desc.RenderState.Rasterizer.FillMode), + CullMode = VKFormats.Vulkan(desc.RenderState.Rasterizer.CullMode), + FrontFace = VKFormats.Vulkan(desc.RenderState.Rasterizer.FrontFace), + DepthBiasEnable = desc.RenderState.Rasterizer.DepthBias is not 0 || desc.RenderState.Rasterizer.DepthBiasSlopeScale is not 0.0f, + DepthBiasConstantFactor = desc.RenderState.Rasterizer.DepthBias, + DepthBiasClamp = desc.RenderState.Rasterizer.DepthBiasClamp, + DepthBiasSlopeFactor = desc.RenderState.Rasterizer.DepthBiasSlopeScale, + LineWidth = 1.0f + }; + + PipelineDepthStencilStateCreateInfo depthStencilState = new() + { + SType = StructureType.PipelineDepthStencilStateCreateInfo, + DepthTestEnable = desc.RenderState.DepthStencil.IsDepthEnabled, + DepthWriteEnable = desc.RenderState.DepthStencil.IsDepthWriteEnabled, + DepthCompareOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.DepthCompareOp), + StencilTestEnable = desc.RenderState.DepthStencil.IsStencilEnabled, + Front = new() + { + FailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.FailOp), + PassOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.PassOp), + DepthFailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.DepthFailOp), + CompareOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.FrontFace.CompareOp), + CompareMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask + }, + Back = new() + { + FailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.FailOp), + PassOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.PassOp), + DepthFailOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.DepthFailOp), + CompareOp = VKFormats.Vulkan(desc.RenderState.DepthStencil.BackFace.CompareOp), + CompareMask = desc.RenderState.DepthStencil.StencilReadMask, + WriteMask = desc.RenderState.DepthStencil.StencilWriteMask + }, + MaxDepthBounds = 1.0f + }; + + PipelineColorBlendStateCreateInfo colorBlendState = new() + { + SType = StructureType.PipelineColorBlendStateCreateInfo, + AttachmentCount = attachmentCount, + PAttachments = attachments + }; + + PipelineViewportStateCreateInfo viewportState = new() + { + SType = StructureType.PipelineViewportStateCreateInfo, + ViewportCount = attachmentCount, + ScissorCount = attachmentCount + }; + + PipelineMultisampleStateCreateInfo multisampleState = new() + { + SType = StructureType.PipelineMultisampleStateCreateInfo, + RasterizationSamples = VKFormats.Vulkan(desc.AttachmentFormats.SampleCount), + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled + }; + + createInfo.PRasterizationState = &rasterizationState; + createInfo.PDepthStencilState = &depthStencilState; + createInfo.PColorBlendState = &colorBlendState; + createInfo.PViewportState = &viewportState; + createInfo.PMultisampleState = &multisampleState; } PipelineDynamicStateCreateInfo dynamicState = new() From 2fd83b54b5f3bb84ab47fad485c0c7744c72532c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 00:00:51 +0800 Subject: [PATCH 339/466] Add VKCommandBuffer class with Vulkan command buffer setup Introduced VKCommandBuffer in Zenith.NET.Vulkan, inheriting from CommandBuffer and initializing Vulkan command pools and buffers using Silk.NET. Stubbed all required method overrides. Added global using alias for VkCommandBuffer in Usings.cs. --- sources/Zenith.NET.Vulkan/Usings.cs | 1 + sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 247 +++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 sources/Zenith.NET.Vulkan/VKCommandBuffer.cs diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 10c7b898..65b3d4ca 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -2,6 +2,7 @@ global using VkBlendOp = Silk.NET.Vulkan.BlendOp; global using VkBorderColor = Silk.NET.Vulkan.BorderColor; global using VkBuffer = Silk.NET.Vulkan.Buffer; +global using VkCommandBuffer = Silk.NET.Vulkan.CommandBuffer; global using VkCompareOp = Silk.NET.Vulkan.CompareOp; global using VkFrontFace = Silk.NET.Vulkan.FrontFace; global using VkPipeline = Silk.NET.Vulkan.Pipeline; diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs new file mode 100644 index 00000000..e0f57485 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -0,0 +1,247 @@ +using System.Numerics; +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKCommandBuffer : CommandBuffer +{ + public CommandPool CommandPool; + + public VkCommandBuffer CommandBuffer; + + public VKCommandBuffer(VKGraphicsContext context, VKCommandQueue queue) : base(context, queue) + { + CommandPoolCreateInfo createInfo = new() + { + SType = StructureType.CommandPoolCreateInfo, + Flags = CommandPoolCreateFlags.ResetCommandBufferBit, + QueueFamilyIndex = queue.QueueFamilyIndex + }; + + context.Vk.CreateCommandPool(context.Device, &createInfo, default, out CommandPool).Success(); + + CommandBufferAllocateInfo allocateInfo = new() + { + SType = StructureType.CommandBufferAllocateInfo, + CommandPool = CommandPool, + CommandBufferCount = 1 + }; + + context.Vk.AllocateCommandBuffers(context.Device, &allocateInfo, out CommandBuffer).Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void BarrierImpl(BarrierStages before, BarrierStages after) + { + throw new NotImplementedException(); + } + + protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) + { + throw new NotImplementedException(); + } + + protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) + { + throw new NotImplementedException(); + } + + protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + throw new NotImplementedException(); + } + + protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) + { + throw new NotImplementedException(); + } + + protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) + { + throw new NotImplementedException(); + } + + protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + throw new NotImplementedException(); + } + + protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); + } + + protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) + { + throw new NotImplementedException(); + } + + protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) + { + throw new NotImplementedException(); + } + + protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) + { + throw new NotImplementedException(); + } + + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + throw new NotImplementedException(); + } + + protected override void EndRenderPassImpl() + { + throw new NotImplementedException(); + } + + protected override void SetPipelineImpl(GraphicsPipeline pipeline) + { + throw new NotImplementedException(); + } + + protected override void SetPipelineImpl(ComputePipeline pipeline) + { + throw new NotImplementedException(); + } + + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + { + throw new NotImplementedException(); + } + + protected override void SetViewportsImpl(ReadOnlySpan viewports) + { + throw new NotImplementedException(); + } + + protected override void SetScissorsImpl(ReadOnlySpan scissors) + { + throw new NotImplementedException(); + } + + protected override void SetBlendConstantImpl(Vector4 blendConstant) + { + throw new NotImplementedException(); + } + + protected override void SetStencilReferenceImpl(uint stencilReference) + { + throw new NotImplementedException(); + } + + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) + { + throw new NotImplementedException(); + } + + protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) + { + throw new NotImplementedException(); + } + + protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) + { + throw new NotImplementedException(); + } + + protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) + { + throw new NotImplementedException(); + } + + protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + throw new NotImplementedException(); + } + + protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) + { + throw new NotImplementedException(); + } + + protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) + { + throw new NotImplementedException(); + } + + protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) + { + throw new NotImplementedException(); + } + + protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) + { + throw new NotImplementedException(); + } + + protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) + { + throw new NotImplementedException(); + } + + protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) + { + throw new NotImplementedException(); + } + + protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) + { + throw new NotImplementedException(); + } + + protected override void EndQueryImpl(QueryHeap queryHeap, uint index) + { + throw new NotImplementedException(); + } + + protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) + { + throw new NotImplementedException(); + } + + protected override void BeginDebugEventImpl(string label) + { + throw new NotImplementedException(); + } + + protected override void EndDebugEventImpl() + { + throw new NotImplementedException(); + } + + protected override void InsertDebugMarkerImpl(string label) + { + throw new NotImplementedException(); + } + + protected override void BeginImpl() + { + throw new NotImplementedException(); + } + + protected override void EndImpl() + { + throw new NotImplementedException(); + } + + protected override void ResetImpl() + { + throw new NotImplementedException(); + } + + protected override void SetResourceName(string name) + { + throw new NotImplementedException(); + } + + protected override void Destroy() + { + base.Destroy(); + } +} From 39265804fdddb55616b08065e6b2044042bfa6e7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 00:03:34 +0800 Subject: [PATCH 340/466] Implement VKCommandBuffer Vulkan methods and debug utils Implemented previously missing VKCommandBuffer methods for debug events, markers, command buffer lifecycle, and resource naming using Vulkan API calls. Exposed Context as VKGraphicsContext and ensured command pool destruction in Destroy. GetNativeObject remains a stub. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 50 +++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index e0f57485..20a74ca0 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -30,6 +30,8 @@ public VKCommandBuffer(VKGraphicsContext context, VKCommandQueue queue) : base(c context.Vk.AllocateCommandBuffers(context.Device, &allocateInfo, out CommandBuffer).Success(); } + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + public override nint GetNativeObject(NativeObjectType type) { return 0; @@ -207,41 +209,75 @@ protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) protected override void BeginDebugEventImpl(string label) { - throw new NotImplementedException(); + using ZenithMarshal.Scope scope = new(); + + DebugUtilsLabelEXT labelInfo = new() + { + SType = StructureType.DebugUtilsLabelExt, + PLabelName = (byte*)ZenithMarshal.StringToPointer(scope, label, StringEncoding.UTF8) + }; + + Context.DebugUtils?.CmdBeginDebugUtilsLabel(CommandBuffer, &labelInfo); } protected override void EndDebugEventImpl() { - throw new NotImplementedException(); + Context.DebugUtils?.CmdEndDebugUtilsLabel(CommandBuffer); } protected override void InsertDebugMarkerImpl(string label) { - throw new NotImplementedException(); + using ZenithMarshal.Scope scope = new(); + + DebugUtilsLabelEXT labelInfo = new() + { + SType = StructureType.DebugUtilsLabelExt, + PLabelName = (byte*)ZenithMarshal.StringToPointer(scope, label, StringEncoding.UTF8) + }; + + Context.DebugUtils?.CmdInsertDebugUtilsLabel(CommandBuffer, &labelInfo); } protected override void BeginImpl() { - throw new NotImplementedException(); + CommandBufferBeginInfo beginInfo = new() + { + SType = StructureType.CommandBufferBeginInfo, + Flags = CommandBufferUsageFlags.OneTimeSubmitBit + }; + + Context.Vk.BeginCommandBuffer(CommandBuffer, &beginInfo).Success(); } protected override void EndImpl() { - throw new NotImplementedException(); + Context.Vk.EndCommandBuffer(CommandBuffer).Success(); } protected override void ResetImpl() { - throw new NotImplementedException(); + Context.Vk.ResetCommandBuffer(CommandBuffer, CommandBufferResetFlags.None).Success(); } protected override void SetResourceName(string name) { - throw new NotImplementedException(); + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.CommandBuffer, + ObjectHandle = (ulong)CommandBuffer.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } protected override void Destroy() { base.Destroy(); + + Context.Vk.DestroyCommandPool(Context.Device, CommandPool, default); } } From aef2761af777e06d85861def4c438e3d304ea617 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 00:29:59 +0800 Subject: [PATCH 341/466] Add Vulkan query heap support and integration Implemented VKQueryHeap to wrap Vulkan QueryPool, including creation, destruction, result retrieval, and debug naming. Updated VKGraphicsContext to instantiate VKQueryHeap. Implemented query operations in VKCommandBuffer. Added extension methods for Vulkan casting and a QueryType mapping in VKFormats. Introduced a global using for VkQueryType. --- sources/Zenith.NET.Vulkan/Extensions.cs | 8 +++ sources/Zenith.NET.Vulkan/Usings.cs | 1 + sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 12 +++- sources/Zenith.NET.Vulkan/VKFormats.cs | 13 ++++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 +- sources/Zenith.NET.Vulkan/VKQueryHeap.cs | 64 +++++++++++++++++++ 6 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKQueryHeap.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 38e89f52..4cec5731 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -64,6 +64,10 @@ internal VKBufferView Vulkan() extension(CommandBuffer commandBuffer) { + internal VKCommandBuffer Vulkan() + { + return (VKCommandBuffer)commandBuffer; + } } extension(CommandQueue commandQueue) @@ -108,6 +112,10 @@ internal VKMeshShadingPipeline Vulkan() extension(QueryHeap queryHeap) { + internal VKQueryHeap Vulkan() + { + return (VKQueryHeap)queryHeap; + } } extension(Sampler sampler) diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 65b3d4ca..6cb35400 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -7,4 +7,5 @@ global using VkFrontFace = Silk.NET.Vulkan.FrontFace; global using VkPipeline = Silk.NET.Vulkan.Pipeline; global using VkPrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology; +global using VkQueryType = Silk.NET.Vulkan.QueryType; global using VkStencilOp = Silk.NET.Vulkan.StencilOp; diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 20a74ca0..6251cbb7 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -194,17 +194,23 @@ protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, B protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { - throw new NotImplementedException(); + VKQueryHeap vkQueryHeap = queryHeap.Vulkan(); + + Context.Vk.CmdResetQueryPool(CommandBuffer, vkQueryHeap.QueryPool, index, 1); + Context.Vk.CmdBeginQuery(CommandBuffer, vkQueryHeap.QueryPool, index, queryHeap.Desc.Type is QueryType.Occlusion ? QueryControlFlags.PreciseBit : QueryControlFlags.None); } protected override void EndQueryImpl(QueryHeap queryHeap, uint index) { - throw new NotImplementedException(); + Context.Vk.CmdEndQuery(CommandBuffer, queryHeap.Vulkan().QueryPool, index); } protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) { - throw new NotImplementedException(); + VKQueryHeap vkQueryHeap = queryHeap.Vulkan(); + + Context.Vk.CmdResetQueryPool(CommandBuffer, vkQueryHeap.QueryPool, index, 1); + Context.Vk.CmdWriteTimestamp(CommandBuffer, PipelineStageFlags.BottomOfPipeBit, vkQueryHeap.QueryPool, index); } protected override void BeginDebugEventImpl(string label) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 10e69427..665b8553 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -379,6 +379,19 @@ public static VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) }; } + public static VkQueryType Vulkan(QueryType queryType) + { + return queryType switch + { + QueryType.Occlusion or + QueryType.BinaryOcclusion => VkQueryType.Occlusion, + + QueryType.Timestamp => VkQueryType.Timestamp, + + _ => default + }; + } + public static PolygonMode Vulkan(FillMode fillMode) { return fillMode switch diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index f4e770be..a3ec4a34 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -616,7 +616,7 @@ protected override MeshShadingPipeline CreateMeshShadingPipelineImpl(MeshShading protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) { - throw new NotImplementedException(); + return new VKQueryHeap(this, desc); } protected override void Destroy() diff --git a/sources/Zenith.NET.Vulkan/VKQueryHeap.cs b/sources/Zenith.NET.Vulkan/VKQueryHeap.cs new file mode 100644 index 00000000..4625e288 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKQueryHeap.cs @@ -0,0 +1,64 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKQueryHeap : QueryHeap +{ + public QueryPool QueryPool; + + public VKQueryHeap(VKGraphicsContext context, QueryHeapDesc desc) : base(context, desc) + { + QueryPoolCreateInfo createInfo = new() + { + SType = StructureType.QueryPoolCreateInfo, + QueryType = VKFormats.Vulkan(desc.Type), + QueryCount = desc.Count + }; + + context.Vk.CreateQueryPool(context.Device, &createInfo, default, out QueryPool).Success(); + + context.Vk.ResetQueryPool(context.Device, QueryPool, 0, desc.Count); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void GetResultsImpl(Span results, uint startIndex) + { + fixed (ulong* pResults = results) + { + Context.Vk.GetQueryPoolResults(Context.Device, + QueryPool, + startIndex, + (uint)results.Length, + (nuint)(sizeof(ulong) * results.Length), + pResults, + sizeof(ulong), + QueryResultFlags.Result64Bit).Success(); + } + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.QueryPool, + ObjectHandle = QueryPool.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyQueryPool(Context.Device, QueryPool, default); + } +} From efcf432f5630a3e390fda387b9488e43579bcdca Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 01:39:11 +0800 Subject: [PATCH 342/466] Implement timeline semaphore support in VKCommandQueue Refactored VKCommandQueue to use timeline semaphores for synchronization. Added global VkSemaphore alias, explicit semaphore creation and destruction, and implemented command submission and waiting methods using Vulkan's timeline semaphore and submission APIs. This improves resource management and enables robust queue signaling. --- sources/Zenith.NET.Vulkan/Usings.cs | 1 + sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 104 ++++++++++++++++++-- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 6cb35400..3681f2c5 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -8,4 +8,5 @@ global using VkPipeline = Silk.NET.Vulkan.Pipeline; global using VkPrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology; global using VkQueryType = Silk.NET.Vulkan.QueryType; +global using VkSemaphore = Silk.NET.Vulkan.Semaphore; global using VkStencilOp = Silk.NET.Vulkan.StencilOp; diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 2ac5974b..68741bfa 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -2,11 +2,25 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : CommandQueue(context, type) +internal unsafe class VKCommandQueue : CommandQueue { - public Queue Queue = queue; + public Queue Queue; - public uint QueueFamilyIndex = queueFamilyIndex; + public uint QueueFamilyIndex; + + public VkSemaphore Semaphore; + + public VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : base(context, type) + { + Queue = queue; + QueueFamilyIndex = queueFamilyIndex; + + SemaphoreCreateInfo createInfo = new() { SType = StructureType.SemaphoreCreateInfo }; + createInfo.AddNext(out SemaphoreTypeCreateInfo typeCreateInfo); + typeCreateInfo.SemaphoreType = SemaphoreType.Timeline; + + context.Vk.CreateSemaphore(context.Device, &createInfo, default, out Semaphore).Success(); + } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; @@ -17,32 +31,97 @@ public override nint GetNativeObject(NativeObjectType type) protected override ulong GetCompletedValue() { - throw new NotImplementedException(); + ulong value; + Context.Vk.GetSemaphoreCounterValue(Context.Device, Semaphore, &value).Success(); + + return value; } protected override CommandBuffer CreateCommandBuffer() { - throw new NotImplementedException(); + return new VKCommandBuffer(Context, this); } protected override void SignalImpl(ulong signalValue) { - throw new NotImplementedException(); + SemaphoreSubmitInfo signalSemaphoreInfo = new() + { + SType = StructureType.SemaphoreSubmitInfo, + Semaphore = Semaphore, + Value = signalValue, + StageMask = PipelineStageFlags2.AllCommandsBit + }; + + SubmitInfo2 submitInfo = new() + { + SType = StructureType.SubmitInfo2, + SignalSemaphoreInfoCount = 1, + PSignalSemaphoreInfos = &signalSemaphoreInfo + }; + + Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); } protected override void SubmitImpl(CommandBuffer commandBuffer) { - throw new NotImplementedException(); + CommandBufferSubmitInfo commandBufferInfo = new() + { + SType = StructureType.CommandBufferSubmitInfo, + CommandBuffer = commandBuffer.Vulkan().CommandBuffer + }; + + SubmitInfo2 submitInfo = new() + { + SType = StructureType.SubmitInfo2, + CommandBufferInfoCount = 1, + PCommandBufferInfos = &commandBufferInfo + }; + + Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); } protected override void WaitImpl(ulong waitValue) { - throw new NotImplementedException(); + fixed (VkSemaphore* pSemaphores = &Semaphore) + { + SemaphoreWaitInfo waitInfo = new() + { + SType = StructureType.SemaphoreWaitInfo, + SemaphoreCount = 1, + PSemaphores = pSemaphores, + PValues = &waitValue + }; + + Context.Vk.WaitSemaphores(Context.Device, &waitInfo, ulong.MaxValue).Success(); + } } protected override void WaitImpl(ReadOnlySpan submissions) { - throw new NotImplementedException(); + foreach (CommandSubmission submission in submissions) + { + if (submission.Queue is null) + { + continue; + } + + SemaphoreSubmitInfo waitSemaphoreInfo = new() + { + SType = StructureType.SemaphoreSubmitInfo, + Semaphore = submission.Queue.Vulkan().Semaphore, + Value = submission.Value, + StageMask = PipelineStageFlags2.AllCommandsBit + }; + + SubmitInfo2 submitInfo = new() + { + SType = StructureType.SubmitInfo2, + WaitSemaphoreInfoCount = 1, + PWaitSemaphoreInfos = &waitSemaphoreInfo + }; + + Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); + } } protected override void SetResourceName(string name) @@ -59,4 +138,11 @@ protected override void SetResourceName(string name) Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } + + protected override void Destroy() + { + base.Destroy(); + + Context.Vk.DestroySemaphore(Context.Device, Semaphore, default); + } } From 28fa535e17a965949f5bf084ee8c7577e6aee3ec Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 14:35:00 +0800 Subject: [PATCH 343/466] Implement Vulkan barrier and heap binding logic Implemented `BarrierImpl` in `VKCommandBuffer` to map `BarrierStages` to Vulkan pipeline stage and access flags, issuing `CmdPipelineBarrier2`. Added resource and sampler heap binding in command buffer begin logic, skipping for transfer queues. Introduced `VKFormats.Vulkan(BarrierStages)` to map custom barrier stages to Vulkan flags. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 44 +++++++++++++++++- sources/Zenith.NET.Vulkan/VKFormats.cs | 49 ++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 6251cbb7..5234fd8d 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -39,7 +39,26 @@ public override nint GetNativeObject(NativeObjectType type) protected override void BarrierImpl(BarrierStages before, BarrierStages after) { - throw new NotImplementedException(); + (PipelineStageFlags2 srcStage, AccessFlags2 srcAccess) = VKFormats.Vulkan(before); + (PipelineStageFlags2 dstStage, AccessFlags2 dstAccess) = VKFormats.Vulkan(after); + + MemoryBarrier2 memoryBarrier = new() + { + SType = StructureType.MemoryBarrier2, + SrcStageMask = srcStage, + SrcAccessMask = srcAccess, + DstStageMask = dstStage, + DstAccessMask = dstAccess + }; + + DependencyInfo dependencyInfo = new() + { + SType = StructureType.DependencyInfo, + MemoryBarrierCount = 1, + PMemoryBarriers = &memoryBarrier + }; + + Context.Vk.CmdPipelineBarrier2(CommandBuffer, &dependencyInfo); } protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) @@ -253,6 +272,29 @@ protected override void BeginImpl() }; Context.Vk.BeginCommandBuffer(CommandBuffer, &beginInfo).Success(); + + if (Queue.Type is CommandQueueType.Transfer) + { + return; + } + + BindHeapInfoEXT resourceBindInfo = new() + { + SType = StructureType.BindHeapInfoExt(), + HeapRange = Context.ResourceHeap.Range, + ReservedRangeSize = Context.ResourceHeap.ReservedBytes + }; + + Context.DescriptorHeap?.CmdBindResourceHeap(CommandBuffer, &resourceBindInfo); + + BindHeapInfoEXT samplerBindInfo = new() + { + SType = StructureType.BindHeapInfoExt(), + HeapRange = Context.SamplerHeap.Range, + ReservedRangeSize = Context.SamplerHeap.ReservedBytes + }; + + Context.DescriptorHeap?.CmdBindSamplerHeap(CommandBuffer, &samplerBindInfo); } protected override void EndImpl() diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 665b8553..ac080a28 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -498,4 +498,53 @@ public static VkStencilOp Vulkan(StencilOp stencilOp) _ => default }; } + + public static (PipelineStageFlags2 Stage, AccessFlags2 Access) Vulkan(BarrierStages barrierStages) + { + if (barrierStages is BarrierStages.None) + { + return (PipelineStageFlags2.None, AccessFlags2.None); + } + + PipelineStageFlags2 stage = default; + AccessFlags2 access = default; + + 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; + } + + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) + { + stage |= PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.EarlyFragmentTestsBit | PipelineStageFlags2.LateFragmentTestsBit | PipelineStageFlags2.ColorAttachmentOutputBit; + access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.ColorAttachmentReadBit | AccessFlags2.ColorAttachmentWriteBit | AccessFlags2.DepthStencilAttachmentReadBit | AccessFlags2.DepthStencilAttachmentWriteBit | AccessFlags2.AccelerationStructureReadBitKhr; + } + + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) + { + stage |= PipelineStageFlags2.ComputeShaderBit | PipelineStageFlags2.DrawIndirectBit; + access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.IndirectCommandReadBit | AccessFlags2.AccelerationStructureReadBitKhr; + } + + if (barrierStages.HasFlag(BarrierStages.Copy)) + { + stage |= PipelineStageFlags2.CopyBit; + access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; + } + + if (barrierStages.HasFlag(BarrierStages.Resolve)) + { + stage |= PipelineStageFlags2.ResolveBit; + access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; + } + + if (barrierStages.HasFlag(BarrierStages.All)) + { + stage = PipelineStageFlags2.AllCommandsBit; + access = AccessFlags2.MemoryReadBit | AccessFlags2.MemoryWriteBit; + } + + return (stage, access); + } } From c47246d5132ca40dd39e1b577b83e0e5a9868758 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 14:39:13 +0800 Subject: [PATCH 344/466] Implement Vulkan image layout transitions and mapping Implemented TransitionImpl in VKCommandBuffer to handle Vulkan image layout transitions using CmdPipelineBarrier2 and DependencyInfo. Added VKFormats.Vulkan(TextureLayout) to map TextureLayout enums to Vulkan pipeline stages, access flags, and image layouts. This enables correct synchronization and layout transitions for textures. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 36 +++++++++++++++++++- sources/Zenith.NET.Vulkan/VKFormats.cs | 20 +++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 5234fd8d..04104697 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -63,7 +63,41 @@ protected override void BarrierImpl(BarrierStages before, BarrierStages after) protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout srcLayout, TextureLayout dstLayout) { - throw new NotImplementedException(); + VKTexture vkTexture = texture.Vulkan(); + + (PipelineStageFlags2 srcStage, AccessFlags2 srcAccess, ImageLayout oldLayout) = VKFormats.Vulkan(srcLayout); + (PipelineStageFlags2 dstStage, AccessFlags2 dstAccess, ImageLayout newLayout) = VKFormats.Vulkan(dstLayout); + + ImageMemoryBarrier2 imageMemoryBarrier = new() + { + SType = StructureType.ImageMemoryBarrier2, + SrcStageMask = srcStage, + SrcAccessMask = srcAccess, + DstStageMask = dstStage, + DstAccessMask = dstAccess, + OldLayout = oldLayout, + NewLayout = newLayout, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = vkTexture.Image, + SubresourceRange = new() + { + AspectMask = VKFormats.Vulkan(vkTexture.Desc.Format).AspectFlags, + BaseMipLevel = subresource.MipLevel, + LevelCount = 1, + BaseArrayLayer = subresource.ArrayLayer, + LayerCount = 1 + } + }; + + DependencyInfo dependencyInfo = new() + { + SType = StructureType.DependencyInfo, + ImageMemoryBarrierCount = 1, + PImageMemoryBarriers = &imageMemoryBarrier + }; + + Context.Vk.CmdPipelineBarrier2(CommandBuffer, &dependencyInfo); } protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index ac080a28..95e2e77a 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -547,4 +547,24 @@ public static (PipelineStageFlags2 Stage, AccessFlags2 Access) Vulkan(BarrierSta return (stage, access); } + + public static (PipelineStageFlags2 Stage, AccessFlags2 Access, ImageLayout Layout) Vulkan(TextureLayout textureLayout) + { + return textureLayout switch + { + TextureLayout.Undefined => (PipelineStageFlags2.None, AccessFlags2.None, ImageLayout.Undefined), + TextureLayout.General => (PipelineStageFlags2.AllCommandsBit, AccessFlags2.MemoryReadBit | AccessFlags2.MemoryWriteBit, ImageLayout.General), + TextureLayout.Sampled => (PipelineStageFlags2.VertexShaderBit | PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit, AccessFlags2.ShaderReadBit, ImageLayout.ShaderReadOnlyOptimal), + TextureLayout.Storage => (PipelineStageFlags2.VertexShaderBit | PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit, AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit, ImageLayout.General), + TextureLayout.ColorAttachment => (PipelineStageFlags2.ColorAttachmentOutputBit, AccessFlags2.ColorAttachmentReadBit | AccessFlags2.ColorAttachmentWriteBit, ImageLayout.ColorAttachmentOptimal), + TextureLayout.DepthStencilAttachment => (PipelineStageFlags2.EarlyFragmentTestsBit | PipelineStageFlags2.LateFragmentTestsBit, AccessFlags2.DepthStencilAttachmentReadBit | AccessFlags2.DepthStencilAttachmentWriteBit, ImageLayout.DepthStencilAttachmentOptimal), + TextureLayout.DepthStencilReadOnly => (PipelineStageFlags2.EarlyFragmentTestsBit | PipelineStageFlags2.LateFragmentTestsBit, AccessFlags2.DepthStencilAttachmentReadBit, ImageLayout.DepthStencilReadOnlyOptimal), + TextureLayout.CopySrc => (PipelineStageFlags2.CopyBit, AccessFlags2.TransferReadBit, ImageLayout.TransferSrcOptimal), + TextureLayout.CopyDst => (PipelineStageFlags2.CopyBit, AccessFlags2.TransferWriteBit, ImageLayout.TransferDstOptimal), + TextureLayout.ResolveSrc => (PipelineStageFlags2.ResolveBit, AccessFlags2.TransferReadBit, ImageLayout.TransferSrcOptimal), + TextureLayout.ResolveDst => (PipelineStageFlags2.ResolveBit, AccessFlags2.TransferWriteBit, ImageLayout.TransferDstOptimal), + TextureLayout.Present => (PipelineStageFlags2.AllCommandsBit, AccessFlags2.None, ImageLayout.PresentSrcKhr), + _ => (PipelineStageFlags2.None, AccessFlags2.None, ImageLayout.Undefined) + }; + } } From a542b0716c74bb4f63e7008ed6d1b125e2b0264a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 14:39:56 +0800 Subject: [PATCH 345/466] Refactor default cases to use tuple defaults Updated the default cases in DXFormats and VKFormats to return (default, default, default) instead of explicit enum values. This change improves code generality and consistency by relying on the default values of the tuple element types. --- sources/Zenith.NET.DirectX12/DXFormats.cs | 2 +- sources/Zenith.NET.Vulkan/VKFormats.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 992397a3..30aef1b5 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -592,7 +592,7 @@ public static (BarrierSync Sync, BarrierAccess Access, BarrierLayout Layout) Dir TextureLayout.ResolveSrc => (BarrierSync.Resolve, BarrierAccess.ResolveSource, BarrierLayout.ResolveSource), TextureLayout.ResolveDst => (BarrierSync.Resolve, BarrierAccess.ResolveDest, BarrierLayout.ResolveDest), TextureLayout.Present => (BarrierSync.All, BarrierAccess.Common, BarrierLayout.Present), - _ => (BarrierSync.None, BarrierAccess.NoAccess, BarrierLayout.Undefined) + _ => (default, default, default) }; } diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 95e2e77a..ff93e33b 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -564,7 +564,7 @@ public static (PipelineStageFlags2 Stage, AccessFlags2 Access, ImageLayout Layou TextureLayout.ResolveSrc => (PipelineStageFlags2.ResolveBit, AccessFlags2.TransferReadBit, ImageLayout.TransferSrcOptimal), TextureLayout.ResolveDst => (PipelineStageFlags2.ResolveBit, AccessFlags2.TransferWriteBit, ImageLayout.TransferDstOptimal), TextureLayout.Present => (PipelineStageFlags2.AllCommandsBit, AccessFlags2.None, ImageLayout.PresentSrcKhr), - _ => (PipelineStageFlags2.None, AccessFlags2.None, ImageLayout.Undefined) + _ => (default, default, default) }; } } From 926752bf3b566276464e4261d2d30126bcd09e11 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 23:45:02 +0800 Subject: [PATCH 346/466] Implement Vulkan copy/resolve commands in VKCommandBuffer Added implementations for buffer and texture copy/resolve methods in VKCommandBuffer using Vulkan 1.3+ synchronization2 structures and commands. Methods now construct the appropriate Vulkan structs and invoke CmdCopyBuffer2, CmdCopyBufferToImage2, CmdCopyImage2, CmdCopyImageToBuffer2, and CmdResolveImage2, replacing NotImplementedException stubs. Handles resource conversions, subresource selection, and layout transitions. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 206 ++++++++++++++++++- 1 file changed, 201 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 04104697..7f81f8cb 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -102,27 +102,223 @@ protected override void TransitionImpl(Texture texture, TextureSubresource subre protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { - throw new NotImplementedException(); + VKBuffer vkSrc = src.Vulkan(); + VKBuffer vkDst = dst.Vulkan(); + + BufferCopy2 region = new() + { + SType = StructureType.BufferCopy2, + SrcOffset = srcOffsetInBytes, + DstOffset = dstOffsetInBytes, + Size = sizeInBytes + }; + + CopyBufferInfo2 copyBufferInfo = new() + { + SType = StructureType.CopyBufferInfo2, + SrcBuffer = vkSrc.Buffer, + DstBuffer = vkDst.Buffer, + RegionCount = 1, + PRegions = ®ion + }; + + Context.Vk.CmdCopyBuffer2(CommandBuffer, ©BufferInfo); } protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - throw new NotImplementedException(); + VKBuffer vkSrc = src.Vulkan(); + VKTexture vkDst = dst.Vulkan(); + + (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkDst.Desc.Format, dstExtent.Width, dstExtent.Height); + + BufferImageCopy2 region = new() + { + SType = StructureType.BufferImageCopy2, + BufferOffset = srcOffsetInBytes, + BufferRowLength = srcRowStrideInBytes / ZenithHelper.SizeInBytes(vkDst.Desc.Format) * blockWidth, + BufferImageHeight = srcSliceStrideInBytes / srcRowStrideInBytes * blockHeight, + ImageSubresource = new() + { + AspectMask = VKFormats.Vulkan(vkDst.Desc.Format).AspectFlags, + MipLevel = dstSubresource.MipLevel, + BaseArrayLayer = dstSubresource.ArrayLayer, + LayerCount = 1 + }, + ImageOffset = new() + { + X = (int)dstOffset.X, + Y = (int)dstOffset.Y, + Z = (int)dstOffset.Z + }, + ImageExtent = new() + { + Width = dstExtent.Width, + Height = dstExtent.Height, + Depth = dstExtent.Depth + } + }; + + CopyBufferToImageInfo2 copyBufferToImageInfo = new() + { + SType = StructureType.CopyBufferToImageInfo2, + SrcBuffer = vkSrc.Buffer, + DstImage = vkDst.Image, + DstImageLayout = ImageLayout.TransferDstOptimal, + RegionCount = 1, + PRegions = ®ion + }; + + Context.Vk.CmdCopyBufferToImage2(CommandBuffer, ©BufferToImageInfo); } protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - throw new NotImplementedException(); + VKTexture vkSrc = src.Vulkan(); + VKTexture vkDst = dst.Vulkan(); + + ImageCopy2 region = new() + { + SType = StructureType.ImageCopy2, + SrcSubresource = new() + { + AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format).AspectFlags, + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, + LayerCount = 1 + }, + SrcOffset = new() + { + X = (int)srcOffset.X, + Y = (int)srcOffset.Y, + Z = (int)srcOffset.Z + }, + DstSubresource = new() + { + AspectMask = VKFormats.Vulkan(vkDst.Desc.Format).AspectFlags, + MipLevel = dstSubresource.MipLevel, + BaseArrayLayer = dstSubresource.ArrayLayer, + LayerCount = 1 + }, + DstOffset = new() + { + X = (int)dstOffset.X, + Y = (int)dstOffset.Y, + Z = (int)dstOffset.Z + }, + Extent = new() + { + Width = extent.Width, + Height = extent.Height, + Depth = extent.Depth + } + }; + + CopyImageInfo2 copyImageInfo = new() + { + SType = StructureType.CopyImageInfo2, + SrcImage = vkSrc.Image, + SrcImageLayout = ImageLayout.TransferSrcOptimal, + DstImage = vkDst.Image, + DstImageLayout = ImageLayout.TransferDstOptimal, + RegionCount = 1, + PRegions = ®ion + }; + + Context.Vk.CmdCopyImage2(CommandBuffer, ©ImageInfo); } protected override void CopyTextureToBufferImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Extent3D srcExtent, Buffer dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { - throw new NotImplementedException(); + VKTexture vkSrc = src.Vulkan(); + VKBuffer vkDst = dst.Vulkan(); + + (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkSrc.Desc.Format, srcExtent.Width, srcExtent.Height); + + BufferImageCopy2 region = new() + { + SType = StructureType.BufferImageCopy2, + BufferOffset = dstOffsetInBytes, + BufferRowLength = dstRowStrideInBytes / ZenithHelper.SizeInBytes(vkSrc.Desc.Format) * blockWidth, + BufferImageHeight = dstSliceStrideInBytes / dstRowStrideInBytes * blockHeight, + ImageSubresource = new() + { + AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format).AspectFlags, + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, + LayerCount = 1 + }, + ImageOffset = new() + { + X = (int)srcOffset.X, + Y = (int)srcOffset.Y, + Z = (int)srcOffset.Z + }, + ImageExtent = new() + { + Width = srcExtent.Width, + Height = srcExtent.Height, + Depth = srcExtent.Depth + } + }; + + CopyImageToBufferInfo2 copyImageToBufferInfo = new() + { + SType = StructureType.CopyImageToBufferInfo2, + SrcImage = vkSrc.Image, + SrcImageLayout = ImageLayout.TransferSrcOptimal, + DstBuffer = vkDst.Buffer, + RegionCount = 1, + PRegions = ®ion + }; + + Context.Vk.CmdCopyImageToBuffer2(CommandBuffer, ©ImageToBufferInfo); } protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { - throw new NotImplementedException(); + VKTexture vkSrc = src.Vulkan(); + VKTexture vkDst = dst.Vulkan(); + + ZenithHelper.MipDimensions(vkSrc.Desc.Width, vkSrc.Desc.Height, vkSrc.Desc.Depth, srcSubresource.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); + + ImageResolve2 region = new() + { + SType = StructureType.ImageResolve2, + SrcSubresource = new() + { + AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format).AspectFlags, + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, + LayerCount = 1 + }, + DstSubresource = new() + { + AspectMask = VKFormats.Vulkan(vkDst.Desc.Format).AspectFlags, + MipLevel = dstSubresource.MipLevel, + BaseArrayLayer = dstSubresource.ArrayLayer, + LayerCount = 1 + }, + Extent = new() + { + Width = mipWidth, + Height = mipHeight, + Depth = mipDepth + } + }; + + ResolveImageInfo2 resolveImageInfo = new() + { + SType = StructureType.ResolveImageInfo2, + SrcImage = vkSrc.Image, + SrcImageLayout = ImageLayout.TransferSrcOptimal, + DstImage = vkDst.Image, + DstImageLayout = ImageLayout.TransferDstOptimal, + RegionCount = 1, + PRegions = ®ion + }; + + Context.Vk.CmdResolveImage2(CommandBuffer, &resolveImageInfo); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) From 9fcbdbb81a660fd191b4b20cad5fae7dc19b4543 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 4 Jul 2026 23:58:59 +0800 Subject: [PATCH 347/466] Implement Vulkan command buffer operations via Silk.NET Replaced NotImplementedException stubs in VKCommandBuffer with actual Silk.NET Vulkan API calls for pipeline binding, viewport/scissor setup, buffer binding, and draw/dispatch commands. Added global using for VkViewport. Introduced a stub for IndexFormat to IndexType conversion in VKFormats. --- sources/Zenith.NET.Vulkan/Usings.cs | 1 + sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 72 +++++++++++++++----- sources/Zenith.NET.Vulkan/VKFormats.cs | 5 ++ 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index 3681f2c5..54005db9 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -10,3 +10,4 @@ global using VkQueryType = Silk.NET.Vulkan.QueryType; global using VkSemaphore = Silk.NET.Vulkan.Semaphore; global using VkStencilOp = Silk.NET.Vulkan.StencilOp; +global using VkViewport = Silk.NET.Vulkan.Viewport; diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 7f81f8cb..4a43316c 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -348,52 +348,88 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA protected override void EndRenderPassImpl() { - throw new NotImplementedException(); + Context.Vk.CmdEndRendering(CommandBuffer); } protected override void SetPipelineImpl(GraphicsPipeline pipeline) { - throw new NotImplementedException(); + Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); } protected override void SetPipelineImpl(ComputePipeline pipeline) { - throw new NotImplementedException(); + Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Compute, pipeline.Vulkan().Pipeline); } protected override void SetPipelineImpl(MeshShadingPipeline pipeline) { - throw new NotImplementedException(); + Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); } protected override void SetViewportsImpl(ReadOnlySpan viewports) { - throw new NotImplementedException(); + VkViewport* pViewports = stackalloc VkViewport[viewports.Length]; + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; + + pViewports[i] = new() + { + X = viewport.X, + Y = viewport.Y, + Width = viewport.Width, + Height = viewport.Height, + MinDepth = viewport.MinDepth, + MaxDepth = viewport.MaxDepth + }; + } + + Context.Vk.CmdSetViewport(CommandBuffer, 0, (uint)viewports.Length, pViewports); } protected override void SetScissorsImpl(ReadOnlySpan scissors) { - throw new NotImplementedException(); + Rect2D* pScissors = stackalloc Rect2D[scissors.Length]; + for (int i = 0; i < scissors.Length; i++) + { + Scissor scissor = scissors[i]; + + pScissors[i] = new() + { + Offset = new() + { + X = scissor.X, + Y = scissor.Y + }, + Extent = new() + { + Width = scissor.Width, + Height = scissor.Height + } + }; + } + + Context.Vk.CmdSetScissor(CommandBuffer, 0, (uint)scissors.Length, pScissors); } protected override void SetBlendConstantImpl(Vector4 blendConstant) { - throw new NotImplementedException(); + Context.Vk.CmdSetBlendConstants(CommandBuffer, &blendConstant.X); } protected override void SetStencilReferenceImpl(uint stencilReference) { - throw new NotImplementedException(); + Context.Vk.CmdSetStencilReference(CommandBuffer, StencilFaceFlags.FrontAndBack, stencilReference); } protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { - throw new NotImplementedException(); + Context.Vk.CmdBindVertexBuffers(CommandBuffer, slot, 1, [buffer.Vulkan().Buffer], [offsetInBytes]); } protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - throw new NotImplementedException(); + Context.Vk.CmdBindIndexBuffer(CommandBuffer, buffer.Vulkan().Buffer, offsetInBytes, VKFormats.Vulkan(indexFormat)); } protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) @@ -403,42 +439,42 @@ protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { - throw new NotImplementedException(); + Context.Vk.CmdDraw(CommandBuffer, vertexCount, instanceCount, firstVertex, firstInstance); } protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - throw new NotImplementedException(); + Context.Vk.CmdDrawIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes, drawCount, (uint)sizeof(IndirectDrawArgs)); } protected override void DrawIndexedImpl(GraphicsPipeline pipeline, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance) { - throw new NotImplementedException(); + Context.Vk.CmdDrawIndexed(CommandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); } protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - throw new NotImplementedException(); + Context.Vk.CmdDrawIndexedIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes, drawCount, (uint)sizeof(IndirectDrawIndexedArgs)); } protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - throw new NotImplementedException(); + Context.Vk.CmdDispatch(CommandBuffer, groupCountX, groupCountY, groupCountZ); } protected override void DispatchIndirectImpl(ComputePipeline pipeline, Buffer indirectBuffer, uint offsetInBytes) { - throw new NotImplementedException(); + Context.Vk.CmdDispatchIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes); } protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - throw new NotImplementedException(); + Context.MeshShader?.CmdDrawMeshTask(CommandBuffer, groupCountX, groupCountY, groupCountZ); } protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint dispatchCount) { - throw new NotImplementedException(); + Context.MeshShader?.CmdDrawMeshTasksIndirect(CommandBuffer, indirectBuffer.Vulkan().Buffer, offsetInBytes, dispatchCount, (uint)sizeof(IndirectDispatchMeshArgs)); } protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index ff93e33b..e4f87fc9 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -567,4 +567,9 @@ public static (PipelineStageFlags2 Stage, AccessFlags2 Access, ImageLayout Layou _ => (default, default, default) }; } + + internal static IndexType Vulkan(IndexFormat indexFormat) + { + throw new NotImplementedException(); + } } From be70bd3a5145d266cd1c08a064b2b0745fa3c45f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 5 Jul 2026 01:16:59 +0800 Subject: [PATCH 348/466] Implement SetConstantBufferImpl for Vulkan command buffer Replaced NotImplementedException in SetConstantBufferImpl with logic to compute the buffer device address, construct a PushDataInfoEXT structure, and push the data to the command buffer using the descriptor heap. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 4a43316c..5b479e02 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -434,7 +434,19 @@ protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buf protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) { - throw new NotImplementedException(); + ulong address = buffer.Vulkan().DeviceAddress + offsetInBytes; + + PushDataInfoEXT pushDataInfo = new() + { + SType = StructureType.PushDataInfoExt(), + Data = new() + { + Address = &address, + Size = sizeof(ulong) + } + }; + + Context.DescriptorHeap?.CmdPushData(CommandBuffer, &pushDataInfo); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) From 851197644fe0c86c28c6f373006cae7c8af07cc5 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 5 Jul 2026 01:33:02 +0800 Subject: [PATCH 349/466] Fix Vulkan viewport Y and height for correct origin Adjusted viewport Y by adding height and negated height value to match Vulkan's top-left origin coordinate system. This ensures proper rendering alignment with Vulkan's conventions. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 5b479e02..cd785ebd 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -376,9 +376,9 @@ protected override void SetViewportsImpl(ReadOnlySpan viewports) pViewports[i] = new() { X = viewport.X, - Y = viewport.Y, + Y = viewport.Y + viewport.Height, Width = viewport.Width, - Height = viewport.Height, + Height = -viewport.Height, MinDepth = viewport.MinDepth, MaxDepth = viewport.MaxDepth }; From 1b166a18574e5e26786ab4a3174b54193f824ee4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 5 Jul 2026 01:42:15 +0800 Subject: [PATCH 350/466] Update to use Vulkan CmdWriteTimestamp2 API Replaced CmdWriteTimestamp with CmdWriteTimestamp2 and updated to use PipelineStageFlags2.BottomOfPipeBit for improved Vulkan API compatibility and more precise pipeline stage specification. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index cd785ebd..993bdfd3 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -507,7 +507,7 @@ protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) VKQueryHeap vkQueryHeap = queryHeap.Vulkan(); Context.Vk.CmdResetQueryPool(CommandBuffer, vkQueryHeap.QueryPool, index, 1); - Context.Vk.CmdWriteTimestamp(CommandBuffer, PipelineStageFlags.BottomOfPipeBit, vkQueryHeap.QueryPool, index); + Context.Vk.CmdWriteTimestamp2(CommandBuffer, PipelineStageFlags2.BottomOfPipeBit, vkQueryHeap.QueryPool, index); } protected override void BeginDebugEventImpl(string label) From 3d207cee78626b77829c51af2aacbf2619bcca5c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 5 Jul 2026 16:42:05 +0800 Subject: [PATCH 351/466] Implement Vulkan render pass setup and attachment views BeginRenderPassImpl now sets up Vulkan rendering attachments and starts a render pass. Added VKFormats helpers for LoadOp and StoreOp mapping. VKTexture manages per-subresource attachment ImageViews and cleans them up on destroy to prevent leaks. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 105 ++++++++++++++++++- sources/Zenith.NET.Vulkan/VKFormats.cs | 21 ++++ sources/Zenith.NET.Vulkan/VKTexture.cs | 37 +++++++ 3 files changed, 162 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 993bdfd3..32991468 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -343,7 +343,110 @@ protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStru protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) { - throw new NotImplementedException(); + uint width = 0; + uint height = 0; + bool hasDepth = false; + bool hasStencil = false; + + RenderingAttachmentInfo* pColorAttachments = stackalloc RenderingAttachmentInfo[colorAttachments.Length]; + for (int i = 0; i < colorAttachments.Length; i++) + { + ColorAttachment attachment = colorAttachments[i]; + + VKTexture texture = attachment.Texture.Vulkan(); + + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, attachment.Subresource.MipLevel, out width, out height, out _); + + pColorAttachments[i] = new() + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = texture.GetAttachmentView(attachment.Subresource), + ImageLayout = ImageLayout.ColorAttachmentOptimal, + LoadOp = VKFormats.Vulkan(attachment.LoadOp), + StoreOp = VKFormats.Vulkan(attachment.StoreOp), + ClearValue = new() + { + Color = new() + { + Float32_0 = attachment.ClearColor.X, + Float32_1 = attachment.ClearColor.Y, + Float32_2 = attachment.ClearColor.Z, + Float32_3 = attachment.ClearColor.W + } + } + }; + } + + RenderingAttachmentInfo* pDepthAttachment = stackalloc RenderingAttachmentInfo[depthStencilAttachment.HasValue ? 1 : 0]; + RenderingAttachmentInfo* pStencilAttachment = stackalloc RenderingAttachmentInfo[depthStencilAttachment.HasValue ? 1 : 0]; + if (depthStencilAttachment.HasValue) + { + DepthStencilAttachment attachment = depthStencilAttachment.Value; + + VKTexture texture = attachment.Texture.Vulkan(); + + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, attachment.Subresource.MipLevel, out width, out height, out _); + + if (hasDepth = ZenithHelper.HasDepth(texture.Desc.Format)) + { + pDepthAttachment[0] = new() + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = texture.GetAttachmentView(attachment.Subresource), + ImageLayout = ImageLayout.DepthStencilAttachmentOptimal, + LoadOp = VKFormats.Vulkan(attachment.DepthLoadOp), + StoreOp = VKFormats.Vulkan(attachment.DepthStoreOp), + ClearValue = new() + { + DepthStencil = new() + { + Depth = attachment.ClearDepth, + Stencil = attachment.ClearStencil + } + } + }; + } + + if (hasStencil = ZenithHelper.HasDepth(texture.Desc.Format)) + { + pStencilAttachment[0] = new() + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = texture.GetAttachmentView(attachment.Subresource), + ImageLayout = ImageLayout.DepthStencilAttachmentOptimal, + LoadOp = VKFormats.Vulkan(attachment.StencilLoadOp), + StoreOp = VKFormats.Vulkan(attachment.StencilStoreOp), + ClearValue = new() + { + DepthStencil = new() + { + Depth = attachment.ClearDepth, + Stencil = attachment.ClearStencil + } + } + }; + } + } + + RenderingInfo renderingInfo = new() + { + SType = StructureType.RenderingInfo, + RenderArea = new() + { + Extent = new() + { + Width = width, + Height = height + } + }, + LayerCount = 1, + ColorAttachmentCount = (uint)colorAttachments.Length, + PColorAttachments = pColorAttachments, + PDepthAttachment = pDepthAttachment, + PStencilAttachment = pStencilAttachment + }; + + Context.Vk.CmdBeginRendering(CommandBuffer, &renderingInfo); } protected override void EndRenderPassImpl() diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index e4f87fc9..b4a1c7c3 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -572,4 +572,25 @@ internal static IndexType Vulkan(IndexFormat indexFormat) { throw new NotImplementedException(); } + + public static AttachmentLoadOp Vulkan(LoadOp loadOp) + { + return loadOp switch + { + LoadOp.Load => AttachmentLoadOp.Load, + LoadOp.Clear => AttachmentLoadOp.Clear, + LoadOp.DontCare => AttachmentLoadOp.DontCare, + _ => default + }; + } + + public static AttachmentStoreOp Vulkan(StoreOp storeOp) + { + return storeOp switch + { + StoreOp.Store => AttachmentStoreOp.Store, + StoreOp.DontCare => AttachmentStoreOp.DontCare, + _ => default + }; + } } diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index 34f138f5..35667283 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -4,6 +4,8 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKTexture : Texture { + private readonly Dictionary attachmentViews = []; + public Image Image; public VKAllocation Allocation; @@ -79,6 +81,34 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } + public ImageView GetAttachmentView(TextureSubresource subresource) + { + if (!attachmentViews.TryGetValue(subresource, out ImageView view)) + { + ImageViewCreateInfo createInfo = new() + { + SType = StructureType.ImageViewCreateInfo, + Image = Image, + ViewType = VKFormats.Vulkan(Desc.Type).ViewType, + Format = VKFormats.Vulkan(Desc.Format).Format, + SubresourceRange = new() + { + AspectMask = VKFormats.Vulkan(Desc.Format).AspectFlags, + BaseMipLevel = subresource.MipLevel, + LevelCount = 1, + BaseArrayLayer = subresource.ArrayLayer, + LayerCount = 1 + } + }; + + Context.Vk.CreateImageView(Context.Device, &createInfo, default, out view).Success(); + + attachmentViews[subresource] = view; + } + + return view; + } + protected override void SetResourceName(string name) { using ZenithMarshal.Scope scope = new(); @@ -96,6 +126,13 @@ protected override void SetResourceName(string name) protected override void Destroy() { + foreach (ImageView attachmentView in attachmentViews.Values) + { + Context.Vk.DestroyImageView(Context.Device, attachmentView, default); + } + + attachmentViews.Clear(); + View.Dispose(); Context.Vk.DestroyImage(Context.Device, Image, default); From 5db9e22fa2d4d0b6e4ccb6dc2f988ccaef4143ad Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 5 Jul 2026 16:42:56 +0800 Subject: [PATCH 352/466] Remove attachmentViews.Clear() from VKTexture cleanup attachmentViews is no longer cleared after destroying image views in VKTexture. This may affect resource management or reuse logic. --- sources/Zenith.NET.Vulkan/VKTexture.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index 35667283..c5cfc98e 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -130,7 +130,6 @@ protected override void Destroy() { Context.Vk.DestroyImageView(Context.Device, attachmentView, default); } - attachmentViews.Clear(); View.Dispose(); From fa38f5275bd392b22ba0355225222eaff8376bcd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 5 Jul 2026 16:56:23 +0800 Subject: [PATCH 353/466] Fix stencil attachment check to use HasStencil Correct the logic to use ZenithHelper.HasStencil instead of HasDepth when determining if a texture format has a stencil component. This ensures stencil attachments are properly detected and handled. --- sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 32991468..d22948fc 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -407,7 +407,7 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA }; } - if (hasStencil = ZenithHelper.HasDepth(texture.Desc.Format)) + if (hasStencil = ZenithHelper.HasStencil(texture.Desc.Format)) { pStencilAttachment[0] = new() { From e71b5d25cd4e1a563cff35d963a4ec8a79a0aba4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 10:41:34 +0800 Subject: [PATCH 354/466] Implement Vulkan acceleration structure classes Added VKBottomLevelAccelerationStructure and VKTopLevelAccelerationStructure with constructors and Update methods. Updated VKCommandBuffer to use these implementations and added extension methods for type casting. Most methods are placeholders pending further implementation. --- sources/Zenith.NET.Vulkan/Extensions.cs | 8 +++++ .../VKBottomLevelAccelerationStructure.cs | 27 +++++++++++++++++ sources/Zenith.NET.Vulkan/VKCommandBuffer.cs | 8 ++--- .../VKTopLevelAccelerationStructure.cs | 29 +++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs create mode 100644 sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 4cec5731..6f157ae4 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -44,6 +44,10 @@ internal void AddNext(out TNext next) extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { + internal VKBottomLevelAccelerationStructure Vulkan() + { + return (VKBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + } } extension(Buffer buffer) @@ -156,5 +160,9 @@ internal VKTextureView Vulkan() extension(TopLevelAccelerationStructure topLevelAccelerationStructure) { + internal VKTopLevelAccelerationStructure Vulkan() + { + return (VKTopLevelAccelerationStructure)topLevelAccelerationStructure; + } } } diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs new file mode 100644 index 00000000..46477cf5 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -0,0 +1,27 @@ +namespace Zenith.NET.Vulkan; + +internal class VKBottomLevelAccelerationStructure : BottomLevelAccelerationStructure +{ + public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) + { + } + + public void Update(VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) + { + } + + public override nint GetNativeObject(NativeObjectType type) + { + throw new NotImplementedException(); + } + + protected override void SetResourceName(string name) + { + throw new NotImplementedException(); + } + + protected override void Destroy() + { + throw new NotImplementedException(); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index d22948fc..bc33b06a 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -323,22 +323,22 @@ protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSu protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + return new VKBottomLevelAccelerationStructure(Context, this, desc); } protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) { - throw new NotImplementedException(); + return new VKTopLevelAccelerationStructure(Context, this, desc); } protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) { - throw new NotImplementedException(); + accelerationStructure.Vulkan().Update(this, newDesc); } protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { - throw new NotImplementedException(); + accelerationStructure.Vulkan().Update(this, newDesc); } protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs new file mode 100644 index 00000000..a1b11885 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -0,0 +1,29 @@ +namespace Zenith.NET.Vulkan; + +internal class VKTopLevelAccelerationStructure : TopLevelAccelerationStructure +{ + public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) + { + } + + public override ResourceHandle Handle { get; } + + public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) + { + } + + public override nint GetNativeObject(NativeObjectType type) + { + throw new NotImplementedException(); + } + + protected override void SetResourceName(string name) + { + throw new NotImplementedException(); + } + + protected override void Destroy() + { + throw new NotImplementedException(); + } +} From 6c95ef2291c1e8a1c066f84fb2f3111be12d33ac Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 10:52:23 +0800 Subject: [PATCH 355/466] Refactor VK acceleration structures for Vulkan resource mgmt Refactored VKBottomLevelAccelerationStructure and VKTopLevelAccelerationStructure to explicitly manage AccelerationStructureKHR handles and related VKBuffer resources (Transform, Storage, Scratch). Implemented SetResourceName and Destroy methods for debug naming and resource cleanup. Stubbed GetNativeObject to return 0. Switched to Silk.NET.Vulkan and marked classes as unsafe for pointer operations. --- .../VKBottomLevelAccelerationStructure.cs | 36 ++++++++++++++++--- .../VKTopLevelAccelerationStructure.cs | 36 ++++++++++++++++--- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index 46477cf5..5fd570cb 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -1,27 +1,53 @@ -namespace Zenith.NET.Vulkan; +using Silk.NET.Vulkan; -internal class VKBottomLevelAccelerationStructure : BottomLevelAccelerationStructure +namespace Zenith.NET.Vulkan; + +internal unsafe class VKBottomLevelAccelerationStructure : BottomLevelAccelerationStructure { + public AccelerationStructureKHR AccelerationStructure; + public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) { } + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public VKBuffer Transform { get; } + + public VKBuffer Storage { get; } + + public VKBuffer Scratch { get; } + public void Update(VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) { } public override nint GetNativeObject(NativeObjectType type) { - throw new NotImplementedException(); + return 0; } protected override void SetResourceName(string name) { - throw new NotImplementedException(); + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.AccelerationStructureKhr, + ObjectHandle = AccelerationStructure.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } protected override void Destroy() { - throw new NotImplementedException(); + Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, default); + + Scratch.Dispose(); + Storage.Dispose(); + Transform.Dispose(); } } diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index a1b11885..a83cb36b 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -1,11 +1,23 @@ -namespace Zenith.NET.Vulkan; +using Silk.NET.Vulkan; -internal class VKTopLevelAccelerationStructure : TopLevelAccelerationStructure +namespace Zenith.NET.Vulkan; + +internal unsafe class VKTopLevelAccelerationStructure : TopLevelAccelerationStructure { + public AccelerationStructureKHR AccelerationStructure; + public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { } + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public VKBuffer Transform { get; } + + public VKBuffer Storage { get; } + + public VKBuffer Scratch { get; } + public override ResourceHandle Handle { get; } public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) @@ -14,16 +26,30 @@ public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureD public override nint GetNativeObject(NativeObjectType type) { - throw new NotImplementedException(); + return 0; } protected override void SetResourceName(string name) { - throw new NotImplementedException(); + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.AccelerationStructureKhr, + ObjectHandle = AccelerationStructure.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } protected override void Destroy() { - throw new NotImplementedException(); + Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, default); + + Scratch.Dispose(); + Storage.Dispose(); + Transform.Dispose(); } } From c8fbdf1ca7b3b2176b551107d9bc31fbd81230c6 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 14:09:13 +0800 Subject: [PATCH 356/466] Update buffer creation for ray tracing support Buffer creation logic now considers Vulkan ray tracing support by passing Capabilities to VKBuffer.CreateInfo and VKFormats.Vulkan. Relevant buffer usages include the AccelerationStructureBuildInputReadOnlyBitKhr flag when ray tracing is enabled, ensuring correct usage flags for compatible devices. --- sources/Zenith.NET.Vulkan/VKBuffer.cs | 8 ++++---- sources/Zenith.NET.Vulkan/VKFormats.cs | 17 ++++++++++++++++- sources/Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 +- sources/Zenith.NET.Vulkan/VKHeap.cs | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 76d49745..55954bc0 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -12,7 +12,7 @@ internal unsafe class VKBuffer : Buffer public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) { - BufferCreateInfo createInfo = CreateInfo(desc, context.QueueFamilies); + BufferCreateInfo createInfo = CreateInfo(desc, context.Capabilities, context.QueueFamilies); context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); @@ -66,7 +66,7 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usage) : base(context, desc) { - BufferCreateInfo createInfo = CreateInfo(desc, context.QueueFamilies); + BufferCreateInfo createInfo = CreateInfo(desc, context.Capabilities, context.QueueFamilies); createInfo.Usage |= usage; context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); @@ -195,13 +195,13 @@ protected override void Destroy() } } - public static BufferCreateInfo CreateInfo(BufferDesc desc, QueueFamilies queueFamilies) + public static BufferCreateInfo CreateInfo(BufferDesc desc, Capabilities capabilities, QueueFamilies queueFamilies) { return new() { SType = StructureType.BufferCreateInfo, Size = desc.SizeInBytes, - Usage = VKFormats.Vulkan(desc.Usages), + Usage = VKFormats.Vulkan(desc.Usages, capabilities.RayTracingSupported), SharingMode = queueFamilies.SharingMode, QueueFamilyIndexCount = queueFamilies.IndexCount, PQueueFamilyIndices = queueFamilies.Indices diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index b4a1c7c3..bfdf7d4b 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -228,18 +228,28 @@ public static (Format Format, ImageAspectFlags AspectFlags) Vulkan(PixelFormat p return (format, aspectFlags); } - public static BufferUsageFlags Vulkan(BufferUsages bufferUsages) + public static BufferUsageFlags Vulkan(BufferUsages bufferUsages, bool rayTracingSupported) { BufferUsageFlags result = BufferUsageFlags.ShaderDeviceAddressBit; if (bufferUsages.HasFlag(BufferUsages.Vertex)) { result |= BufferUsageFlags.VertexBufferBit; + + if (rayTracingSupported) + { + result |= BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; + } } if (bufferUsages.HasFlag(BufferUsages.Index)) { result |= BufferUsageFlags.IndexBufferBit; + + if (rayTracingSupported) + { + result |= BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; + } } if (bufferUsages.HasFlag(BufferUsages.Indirect)) @@ -255,6 +265,11 @@ public static BufferUsageFlags Vulkan(BufferUsages bufferUsages) if (bufferUsages.HasFlag(BufferUsages.StorageReadOnly) || bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) { result |= BufferUsageFlags.StorageBufferBit; + + if (rayTracingSupported) + { + result |= BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; + } } if (bufferUsages.HasFlag(BufferUsages.TransferSrc)) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index a3ec4a34..100c44ea 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -529,7 +529,7 @@ protected override Heap CreateHeapImpl(HeapDesc desc) protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, QueueFamilies); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Capabilities, QueueFamilies); DeviceBufferMemoryRequirements requirements = new() { diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index 3c07c5c0..d3c3098e 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -30,7 +30,7 @@ public override nint GetNativeObject(NativeObjectType type) protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) { - BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Context.QueueFamilies); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Context.Capabilities, Context.QueueFamilies); Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); From cc73a2b7fe15f59e3ab09e9d68727df6a7073808 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 15:34:10 +0800 Subject: [PATCH 357/466] Add Vulkan BLAS creation, update, and device address support Expanded VKBottomLevelAccelerationStructure to allocate buffers, create and build the acceleration structure, and query its device address. Added Update method for in-place geometry updates. Stubbed Info method for geometry/build range preparation. --- .../VKBottomLevelAccelerationStructure.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index 5fd570cb..dbae79cf 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -6,8 +6,58 @@ internal unsafe class VKBottomLevelAccelerationStructure : BottomLevelAccelerati { public AccelerationStructureKHR AccelerationStructure; + public ulong DeviceAddress; + public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) { + using ZenithMarshal.Scope scope = new(); + + Transform = new(context, new() + { + SizeInBytes = (uint)(sizeof(TransformMatrixKHR) * desc.Geometries.Length), + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr); + + AccelerationStructureBuildGeometryInfoKHR info = Info(scope, desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); + + AccelerationStructureBuildSizesInfoKHR sizeInfo = new() { SType = StructureType.AccelerationStructureBuildSizesInfoKhr }; + context.AccelerationStructure?.GetAccelerationStructureBuildSizes(context.Device, AccelerationStructureBuildTypeKHR.DeviceKhr, &info, maxPrimitiveCounts, &sizeInfo); + + Storage = new(context, new() + { + SizeInBytes = (uint)sizeInfo.AccelerationStructureSize, + Residency = MemoryResidency.GpuOnly + }, BufferUsageFlags.AccelerationStructureStorageBitKhr); + + Scratch = new(context, new() + { + SizeInBytes = (uint)sizeInfo.BuildScratchSize, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly + }); + + AccelerationStructureCreateInfoKHR createInfo = new() + { + SType = StructureType.AccelerationStructureCreateInfoKhr, + Buffer = Storage.Buffer, + Size = sizeInfo.AccelerationStructureSize, + Type = AccelerationStructureTypeKHR.BottomLevelKhr + }; + + context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, default, out AccelerationStructure).Success(); + + AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() + { + SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, + AccelerationStructure = AccelerationStructure + }; + + DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0; + + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; @@ -20,6 +70,15 @@ public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBu public void Update(VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) { + using ZenithMarshal.Scope scope = new(); + + AccelerationStructureBuildGeometryInfoKHR info = Info(scope, newDesc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); + info.Mode = BuildAccelerationStructureModeKHR.UpdateKhr; + info.SrcAccelerationStructure = AccelerationStructure; + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); } public override nint GetNativeObject(NativeObjectType type) @@ -50,4 +109,9 @@ protected override void Destroy() Storage.Dispose(); Transform.Dispose(); } + + private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos) + { + throw new NotImplementedException(); + } } From 0a222227f263cf0a2f68cbef171412bdc21849fd Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 16:15:53 +0800 Subject: [PATCH 358/466] Implement BLAS Info method and add Vulkan format stubs Implemented the Info method in VKBottomLevelAccelerationStructure to build Vulkan geometry and range info arrays. Added stub conversion methods in VKFormats for build flags, Matrix4x4, and geometry type, and imported System.Numerics for matrix support. --- .../VKBottomLevelAccelerationStructure.cs | 60 ++++++++++++++++++- sources/Zenith.NET.Vulkan/VKFormats.cs | 18 +++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index dbae79cf..3d5b4c25 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -112,6 +112,64 @@ protected override void Destroy() private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos) { - throw new NotImplementedException(); + uint geometryCount = (uint)desc.Geometries.Length; + + nint pointer = Transform.Map(); + + TransformMatrixKHR* transforms = (TransformMatrixKHR*)pointer; + AccelerationStructureGeometryKHR* geometries = (AccelerationStructureGeometryKHR*)ZenithMarshal.Allocate(scope, geometryCount); + maxPrimitiveCounts = (uint*)ZenithMarshal.Allocate(scope, geometryCount); + buildRangeInfos = (AccelerationStructureBuildRangeInfoKHR*)ZenithMarshal.Allocate(scope, geometryCount); + for (uint i = 0; i < geometryCount; i++) + { + RayTracingGeometry geometry = desc.Geometries[i]; + + transforms[i] = VKFormats.Vulkan(geometry.TriangleGeometry.Transform); + geometries[i] = new() + { + SType = StructureType.AccelerationStructureGeometryKhr, + GeometryType = VKFormats.Vulkan(geometry.Type), + Geometry = new + ( + triangles: geometry.Type is RayTracingGeometryType.Triangle ? new() + { + SType = StructureType.AccelerationStructureGeometryTrianglesDataKhr, + VertexFormat = VKFormats.Vulkan(geometry.TriangleGeometry.VertexFormat).Format, + VertexData = new() { DeviceAddress = geometry.TriangleGeometry.VertexBuffer.Vulkan().DeviceAddress + geometry.TriangleGeometry.VertexOffsetInBytes }, + VertexStride = geometry.TriangleGeometry.VertexStrideInBytes, + MaxVertex = geometry.TriangleGeometry.VertexCount, + IndexType = VKFormats.Vulkan(geometry.TriangleGeometry.IndexFormat), + IndexData = geometry.TriangleGeometry.IndexBuffer is not null ? new() { DeviceAddress = geometry.TriangleGeometry.IndexBuffer.Vulkan().DeviceAddress + geometry.TriangleGeometry.IndexOffsetInBytes } : default, + TransformData = new() { DeviceAddress = Transform.DeviceAddress + (ulong)(sizeof(TransformMatrixKHR) * i) } + } : null, + aabbs: geometry.Type is RayTracingGeometryType.Aabb ? new() + { + SType = StructureType.AccelerationStructureGeometryAabbsDataKhr, + Data = new() { DeviceAddress = geometry.AabbGeometry.Buffer.Vulkan().DeviceAddress + geometry.AabbGeometry.OffsetInBytes }, + Stride = geometry.AabbGeometry.StrideInBytes + } : null + ), + Flags = geometry.IsOpaque ? GeometryFlagsKHR.OpaqueBitKhr : GeometryFlagsKHR.None + }; + maxPrimitiveCounts[i] = geometry.Type switch + { + RayTracingGeometryType.Triangle => geometry.TriangleGeometry.IndexBuffer is null ? geometry.TriangleGeometry.VertexCount / 3u : geometry.TriangleGeometry.IndexCount, + RayTracingGeometryType.Aabb => geometry.AabbGeometry.Count, + _ => default + }; + buildRangeInfos[i] = new() { PrimitiveCount = maxPrimitiveCounts[i] }; + } + + Transform.Unmap(); + + return new() + { + SType = StructureType.AccelerationStructureBuildGeometryInfoKhr, + Type = AccelerationStructureTypeKHR.BottomLevelKhr, + Flags = VKFormats.Vulkan(desc.BuildFlags), + Mode = BuildAccelerationStructureModeKHR.BuildKhr, + GeometryCount = geometryCount, + PGeometries = geometries + }; } } diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index bfdf7d4b..46142d00 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -1,4 +1,5 @@ -using Silk.NET.Vulkan; +using System.Numerics; +using Silk.NET.Vulkan; namespace Zenith.NET.Vulkan; @@ -608,4 +609,19 @@ public static AttachmentStoreOp Vulkan(StoreOp storeOp) _ => default }; } + + internal static BuildAccelerationStructureFlagsKHR Vulkan(AccelerationStructureBuildFlags buildFlags) + { + throw new NotImplementedException(); + } + + internal static TransformMatrixKHR Vulkan(Matrix4x4 transform) + { + throw new NotImplementedException(); + } + + internal static GeometryTypeKHR Vulkan(RayTracingGeometryType type) + { + throw new NotImplementedException(); + } } From ec27eda9b4aedb3c8c0c5a74221d4c1071a9e68f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 16:37:36 +0800 Subject: [PATCH 359/466] Fix scratch buffer sizing and triangle primitive count logic Set scratch buffer size to the max of build and update sizes to ensure sufficient memory. Corrected triangle primitive count calculation by dividing after selecting the appropriate count, fixing a logic error. --- .../Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index 3d5b4c25..f768ecc6 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -31,7 +31,7 @@ public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBu Scratch = new(context, new() { - SizeInBytes = (uint)sizeInfo.BuildScratchSize, + SizeInBytes = (uint)Math.Max(sizeInfo.BuildScratchSize, sizeInfo.UpdateScratchSize), Usages = BufferUsages.StorageReadWrite, Residency = MemoryResidency.GpuOnly }); @@ -153,7 +153,7 @@ private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope }; maxPrimitiveCounts[i] = geometry.Type switch { - RayTracingGeometryType.Triangle => geometry.TriangleGeometry.IndexBuffer is null ? geometry.TriangleGeometry.VertexCount / 3u : geometry.TriangleGeometry.IndexCount, + RayTracingGeometryType.Triangle => (geometry.TriangleGeometry.IndexBuffer is null ? geometry.TriangleGeometry.VertexCount : geometry.TriangleGeometry.IndexCount) / 3u, RayTracingGeometryType.Aabb => geometry.AabbGeometry.Count, _ => default }; From b6e2b7febe1384d5a7da3443565207a3f0a6fd9d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 19:19:32 +0800 Subject: [PATCH 360/466] Expand VKTopLevelAccelerationStructure and update BLAS Significantly refactor VKTopLevelAccelerationStructure: - Add DeviceAddress, Token, and Instance fields - Allocate and initialize buffers in the constructor - Implement Update and proper resource disposal in Destroy - Add BuildSyncBarrier for synchronization - Update BLAS to discard unused maxPrimitiveCounts output --- .../VKBottomLevelAccelerationStructure.cs | 2 +- .../VKTopLevelAccelerationStructure.cs | 99 ++++++++++++++++++- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index f768ecc6..802c8d0f 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -72,7 +72,7 @@ public void Update(VKCommandBuffer commandBuffer, BottomLevelAccelerationStructu { using ZenithMarshal.Scope scope = new(); - AccelerationStructureBuildGeometryInfoKHR info = Info(scope, newDesc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); + AccelerationStructureBuildGeometryInfoKHR info = Info(scope, newDesc, out _, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); info.Mode = BuildAccelerationStructureModeKHR.UpdateKhr; info.SrcAccelerationStructure = AccelerationStructure; info.DstAccelerationStructure = AccelerationStructure; diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index a83cb36b..b100799e 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -6,22 +6,87 @@ internal unsafe class VKTopLevelAccelerationStructure : TopLevelAccelerationStru { public AccelerationStructureKHR AccelerationStructure; + public ulong DeviceAddress; + + public VKDescriptorToken Token; + public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { + using ZenithMarshal.Scope scope = new(); + + Instance = new(context, new() + { + SizeInBytes = (uint)(sizeof(AccelerationStructureInstanceKHR) * desc.Instances.Length), + Residency = MemoryResidency.CpuWriteOnly + }); + + AccelerationStructureBuildGeometryInfoKHR info = Info(scope, desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); + + AccelerationStructureBuildSizesInfoKHR sizeInfo = new() { SType = StructureType.AccelerationStructureBuildSizesInfoKhr }; + context.AccelerationStructure?.GetAccelerationStructureBuildSizes(context.Device, AccelerationStructureBuildTypeKHR.DeviceKhr, &info, maxPrimitiveCounts, &sizeInfo); + + Storage = new(context, new() + { + SizeInBytes = (uint)sizeInfo.AccelerationStructureSize, + Residency = MemoryResidency.GpuOnly + }, BufferUsageFlags.AccelerationStructureStorageBitKhr); + + Scratch = new(context, new() + { + SizeInBytes = (uint)Math.Max(sizeInfo.BuildScratchSize, sizeInfo.UpdateScratchSize), + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly + }); + + AccelerationStructureCreateInfoKHR createInfo = new() + { + SType = StructureType.AccelerationStructureCreateInfoKhr, + Buffer = Storage.Buffer, + Size = sizeInfo.AccelerationStructureSize, + Type = AccelerationStructureTypeKHR.TopLevelKhr + }; + + context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, default, out AccelerationStructure).Success(); + + AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() + { + SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, + AccelerationStructure = AccelerationStructure + }; + + DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0; + + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); + context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public VKBuffer Transform { get; } + public VKBuffer Instance { get; } public VKBuffer Storage { get; } public VKBuffer Scratch { get; } - public override ResourceHandle Handle { get; } + public override ResourceHandle Handle => Token.ResourceHandle; public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) { + using ZenithMarshal.Scope scope = new(); + + AccelerationStructureBuildGeometryInfoKHR info = Info(scope, newDesc, out _, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); + info.Mode = BuildAccelerationStructureModeKHR.UpdateKhr; + info.SrcAccelerationStructure = AccelerationStructure; + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); + Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit); } public override nint GetNativeObject(NativeObjectType type) @@ -46,10 +111,38 @@ protected override void SetResourceName(string name) protected override void Destroy() { + Token.Dispose(); + Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, default); Scratch.Dispose(); Storage.Dispose(); - Transform.Dispose(); + Instance.Dispose(); + } + + private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope, TopLevelAccelerationStructureDesc desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos) + { + throw new NotImplementedException(); + } + + private static void BuildSyncBarrier(VKCommandBuffer commandBuffer, PipelineStageFlags2 dstStage) + { + MemoryBarrier2 memoryBarrier = new() + { + SType = StructureType.MemoryBarrier2, + SrcStageMask = PipelineStageFlags2.AccelerationStructureBuildBitKhr, + SrcAccessMask = AccessFlags2.AccelerationStructureWriteBitKhr, + DstStageMask = dstStage, + DstAccessMask = AccessFlags2.AccelerationStructureReadBitKhr + }; + + DependencyInfo dependencyInfo = new() + { + SType = StructureType.DependencyInfo, + MemoryBarrierCount = 1, + PMemoryBarriers = &memoryBarrier + }; + + commandBuffer.Context.Vk.CmdPipelineBarrier2(commandBuffer.CommandBuffer, &dependencyInfo); } } From 36e167db0ce8028b7d85a518e2e463017f460fd4 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 19:26:36 +0800 Subject: [PATCH 361/466] Refactor descriptor initializers and AS token allocation Refactored DeviceAddressRangeEXT and related structure initializations to use object initializers for improved clarity and consistency. Removed the DeviceAddress field from VKTopLevelAccelerationStructure, replacing it with a local DeviceAddressRangeEXT for VKDescriptorToken allocation. Reordered synchronization barriers and build commands for acceleration structures. Updated VKBufferView and VKTextureView to use object initializers for descriptor data, enhancing readability. --- sources/Zenith.NET.Vulkan/VKBufferView.cs | 11 ++++---- sources/Zenith.NET.Vulkan/VKTextureView.cs | 5 +--- .../VKTopLevelAccelerationStructure.cs | 27 ++++++++++++------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBufferView.cs b/sources/Zenith.NET.Vulkan/VKBufferView.cs index 6b6a54e3..7d930eb3 100644 --- a/sources/Zenith.NET.Vulkan/VKBufferView.cs +++ b/sources/Zenith.NET.Vulkan/VKBufferView.cs @@ -32,16 +32,17 @@ protected override void Destroy() private VKDescriptorToken CreateToken(DescriptorType type) { - DeviceAddressRangeEXT addressRange = new(Desc.Buffer.Vulkan().DeviceAddress + Desc.OffsetInBytes, Desc.SizeInBytes); + DeviceAddressRangeEXT addressRange = new() + { + Address = Desc.Buffer.Vulkan().DeviceAddress + Desc.OffsetInBytes, + Size = Desc.SizeInBytes + }; return context.ResourceHeap.Allocate(new ResourceDescriptorInfoEXT() { SType = StructureType.ResourceDescriptorInfoExt(), Type = type, - Data = new() - { - PAddressRange = &addressRange - } + Data = new() { PAddressRange = &addressRange } }); } } diff --git a/sources/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Zenith.NET.Vulkan/VKTextureView.cs index 02e38a0f..7efc58a7 100644 --- a/sources/Zenith.NET.Vulkan/VKTextureView.cs +++ b/sources/Zenith.NET.Vulkan/VKTextureView.cs @@ -55,10 +55,7 @@ private VKDescriptorToken CreateToken(DescriptorType type, ImageLayout layout) { SType = StructureType.ResourceDescriptorInfoExt(), Type = type, - Data = new() - { - PImage = &image - } + Data = new() { PImage = &image } }); } } diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index b100799e..6b7375c8 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -6,8 +6,6 @@ internal unsafe class VKTopLevelAccelerationStructure : TopLevelAccelerationStru { public AccelerationStructureKHR AccelerationStructure; - public ulong DeviceAddress; - public VKDescriptorToken Token; public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) @@ -48,20 +46,31 @@ public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffe context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, default, out AccelerationStructure).Success(); + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); + context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit); + AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() { SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, AccelerationStructure = AccelerationStructure }; - DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0; - - info.DstAccelerationStructure = AccelerationStructure; - info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + DeviceAddressRangeEXT addressRange = new() + { + Address = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0, + Size = sizeInfo.AccelerationStructureSize + }; - BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); - context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); - BuildSyncBarrier(commandBuffer, PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit); + Token = context.ResourceHeap.Allocate(new ResourceDescriptorInfoEXT() + { + SType = StructureType.ResourceDescriptorInfoExt(), + Type = DescriptorType.AccelerationStructureKhr, + Data = new() { PAddressRange = &addressRange } + }); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; From 7f9cfd252f1043a7358f234dc500865b6a146a32 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 20:03:26 +0800 Subject: [PATCH 362/466] Implement Info method for TLAS build and buffer usage Implemented the Info method to set up top-level acceleration structure (TLAS) build info, including mapping and populating the instance buffer, allocating build range and primitive count arrays, and constructing the AccelerationStructureBuildGeometryInfoKHR. Also set explicit buffer usage flags and removed the NotImplementedException. --- .../VKTopLevelAccelerationStructure.cs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index 6b7375c8..d4b473a6 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -16,7 +16,7 @@ public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffe { SizeInBytes = (uint)(sizeof(AccelerationStructureInstanceKHR) * desc.Instances.Length), Residency = MemoryResidency.CpuWriteOnly - }); + }, BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr); AccelerationStructureBuildGeometryInfoKHR info = Info(scope, desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos); @@ -131,7 +131,47 @@ protected override void Destroy() private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope, TopLevelAccelerationStructureDesc desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos) { - throw new NotImplementedException(); + uint instanceCount = (uint)desc.Instances.Length; + + nint pointer = Instance.Map(); + + AccelerationStructureInstanceKHR* instances = (AccelerationStructureInstanceKHR*)pointer; + for (uint i = 0; i < instanceCount; i++) + { + RayTracingInstance instance = desc.Instances[i]; + + // TODO.. + } + + Instance.Unmap(); + + maxPrimitiveCounts = (uint*)ZenithMarshal.AllocateAndFill(scope, [instanceCount]); + buildRangeInfos = (AccelerationStructureBuildRangeInfoKHR*)ZenithMarshal.AllocateAndFill(scope, [new AccelerationStructureBuildRangeInfoKHR() { PrimitiveCount = instanceCount }]); + + return new() + { + SType = StructureType.AccelerationStructureBuildGeometryInfoKhr, + Type = AccelerationStructureTypeKHR.TopLevelKhr, + Flags = VKFormats.Vulkan(desc.BuildFlags), + Mode = BuildAccelerationStructureModeKHR.BuildKhr, + GeometryCount = 1, + PGeometries = (AccelerationStructureGeometryKHR*)ZenithMarshal.AllocateAndFill(scope, + [ + new AccelerationStructureGeometryKHR() + { + SType = StructureType.AccelerationStructureGeometryKhr, + GeometryType = GeometryTypeKHR.InstancesKhr, + Geometry = new() + { + Instances = new() + { + SType = StructureType.AccelerationStructureGeometryInstancesDataKhr, + Data = new() { DeviceAddress = Instance.DeviceAddress } + } + } + } + ]) + }; } private static void BuildSyncBarrier(VKCommandBuffer commandBuffer, PipelineStageFlags2 dstStage) From af7c4c3323bea06c3b5276af8014221e2db1fa2c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 6 Jul 2026 21:24:58 +0800 Subject: [PATCH 363/466] Reorder BLAS build to ensure device address validity Reordered assignments and build call so the acceleration structure is built before querying its device address, ensuring correct initialization and usage. --- .../VKBottomLevelAccelerationStructure.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index 802c8d0f..b542a751 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -46,6 +46,11 @@ public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBu context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, default, out AccelerationStructure).Success(); + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() { SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, @@ -53,11 +58,6 @@ public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBu }; DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0; - - info.DstAccelerationStructure = AccelerationStructure; - info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; - - context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; From 70aee84c4c02bb0a4ea4a13a07d1c9c3085206ce Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 00:05:31 +0800 Subject: [PATCH 364/466] Implement instance population in VKTopLevelAccelerationStructure Added Vulkan(RayTracingInstanceFlags) stub to VKFormats. Replaced TODO with actual instance data assignment in VKTopLevelAccelerationStructure, setting transform, custom index, mask, flags, and device address for each instance. --- sources/Zenith.NET.Vulkan/VKFormats.cs | 5 +++++ .../Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 46142d00..b7e3b361 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -624,4 +624,9 @@ internal static GeometryTypeKHR Vulkan(RayTracingGeometryType type) { throw new NotImplementedException(); } + + internal static GeometryInstanceFlagsKHR Vulkan(RayTracingInstanceFlags flags) + { + throw new NotImplementedException(); + } } diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index d4b473a6..6d83edaa 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -140,7 +140,14 @@ private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope { RayTracingInstance instance = desc.Instances[i]; - // TODO.. + instances[i] = new() + { + Transform = VKFormats.Vulkan(instance.Transform), + InstanceCustomIndex = instance.InstanceId, + Mask = instance.VisibilityMask, + Flags = VKFormats.Vulkan(instance.Flags), + AccelerationStructureReference = instance.AccelerationStructure.Vulkan().DeviceAddress + }; } Instance.Unmap(); From 1636269b0a8455a3b4d434a601d330a2335f2c2d Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 01:18:33 +0800 Subject: [PATCH 365/466] Refactor and extend Vulkan format mapping utilities Refactored VKFormats.cs to provide explicit, comprehensive conversions between Zenith.NET abstractions and Vulkan enums/flags. Re-implemented and expanded mapping methods for texture, pixel, and pipeline types, added new conversions (e.g., Matrix4x4 to TransformMatrixKHR), and improved modularity and maintainability for Vulkan interop. --- sources/Zenith.NET.Vulkan/VKFormats.cs | 812 ++++++++++++++----------- 1 file changed, 445 insertions(+), 367 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index b7e3b361..eb7d57e2 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -5,55 +5,36 @@ namespace Zenith.NET.Vulkan; internal static class VKFormats { - public static (ImageType Type, ImageViewType ViewType) Vulkan(TextureType textureType) + public static BuildAccelerationStructureFlagsKHR Vulkan(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - return - ( - textureType switch - { - TextureType.Texture1D or - TextureType.Texture1DArray => ImageType.Type1D, + BuildAccelerationStructureFlagsKHR result = default; - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ImageType.Type2D, + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + { + result |= BuildAccelerationStructureFlagsKHR.AllowUpdateBitKhr; + } - TextureType.Texture3D => ImageType.Type3D, + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) + { + result |= BuildAccelerationStructureFlagsKHR.AllowCompactionBitKhr; + } - _ => default - }, - textureType switch - { - TextureType.Texture1D => ImageViewType.Type1D, - TextureType.Texture1DArray => ImageViewType.Type1DArray, - TextureType.Texture2D => ImageViewType.Type2D, - TextureType.Texture2DArray => ImageViewType.Type2DArray, - TextureType.Texture3D => ImageViewType.Type3D, - TextureType.TextureCube => ImageViewType.TypeCube, - TextureType.TextureCubeArray => ImageViewType.TypeCubeArray, - _ => default - } - ); - } + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + { + result |= BuildAccelerationStructureFlagsKHR.PreferFastTraceBitKhr; + } - public static (Filter Filter, SamplerMipmapMode MipmapMode) Vulkan(FilterMode filterMode) - { - return - ( - filterMode switch - { - FilterMode.Point => Filter.Nearest, - FilterMode.Linear => Filter.Linear, - _ => default - }, - filterMode switch - { - FilterMode.Point => SamplerMipmapMode.Nearest, - FilterMode.Linear => SamplerMipmapMode.Linear, - _ => default - } - ); + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + { + result |= BuildAccelerationStructureFlagsKHR.PreferFastBuildBitKhr; + } + + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + { + result |= BuildAccelerationStructureFlagsKHR.LowMemoryBitKhr; + } + + return result; } public static SamplerAddressMode Vulkan(AddressMode addressMode) @@ -68,165 +49,97 @@ public static SamplerAddressMode Vulkan(AddressMode addressMode) }; } - public static VkCompareOp Vulkan(CompareOp compareOp) + public static (PipelineStageFlags2 Stage, AccessFlags2 Access) Vulkan(BarrierStages barrierStages) { - return compareOp switch + if (barrierStages is BarrierStages.None) { - CompareOp.Never => VkCompareOp.Never, - CompareOp.Less => VkCompareOp.Less, - CompareOp.Equal => VkCompareOp.Equal, - CompareOp.LessEqual => VkCompareOp.LessOrEqual, - CompareOp.Greater => VkCompareOp.Greater, - CompareOp.NotEqual => VkCompareOp.NotEqual, - CompareOp.GreaterEqual => VkCompareOp.GreaterOrEqual, - CompareOp.Always => VkCompareOp.Always, - _ => default - }; + return (PipelineStageFlags2.None, AccessFlags2.None); + } + + PipelineStageFlags2 stage = default; + AccessFlags2 access = default; + + 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; + } + + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) + { + stage |= PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.EarlyFragmentTestsBit | PipelineStageFlags2.LateFragmentTestsBit | PipelineStageFlags2.ColorAttachmentOutputBit; + access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.ColorAttachmentReadBit | AccessFlags2.ColorAttachmentWriteBit | AccessFlags2.DepthStencilAttachmentReadBit | AccessFlags2.DepthStencilAttachmentWriteBit | AccessFlags2.AccelerationStructureReadBitKhr; + } + + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) + { + stage |= PipelineStageFlags2.ComputeShaderBit | PipelineStageFlags2.DrawIndirectBit; + access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.IndirectCommandReadBit | AccessFlags2.AccelerationStructureReadBitKhr; + } + + if (barrierStages.HasFlag(BarrierStages.Copy)) + { + stage |= PipelineStageFlags2.CopyBit; + access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; + } + + if (barrierStages.HasFlag(BarrierStages.Resolve)) + { + stage |= PipelineStageFlags2.ResolveBit; + access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; + } + + if (barrierStages.HasFlag(BarrierStages.All)) + { + stage = PipelineStageFlags2.AllCommandsBit; + access = AccessFlags2.MemoryReadBit | AccessFlags2.MemoryWriteBit; + } + + return (stage, access); } - public static VkBorderColor Vulkan(BorderColor borderColor) + public static VkBlendFactor Vulkan(BlendFactor blendFactor) { - return borderColor switch + return blendFactor switch { - BorderColor.TransparentBlack => VkBorderColor.FloatTransparentBlack, - BorderColor.OpaqueBlack => VkBorderColor.FloatOpaqueBlack, - BorderColor.OpaqueWhite => VkBorderColor.FloatOpaqueWhite, + BlendFactor.Zero => VkBlendFactor.Zero, + BlendFactor.One => VkBlendFactor.One, + BlendFactor.SrcColor => VkBlendFactor.SrcColor, + BlendFactor.OneMinusSrcColor => VkBlendFactor.OneMinusSrcColor, + BlendFactor.DstColor => VkBlendFactor.DstColor, + BlendFactor.OneMinusDstColor => VkBlendFactor.OneMinusDstColor, + BlendFactor.SrcAlpha => VkBlendFactor.SrcAlpha, + BlendFactor.OneMinusSrcAlpha => VkBlendFactor.OneMinusSrcAlpha, + BlendFactor.DstAlpha => VkBlendFactor.DstAlpha, + BlendFactor.OneMinusDstAlpha => VkBlendFactor.OneMinusDstAlpha, + BlendFactor.Constant => VkBlendFactor.ConstantColor, + BlendFactor.OneMinusConstant => VkBlendFactor.OneMinusConstantColor, _ => default }; } - public static SampleCountFlags Vulkan(SampleCount sampleCount) + public static VkBlendOp Vulkan(BlendOp blendOp) { - return sampleCount switch + return blendOp switch { - SampleCount.Count1 => SampleCountFlags.Count1Bit, - SampleCount.Count2 => SampleCountFlags.Count2Bit, - SampleCount.Count4 => SampleCountFlags.Count4Bit, - SampleCount.Count8 => SampleCountFlags.Count8Bit, - SampleCount.Count16 => SampleCountFlags.Count16Bit, - SampleCount.Count32 => SampleCountFlags.Count32Bit, + BlendOp.Add => VkBlendOp.Add, + BlendOp.Subtract => VkBlendOp.Subtract, + BlendOp.ReverseSubtract => VkBlendOp.ReverseSubtract, + BlendOp.Min => VkBlendOp.Min, + BlendOp.Max => VkBlendOp.Max, _ => default }; } - public static (Format Format, ImageAspectFlags AspectFlags) Vulkan(PixelFormat pixelFormat) + public static VkBorderColor Vulkan(BorderColor borderColor) { - Format format = pixelFormat switch + return borderColor switch { - PixelFormat.R8UNorm => Format.R8Unorm, - PixelFormat.R8SNorm => Format.R8SNorm, - PixelFormat.R8UInt => Format.R8Uint, - PixelFormat.R8SInt => Format.R8Sint, - - PixelFormat.R16UNorm => Format.R16Unorm, - PixelFormat.R16SNorm => Format.R16SNorm, - PixelFormat.R16UInt => Format.R16Uint, - PixelFormat.R16SInt => Format.R16Sint, - PixelFormat.R16Float => Format.R16Sfloat, - - PixelFormat.R32UInt => Format.R32Uint, - PixelFormat.R32SInt => Format.R32Sint, - PixelFormat.R32Float => Format.R32Sfloat, - - PixelFormat.R8G8UNorm => Format.R8G8Unorm, - PixelFormat.R8G8SNorm => Format.R8G8SNorm, - PixelFormat.R8G8UInt => Format.R8G8Uint, - PixelFormat.R8G8SInt => Format.R8G8Sint, - - PixelFormat.R16G16UNorm => Format.R16G16Unorm, - PixelFormat.R16G16SNorm => Format.R16G16SNorm, - PixelFormat.R16G16UInt => Format.R16G16Uint, - PixelFormat.R16G16SInt => Format.R16G16Sint, - PixelFormat.R16G16Float => Format.R16G16Sfloat, - - PixelFormat.R32G32UInt => Format.R32G32Uint, - PixelFormat.R32G32SInt => Format.R32G32Sint, - PixelFormat.R32G32Float => Format.R32G32Sfloat, - - PixelFormat.R32G32B32UInt => Format.R32G32B32Uint, - PixelFormat.R32G32B32SInt => Format.R32G32B32Sint, - PixelFormat.R32G32B32Float => Format.R32G32B32Sfloat, - - PixelFormat.R8G8B8A8UNorm => Format.R8G8B8A8Unorm, - PixelFormat.R8G8B8A8SNorm => Format.R8G8B8A8SNorm, - PixelFormat.R8G8B8A8UInt => Format.R8G8B8A8Uint, - PixelFormat.R8G8B8A8SInt => Format.R8G8B8A8Sint, - PixelFormat.R8G8B8A8SRgb => Format.R8G8B8A8Srgb, - - PixelFormat.R16G16B16A16UNorm => Format.R16G16B16A16Unorm, - PixelFormat.R16G16B16A16SNorm => Format.R16G16B16A16SNorm, - PixelFormat.R16G16B16A16UInt => Format.R16G16B16A16Uint, - PixelFormat.R16G16B16A16SInt => Format.R16G16B16A16Sint, - PixelFormat.R16G16B16A16Float => Format.R16G16B16A16Sfloat, - - PixelFormat.R32G32B32A32UInt => Format.R32G32B32A32Uint, - PixelFormat.R32G32B32A32SInt => Format.R32G32B32A32Sint, - PixelFormat.R32G32B32A32Float => Format.R32G32B32A32Sfloat, - - PixelFormat.B8G8R8A8UNorm => Format.B8G8R8A8Unorm, - PixelFormat.B8G8R8A8SRgb => Format.B8G8R8A8Srgb, - - PixelFormat.D16UNorm => Format.D16Unorm, - PixelFormat.D24UNormS8UInt => Format.D24UnormS8Uint, - PixelFormat.D32Float => Format.D32Sfloat, - PixelFormat.D32FloatS8UInt => Format.D32SfloatS8Uint, - - PixelFormat.BC4UNorm => Format.BC4UnormBlock, - PixelFormat.BC4SNorm => Format.BC4SNormBlock, - PixelFormat.BC5UNorm => Format.BC5UnormBlock, - PixelFormat.BC5SNorm => Format.BC5SNormBlock, - PixelFormat.BC6HUFloat => Format.BC6HUfloatBlock, - PixelFormat.BC6HSFloat => Format.BC6HSfloatBlock, - PixelFormat.BC7UNorm => Format.BC7UnormBlock, - PixelFormat.BC7SRgb => Format.BC7SrgbBlock, - - PixelFormat.ETC2UNorm => Format.Etc2R8G8B8UnormBlock, - PixelFormat.ETC2SRgb => Format.Etc2R8G8B8SrgbBlock, - PixelFormat.ETC2A1UNorm => Format.Etc2R8G8B8A1UnormBlock, - PixelFormat.ETC2A1SRgb => Format.Etc2R8G8B8A1SrgbBlock, - PixelFormat.ETC2A8UNorm => Format.Etc2R8G8B8A8UnormBlock, - PixelFormat.ETC2A8SRgb => Format.Etc2R8G8B8A8SrgbBlock, - - PixelFormat.ASTC4x4UNorm => Format.Astc4x4UnormBlock, - PixelFormat.ASTC4x4SRgb => Format.Astc4x4SrgbBlock, - PixelFormat.ASTC4x4Float => Format.Astc4x4SfloatBlock, - PixelFormat.ASTC5x5UNorm => Format.Astc5x5UnormBlock, - PixelFormat.ASTC5x5SRgb => Format.Astc5x5SrgbBlock, - PixelFormat.ASTC5x5Float => Format.Astc5x5SfloatBlock, - PixelFormat.ASTC6x6UNorm => Format.Astc6x6UnormBlock, - PixelFormat.ASTC6x6SRgb => Format.Astc6x6SrgbBlock, - PixelFormat.ASTC6x6Float => Format.Astc6x6SfloatBlock, - PixelFormat.ASTC8x8UNorm => Format.Astc8x8UnormBlock, - PixelFormat.ASTC8x8SRgb => Format.Astc8x8SrgbBlock, - PixelFormat.ASTC8x8Float => Format.Astc8x8SfloatBlock, - PixelFormat.ASTC10x10UNorm => Format.Astc10x10UnormBlock, - PixelFormat.ASTC10x10SRgb => Format.Astc10x10SrgbBlock, - PixelFormat.ASTC10x10Float => Format.Astc10x10SfloatBlock, - PixelFormat.ASTC12x12UNorm => Format.Astc12x12UnormBlock, - PixelFormat.ASTC12x12SRgb => Format.Astc12x12SrgbBlock, - PixelFormat.ASTC12x12Float => Format.Astc12x12SfloatBlock, - + BorderColor.TransparentBlack => VkBorderColor.FloatTransparentBlack, + BorderColor.OpaqueBlack => VkBorderColor.FloatOpaqueBlack, + BorderColor.OpaqueWhite => VkBorderColor.FloatOpaqueWhite, _ => default }; - - ImageAspectFlags aspectFlags = default; - - if (ZenithHelper.HasDepth(pixelFormat)) - { - aspectFlags |= ImageAspectFlags.DepthBit; - } - - if (ZenithHelper.HasStencil(pixelFormat)) - { - aspectFlags |= ImageAspectFlags.StencilBit; - } - - if (aspectFlags is 0) - { - aspectFlags = ImageAspectFlags.ColorBit; - } - - return (format, aspectFlags); } public static BufferUsageFlags Vulkan(BufferUsages bufferUsages, bool rayTracingSupported) @@ -286,41 +199,58 @@ public static BufferUsageFlags Vulkan(BufferUsages bufferUsages, bool rayTracing return result; } - public static ImageUsageFlags Vulkan(TextureUsages textureUsages) + public static ColorComponentFlags Vulkan(ColorWrites colorWrites) { - ImageUsageFlags result = default; + ColorComponentFlags result = default; - if (textureUsages.HasFlag(TextureUsages.Sampled)) + if (colorWrites.HasFlag(ColorWrites.Red)) { - result |= ImageUsageFlags.SampledBit; + result |= ColorComponentFlags.RBit; } - if (textureUsages.HasFlag(TextureUsages.Storage)) + if (colorWrites.HasFlag(ColorWrites.Green)) { - result |= ImageUsageFlags.StorageBit; + result |= ColorComponentFlags.GBit; } - if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) + if (colorWrites.HasFlag(ColorWrites.Blue)) { - result |= ImageUsageFlags.ColorAttachmentBit; + result |= ColorComponentFlags.BBit; } - if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) + if (colorWrites.HasFlag(ColorWrites.Alpha)) { - result |= ImageUsageFlags.DepthStencilAttachmentBit; + result |= ColorComponentFlags.ABit; } - if (textureUsages.HasFlag(TextureUsages.TransferSrc)) - { - result |= ImageUsageFlags.TransferSrcBit; - } + return result; + } - if (textureUsages.HasFlag(TextureUsages.TransferDst)) + public static VkCompareOp Vulkan(CompareOp compareOp) + { + return compareOp switch { - result |= ImageUsageFlags.TransferDstBit; - } + CompareOp.Never => VkCompareOp.Never, + CompareOp.Less => VkCompareOp.Less, + CompareOp.Equal => VkCompareOp.Equal, + CompareOp.LessEqual => VkCompareOp.LessOrEqual, + CompareOp.Greater => VkCompareOp.Greater, + CompareOp.NotEqual => VkCompareOp.NotEqual, + CompareOp.GreaterEqual => VkCompareOp.GreaterOrEqual, + CompareOp.Always => VkCompareOp.Always, + _ => default + }; + } - return result; + public static CullModeFlags Vulkan(CullMode cullMode) + { + return cullMode switch + { + CullMode.None => CullModeFlags.None, + CullMode.Front => CullModeFlags.FrontBit, + CullMode.Back => CullModeFlags.BackBit, + _ => default + }; } public static Format Vulkan(ElementFormat elementFormat) @@ -355,31 +285,235 @@ public static Format Vulkan(ElementFormat elementFormat) ElementFormat.UShort2UNorm => Format.R16G16Unorm, ElementFormat.UShort4UNorm => Format.R16G16B16A16Unorm, - ElementFormat.Short1SNorm => Format.R16SNorm, - ElementFormat.Short2SNorm => Format.R16G16SNorm, - ElementFormat.Short4SNorm => Format.R16G16B16A16SNorm, + ElementFormat.Short1SNorm => Format.R16SNorm, + ElementFormat.Short2SNorm => Format.R16G16SNorm, + ElementFormat.Short4SNorm => Format.R16G16B16A16SNorm, + + ElementFormat.Half1 => Format.R16Sfloat, + ElementFormat.Half2 => Format.R16G16Sfloat, + ElementFormat.Half4 => Format.R16G16B16A16Sfloat, + + ElementFormat.Float1 => Format.R32Sfloat, + ElementFormat.Float2 => Format.R32G32Sfloat, + ElementFormat.Float3 => Format.R32G32B32Sfloat, + ElementFormat.Float4 => Format.R32G32B32A32Sfloat, + + ElementFormat.UInt1 => Format.R32Uint, + ElementFormat.UInt2 => Format.R32G32Uint, + ElementFormat.UInt3 => Format.R32G32B32Uint, + ElementFormat.UInt4 => Format.R32G32B32A32Uint, + + ElementFormat.Int1 => Format.R32Sint, + ElementFormat.Int2 => Format.R32G32Sint, + ElementFormat.Int3 => Format.R32G32B32Sint, + ElementFormat.Int4 => Format.R32G32B32A32Sint, + + _ => default + }; + } + + public static PolygonMode Vulkan(FillMode fillMode) + { + return fillMode switch + { + FillMode.Solid => PolygonMode.Fill, + FillMode.Wireframe => PolygonMode.Line, + _ => default + }; + } + + public static (Filter Filter, SamplerMipmapMode MipmapMode) Vulkan(FilterMode filterMode) + { + return + ( + filterMode switch + { + FilterMode.Point => Filter.Nearest, + FilterMode.Linear => Filter.Linear, + _ => default + }, + filterMode switch + { + FilterMode.Point => SamplerMipmapMode.Nearest, + FilterMode.Linear => SamplerMipmapMode.Linear, + _ => default + } + ); + } + + public static VkFrontFace Vulkan(FrontFace frontFace) + { + return frontFace switch + { + FrontFace.CounterClockwise => VkFrontFace.CounterClockwise, + FrontFace.Clockwise => VkFrontFace.Clockwise, + _ => default + }; + } + + public static IndexType Vulkan(IndexFormat indexFormat) + { + return indexFormat switch + { + IndexFormat.UInt16 => IndexType.Uint16, + IndexFormat.UInt32 => IndexType.Uint32, + _ => default + }; + } + + public static AttachmentLoadOp Vulkan(LoadOp loadOp) + { + return loadOp switch + { + LoadOp.Load => AttachmentLoadOp.Load, + LoadOp.Clear => AttachmentLoadOp.Clear, + LoadOp.DontCare => AttachmentLoadOp.DontCare, + _ => default + }; + } + + public static unsafe TransformMatrixKHR Vulkan(Matrix4x4 matrix4x4) + { + TransformMatrixKHR result; + + float* pResult = (float*)&result; + + pResult[0] = matrix4x4.M11; + pResult[1] = matrix4x4.M21; + pResult[2] = matrix4x4.M31; + pResult[3] = matrix4x4.M41; + + pResult[4] = matrix4x4.M12; + pResult[5] = matrix4x4.M22; + pResult[6] = matrix4x4.M32; + pResult[7] = matrix4x4.M42; + + pResult[8] = matrix4x4.M13; + pResult[9] = matrix4x4.M23; + pResult[10] = matrix4x4.M33; + pResult[11] = matrix4x4.M43; + + return result; + } + + public static (Format Format, ImageAspectFlags AspectFlags) Vulkan(PixelFormat pixelFormat) + { + Format format = pixelFormat switch + { + PixelFormat.R8UNorm => Format.R8Unorm, + PixelFormat.R8SNorm => Format.R8SNorm, + PixelFormat.R8UInt => Format.R8Uint, + PixelFormat.R8SInt => Format.R8Sint, + + PixelFormat.R16UNorm => Format.R16Unorm, + PixelFormat.R16SNorm => Format.R16SNorm, + PixelFormat.R16UInt => Format.R16Uint, + PixelFormat.R16SInt => Format.R16Sint, + PixelFormat.R16Float => Format.R16Sfloat, + + PixelFormat.R32UInt => Format.R32Uint, + PixelFormat.R32SInt => Format.R32Sint, + PixelFormat.R32Float => Format.R32Sfloat, + + PixelFormat.R8G8UNorm => Format.R8G8Unorm, + PixelFormat.R8G8SNorm => Format.R8G8SNorm, + PixelFormat.R8G8UInt => Format.R8G8Uint, + PixelFormat.R8G8SInt => Format.R8G8Sint, + + PixelFormat.R16G16UNorm => Format.R16G16Unorm, + PixelFormat.R16G16SNorm => Format.R16G16SNorm, + PixelFormat.R16G16UInt => Format.R16G16Uint, + PixelFormat.R16G16SInt => Format.R16G16Sint, + PixelFormat.R16G16Float => Format.R16G16Sfloat, + + PixelFormat.R32G32UInt => Format.R32G32Uint, + PixelFormat.R32G32SInt => Format.R32G32Sint, + PixelFormat.R32G32Float => Format.R32G32Sfloat, + + PixelFormat.R32G32B32UInt => Format.R32G32B32Uint, + PixelFormat.R32G32B32SInt => Format.R32G32B32Sint, + PixelFormat.R32G32B32Float => Format.R32G32B32Sfloat, + + PixelFormat.R8G8B8A8UNorm => Format.R8G8B8A8Unorm, + PixelFormat.R8G8B8A8SNorm => Format.R8G8B8A8SNorm, + PixelFormat.R8G8B8A8UInt => Format.R8G8B8A8Uint, + PixelFormat.R8G8B8A8SInt => Format.R8G8B8A8Sint, + PixelFormat.R8G8B8A8SRgb => Format.R8G8B8A8Srgb, + + PixelFormat.R16G16B16A16UNorm => Format.R16G16B16A16Unorm, + PixelFormat.R16G16B16A16SNorm => Format.R16G16B16A16SNorm, + PixelFormat.R16G16B16A16UInt => Format.R16G16B16A16Uint, + PixelFormat.R16G16B16A16SInt => Format.R16G16B16A16Sint, + PixelFormat.R16G16B16A16Float => Format.R16G16B16A16Sfloat, + + PixelFormat.R32G32B32A32UInt => Format.R32G32B32A32Uint, + PixelFormat.R32G32B32A32SInt => Format.R32G32B32A32Sint, + PixelFormat.R32G32B32A32Float => Format.R32G32B32A32Sfloat, + + PixelFormat.B8G8R8A8UNorm => Format.B8G8R8A8Unorm, + PixelFormat.B8G8R8A8SRgb => Format.B8G8R8A8Srgb, + + PixelFormat.D16UNorm => Format.D16Unorm, + PixelFormat.D24UNormS8UInt => Format.D24UnormS8Uint, + PixelFormat.D32Float => Format.D32Sfloat, + PixelFormat.D32FloatS8UInt => Format.D32SfloatS8Uint, + + PixelFormat.BC4UNorm => Format.BC4UnormBlock, + PixelFormat.BC4SNorm => Format.BC4SNormBlock, + PixelFormat.BC5UNorm => Format.BC5UnormBlock, + PixelFormat.BC5SNorm => Format.BC5SNormBlock, + PixelFormat.BC6HUFloat => Format.BC6HUfloatBlock, + PixelFormat.BC6HSFloat => Format.BC6HSfloatBlock, + PixelFormat.BC7UNorm => Format.BC7UnormBlock, + PixelFormat.BC7SRgb => Format.BC7SrgbBlock, + + PixelFormat.ETC2UNorm => Format.Etc2R8G8B8UnormBlock, + PixelFormat.ETC2SRgb => Format.Etc2R8G8B8SrgbBlock, + PixelFormat.ETC2A1UNorm => Format.Etc2R8G8B8A1UnormBlock, + PixelFormat.ETC2A1SRgb => Format.Etc2R8G8B8A1SrgbBlock, + PixelFormat.ETC2A8UNorm => Format.Etc2R8G8B8A8UnormBlock, + PixelFormat.ETC2A8SRgb => Format.Etc2R8G8B8A8SrgbBlock, + + PixelFormat.ASTC4x4UNorm => Format.Astc4x4UnormBlock, + PixelFormat.ASTC4x4SRgb => Format.Astc4x4SrgbBlock, + PixelFormat.ASTC4x4Float => Format.Astc4x4SfloatBlock, + PixelFormat.ASTC5x5UNorm => Format.Astc5x5UnormBlock, + PixelFormat.ASTC5x5SRgb => Format.Astc5x5SrgbBlock, + PixelFormat.ASTC5x5Float => Format.Astc5x5SfloatBlock, + PixelFormat.ASTC6x6UNorm => Format.Astc6x6UnormBlock, + PixelFormat.ASTC6x6SRgb => Format.Astc6x6SrgbBlock, + PixelFormat.ASTC6x6Float => Format.Astc6x6SfloatBlock, + PixelFormat.ASTC8x8UNorm => Format.Astc8x8UnormBlock, + PixelFormat.ASTC8x8SRgb => Format.Astc8x8SrgbBlock, + PixelFormat.ASTC8x8Float => Format.Astc8x8SfloatBlock, + PixelFormat.ASTC10x10UNorm => Format.Astc10x10UnormBlock, + PixelFormat.ASTC10x10SRgb => Format.Astc10x10SrgbBlock, + PixelFormat.ASTC10x10Float => Format.Astc10x10SfloatBlock, + PixelFormat.ASTC12x12UNorm => Format.Astc12x12UnormBlock, + PixelFormat.ASTC12x12SRgb => Format.Astc12x12SrgbBlock, + PixelFormat.ASTC12x12Float => Format.Astc12x12SfloatBlock, + + _ => default + }; - ElementFormat.Half1 => Format.R16Sfloat, - ElementFormat.Half2 => Format.R16G16Sfloat, - ElementFormat.Half4 => Format.R16G16B16A16Sfloat, + ImageAspectFlags aspectFlags = default; - ElementFormat.Float1 => Format.R32Sfloat, - ElementFormat.Float2 => Format.R32G32Sfloat, - ElementFormat.Float3 => Format.R32G32B32Sfloat, - ElementFormat.Float4 => Format.R32G32B32A32Sfloat, + if (ZenithHelper.HasDepth(pixelFormat)) + { + aspectFlags |= ImageAspectFlags.DepthBit; + } - ElementFormat.UInt1 => Format.R32Uint, - ElementFormat.UInt2 => Format.R32G32Uint, - ElementFormat.UInt3 => Format.R32G32B32Uint, - ElementFormat.UInt4 => Format.R32G32B32A32Uint, + if (ZenithHelper.HasStencil(pixelFormat)) + { + aspectFlags |= ImageAspectFlags.StencilBit; + } - ElementFormat.Int1 => Format.R32Sint, - ElementFormat.Int2 => Format.R32G32Sint, - ElementFormat.Int3 => Format.R32G32B32Sint, - ElementFormat.Int4 => Format.R32G32B32A32Sint, + if (aspectFlags is 0) + { + aspectFlags = ImageAspectFlags.ColorBit; + } - _ => default - }; + return (format, aspectFlags); } public static VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) @@ -408,97 +542,57 @@ QueryType.Occlusion or }; } - public static PolygonMode Vulkan(FillMode fillMode) - { - return fillMode switch - { - FillMode.Solid => PolygonMode.Fill, - FillMode.Wireframe => PolygonMode.Line, - _ => default - }; - } - - public static CullModeFlags Vulkan(CullMode cullMode) - { - return cullMode switch - { - CullMode.None => CullModeFlags.None, - CullMode.Front => CullModeFlags.FrontBit, - CullMode.Back => CullModeFlags.BackBit, - _ => default - }; - } - - public static VkFrontFace Vulkan(FrontFace frontFace) - { - return frontFace switch - { - FrontFace.CounterClockwise => VkFrontFace.CounterClockwise, - FrontFace.Clockwise => VkFrontFace.Clockwise, - _ => default - }; - } - - public static VkBlendFactor Vulkan(BlendFactor blendFactor) - { - return blendFactor switch - { - BlendFactor.Zero => VkBlendFactor.Zero, - BlendFactor.One => VkBlendFactor.One, - BlendFactor.SrcColor => VkBlendFactor.SrcColor, - BlendFactor.OneMinusSrcColor => VkBlendFactor.OneMinusSrcColor, - BlendFactor.DstColor => VkBlendFactor.DstColor, - BlendFactor.OneMinusDstColor => VkBlendFactor.OneMinusDstColor, - BlendFactor.SrcAlpha => VkBlendFactor.SrcAlpha, - BlendFactor.OneMinusSrcAlpha => VkBlendFactor.OneMinusSrcAlpha, - BlendFactor.DstAlpha => VkBlendFactor.DstAlpha, - BlendFactor.OneMinusDstAlpha => VkBlendFactor.OneMinusDstAlpha, - BlendFactor.Constant => VkBlendFactor.ConstantColor, - BlendFactor.OneMinusConstant => VkBlendFactor.OneMinusConstantColor, - _ => default - }; - } - - public static VkBlendOp Vulkan(BlendOp blendOp) + public static GeometryTypeKHR Vulkan(RayTracingGeometryType rayTracingGeometryType) { - return blendOp switch + return rayTracingGeometryType switch { - BlendOp.Add => VkBlendOp.Add, - BlendOp.Subtract => VkBlendOp.Subtract, - BlendOp.ReverseSubtract => VkBlendOp.ReverseSubtract, - BlendOp.Min => VkBlendOp.Min, - BlendOp.Max => VkBlendOp.Max, + RayTracingGeometryType.Triangle => GeometryTypeKHR.TrianglesKhr, + RayTracingGeometryType.Aabb => GeometryTypeKHR.AabbsKhr, _ => default }; } - public static ColorComponentFlags Vulkan(ColorWrites colorWrites) + public static GeometryInstanceFlagsKHR Vulkan(RayTracingInstanceFlags rayTracingInstanceFlags) { - ColorComponentFlags result = default; + GeometryInstanceFlagsKHR result = default; - if (colorWrites.HasFlag(ColorWrites.Red)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { - result |= ColorComponentFlags.RBit; + result |= GeometryInstanceFlagsKHR.TriangleFrontCounterclockwiseBitKhr; } - if (colorWrites.HasFlag(ColorWrites.Green)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) { - result |= ColorComponentFlags.GBit; + result |= GeometryInstanceFlagsKHR.TriangleFacingCullDisableBitKhr; } - if (colorWrites.HasFlag(ColorWrites.Blue)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) { - result |= ColorComponentFlags.BBit; + result |= GeometryInstanceFlagsKHR.ForceOpaqueBitKhr; } - if (colorWrites.HasFlag(ColorWrites.Alpha)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) { - result |= ColorComponentFlags.ABit; + result |= GeometryInstanceFlagsKHR.ForceNoOpaqueBitKhr; } return result; } + public static SampleCountFlags Vulkan(SampleCount sampleCount) + { + return sampleCount switch + { + SampleCount.Count1 => SampleCountFlags.Count1Bit, + SampleCount.Count2 => SampleCountFlags.Count2Bit, + SampleCount.Count4 => SampleCountFlags.Count4Bit, + SampleCount.Count8 => SampleCountFlags.Count8Bit, + SampleCount.Count16 => SampleCountFlags.Count16Bit, + SampleCount.Count32 => SampleCountFlags.Count32Bit, + _ => default + }; + } + public static VkStencilOp Vulkan(StencilOp stencilOp) { return stencilOp switch @@ -515,53 +609,14 @@ public static VkStencilOp Vulkan(StencilOp stencilOp) }; } - public static (PipelineStageFlags2 Stage, AccessFlags2 Access) Vulkan(BarrierStages barrierStages) + public static AttachmentStoreOp Vulkan(StoreOp storeOp) { - if (barrierStages is BarrierStages.None) - { - return (PipelineStageFlags2.None, AccessFlags2.None); - } - - PipelineStageFlags2 stage = default; - AccessFlags2 access = default; - - 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; - } - - if (barrierStages.HasFlag(BarrierStages.FragmentShading)) - { - stage |= PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.EarlyFragmentTestsBit | PipelineStageFlags2.LateFragmentTestsBit | PipelineStageFlags2.ColorAttachmentOutputBit; - access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.ColorAttachmentReadBit | AccessFlags2.ColorAttachmentWriteBit | AccessFlags2.DepthStencilAttachmentReadBit | AccessFlags2.DepthStencilAttachmentWriteBit | AccessFlags2.AccelerationStructureReadBitKhr; - } - - if (barrierStages.HasFlag(BarrierStages.ComputeShading)) - { - stage |= PipelineStageFlags2.ComputeShaderBit | PipelineStageFlags2.DrawIndirectBit; - access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.IndirectCommandReadBit | AccessFlags2.AccelerationStructureReadBitKhr; - } - - if (barrierStages.HasFlag(BarrierStages.Copy)) - { - stage |= PipelineStageFlags2.CopyBit; - access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; - } - - if (barrierStages.HasFlag(BarrierStages.Resolve)) - { - stage |= PipelineStageFlags2.ResolveBit; - access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; - } - - if (barrierStages.HasFlag(BarrierStages.All)) + return storeOp switch { - stage = PipelineStageFlags2.AllCommandsBit; - access = AccessFlags2.MemoryReadBit | AccessFlags2.MemoryWriteBit; - } - - return (stage, access); + StoreOp.Store => AttachmentStoreOp.Store, + StoreOp.DontCare => AttachmentStoreOp.DontCare, + _ => default + }; } public static (PipelineStageFlags2 Stage, AccessFlags2 Access, ImageLayout Layout) Vulkan(TextureLayout textureLayout) @@ -584,49 +639,72 @@ public static (PipelineStageFlags2 Stage, AccessFlags2 Access, ImageLayout Layou }; } - internal static IndexType Vulkan(IndexFormat indexFormat) + public static (ImageType Type, ImageViewType ViewType) Vulkan(TextureType textureType) { - throw new NotImplementedException(); + return + ( + textureType switch + { + TextureType.Texture1D or + TextureType.Texture1DArray => ImageType.Type1D, + + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ImageType.Type2D, + + TextureType.Texture3D => ImageType.Type3D, + + _ => default + }, + textureType switch + { + TextureType.Texture1D => ImageViewType.Type1D, + TextureType.Texture1DArray => ImageViewType.Type1DArray, + TextureType.Texture2D => ImageViewType.Type2D, + TextureType.Texture2DArray => ImageViewType.Type2DArray, + TextureType.Texture3D => ImageViewType.Type3D, + TextureType.TextureCube => ImageViewType.TypeCube, + TextureType.TextureCubeArray => ImageViewType.TypeCubeArray, + _ => default + } + ); } - public static AttachmentLoadOp Vulkan(LoadOp loadOp) + public static ImageUsageFlags Vulkan(TextureUsages textureUsages) { - return loadOp switch + ImageUsageFlags result = default; + + if (textureUsages.HasFlag(TextureUsages.Sampled)) { - LoadOp.Load => AttachmentLoadOp.Load, - LoadOp.Clear => AttachmentLoadOp.Clear, - LoadOp.DontCare => AttachmentLoadOp.DontCare, - _ => default - }; - } + result |= ImageUsageFlags.SampledBit; + } - public static AttachmentStoreOp Vulkan(StoreOp storeOp) - { - return storeOp switch + if (textureUsages.HasFlag(TextureUsages.Storage)) { - StoreOp.Store => AttachmentStoreOp.Store, - StoreOp.DontCare => AttachmentStoreOp.DontCare, - _ => default - }; - } + result |= ImageUsageFlags.StorageBit; + } - internal static BuildAccelerationStructureFlagsKHR Vulkan(AccelerationStructureBuildFlags buildFlags) - { - throw new NotImplementedException(); - } + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) + { + result |= ImageUsageFlags.ColorAttachmentBit; + } - internal static TransformMatrixKHR Vulkan(Matrix4x4 transform) - { - throw new NotImplementedException(); - } + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) + { + result |= ImageUsageFlags.DepthStencilAttachmentBit; + } - internal static GeometryTypeKHR Vulkan(RayTracingGeometryType type) - { - throw new NotImplementedException(); - } + if (textureUsages.HasFlag(TextureUsages.TransferSrc)) + { + result |= ImageUsageFlags.TransferSrcBit; + } - internal static GeometryInstanceFlagsKHR Vulkan(RayTracingInstanceFlags flags) - { - throw new NotImplementedException(); + if (textureUsages.HasFlag(TextureUsages.TransferDst)) + { + result |= ImageUsageFlags.TransferDstBit; + } + + return result; } } From 4891cf21ecbc5e857556062e37198f651a66189c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 10:32:26 +0800 Subject: [PATCH 366/466] Refactor Surface handles, add Vulkan swapchain support Renamed Surface.NativeHandles to Handles and updated all usages. Updated DXSwapChain and MTLSwapChain to use the new Handles field. Implemented CreateSwapChainImpl in VKGraphicsContext to return a new VKSwapChain. Added a new VKSwapChain class with initial Vulkan swapchain and surface creation logic. Added a Vulkan() extension method for SwapChain in Extensions. --- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 2 +- sources/Zenith.NET.Metal/MTLSwapChain.cs | 2 +- sources/Zenith.NET.Vulkan/Extensions.cs | 4 + .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 2 +- sources/Zenith.NET.Vulkan/VKSwapChain.cs | 115 ++++++++++++++++++ sources/Zenith.NET/Structs/Surface.cs | 12 +- 6 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 sources/Zenith.NET.Vulkan/VKSwapChain.cs diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 5c0483e4..c023f988 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -78,7 +78,7 @@ private void CreateSwapChain() }; Context.Factory.CreateSwapChainForHwnd((IUnknown*)Context.GraphicsQueue.DirectX12().CommandQueue.Handle, - Desc.Surface.NativeHandles[0], + Desc.Surface.Handles[0], &swapChainDesc, default(SwapChainFullscreenDesc*), default(IDXGIOutput*), diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 06a69f31..a027efc5 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -90,7 +90,7 @@ protected override void Destroy() private void Initialize() { - MetalLayer = new(Desc.Surface.NativeHandles[0], NativeObjectOwnership.Borrowed) + MetalLayer = new(Desc.Surface.Handles[0], NativeObjectOwnership.Borrowed) { Device = Context.Device, PixelFormat = MTLFormats.Metal(Desc.Format).PixelFormat, diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 6f157ae4..2412d882 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -140,6 +140,10 @@ internal VKShader Vulkan() extension(SwapChain swapChain) { + internal VKSwapChain Vulkan() + { + return (VKSwapChain)swapChain; + } } extension(Texture texture) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 100c44ea..ef06174b 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -519,7 +519,7 @@ protected override void Initialize(bool useValidationLayer, protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) { - throw new NotImplementedException(); + return new VKSwapChain(this, desc); } protected override Heap CreateHeapImpl(HeapDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs new file mode 100644 index 00000000..0a040397 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -0,0 +1,115 @@ +using System.Diagnostics; +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKSwapChain : SwapChain +{ + private readonly VKTexture[] textures = []; + + public SurfaceKHR Surface; + + public SwapchainKHR Swapchain; + + private uint index; + + public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context, desc) + { + CreateSwapChain(); + CreateTextures(); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override Texture Drawable => textures[index]; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void PresentImpl() + { + } + + protected override void ResizeImpl() + { + DestroyTextures(); + DestroySwapChain(); + + CreateSwapChain(); + CreateTextures(); + } + + protected override void RefreshImpl() + { + DestroyTextures(); + DestroySwapChain(); + + CreateSwapChain(); + CreateTextures(); + } + + protected override void SetResourceName(string name) + { + } + + protected override void Destroy() + { + DestroyTextures(); + DestroySwapChain(); + } + + private void CreateSwapChain() + { + switch (Desc.Surface.Type) + { + case SurfaceType.Win32: + { + Win32SurfaceCreateInfoKHR createInfo = new() + { + SType = StructureType.Win32SurfaceCreateInfoKhr, + Hinstance = Process.GetCurrentProcess().Handle, + Hwnd = Desc.Surface.Handles[0] + }; + + Context.Win32Surface?.CreateWin32Surface(Context.Instance, &createInfo, default, out Surface).Success(); + } + break; + + case SurfaceType.Wayland: + { + WaylandSurfaceCreateInfoKHR createInfo = new() + { + SType = StructureType.WaylandSurfaceCreateInfoKhr, + Display = (nint*)Desc.Surface.Handles[0], + Surface = (nint*)Desc.Surface.Handles[1] + }; + + Context.WaylandSurface?.CreateWaylandSurface(Context.Instance, &createInfo, default, out Surface).Success(); + } + break; + + case SurfaceType.Xlib: + break; + + case SurfaceType.Android: + break; + + case SurfaceType.Apple: + break; + } + } + + private void DestroySwapChain() + { + } + + private void CreateTextures() + { + } + + private void DestroyTextures() + { + } +} diff --git a/sources/Zenith.NET/Structs/Surface.cs b/sources/Zenith.NET/Structs/Surface.cs index 5ec34028..f0623658 100644 --- a/sources/Zenith.NET/Structs/Surface.cs +++ b/sources/Zenith.NET/Structs/Surface.cs @@ -4,7 +4,7 @@ public struct Surface { public SurfaceType Type; - public nint[] NativeHandles; + public nint[] Handles; public uint Width; @@ -15,7 +15,7 @@ public static Surface Win32(nint hwnd, uint width, uint height) return new() { Type = SurfaceType.Win32, - NativeHandles = [hwnd], + Handles = [hwnd], Width = width, Height = height }; @@ -26,7 +26,7 @@ public static Surface Wayland(nint display, nint surface, uint width, uint heigh return new() { Type = SurfaceType.Wayland, - NativeHandles = [display, surface], + Handles = [display, surface], Width = width, Height = height }; @@ -37,7 +37,7 @@ public static Surface Xlib(nint display, nint window, uint width, uint height) return new() { Type = SurfaceType.Xlib, - NativeHandles = [display, window], + Handles = [display, window], Width = width, Height = height }; @@ -48,7 +48,7 @@ public static Surface Android(nint nativeWindow, uint width, uint height) return new() { Type = SurfaceType.Android, - NativeHandles = [nativeWindow], + Handles = [nativeWindow], Width = width, Height = height }; @@ -59,7 +59,7 @@ public static Surface Apple(nint layer, uint width, uint height) return new() { Type = SurfaceType.Apple, - NativeHandles = [layer], + Handles = [layer], Width = width, Height = height }; From 854c2a1d557b7788712a5eb9292039824d4146b0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 11:26:38 +0800 Subject: [PATCH 367/466] Support multiple Vulkan surfaces and improve swapchain logic Refactored VKSwapChain to handle Win32, Wayland, Xlib, Android, and Apple/Metal surfaces. Added detailed swapchain creation with capability and format selection. Implemented explicit destruction of swapchain and surface, and managed swapchain images as VKTexture objects. Improved resource management using ZenithMarshal.Scope and performed minor field refactoring. --- sources/Zenith.NET.Vulkan/VKSwapChain.cs | 158 ++++++++++++++++++++++- 1 file changed, 152 insertions(+), 6 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index 0a040397..3d42a7ad 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -5,12 +5,11 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKSwapChain : SwapChain { - private readonly VKTexture[] textures = []; - public SurfaceKHR Surface; public SwapchainKHR Swapchain; + private VKTexture[] textures = []; private uint index; public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context, desc) @@ -62,54 +61,201 @@ protected override void Destroy() private void CreateSwapChain() { + using ZenithMarshal.Scope scope = new(); + switch (Desc.Surface.Type) { case SurfaceType.Win32: { - Win32SurfaceCreateInfoKHR createInfo = new() + Win32SurfaceCreateInfoKHR surfaceCreateInfo = new() { SType = StructureType.Win32SurfaceCreateInfoKhr, Hinstance = Process.GetCurrentProcess().Handle, Hwnd = Desc.Surface.Handles[0] }; - Context.Win32Surface?.CreateWin32Surface(Context.Instance, &createInfo, default, out Surface).Success(); + Context.Win32Surface?.CreateWin32Surface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); } break; case SurfaceType.Wayland: { - WaylandSurfaceCreateInfoKHR createInfo = new() + WaylandSurfaceCreateInfoKHR surfaceCreateInfo = new() { SType = StructureType.WaylandSurfaceCreateInfoKhr, Display = (nint*)Desc.Surface.Handles[0], Surface = (nint*)Desc.Surface.Handles[1] }; - Context.WaylandSurface?.CreateWaylandSurface(Context.Instance, &createInfo, default, out Surface).Success(); + Context.WaylandSurface?.CreateWaylandSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); } break; case SurfaceType.Xlib: + { + XlibSurfaceCreateInfoKHR surfaceCreateInfo = new() + { + SType = StructureType.XlibSurfaceCreateInfoKhr, + Dpy = (nint*)Desc.Surface.Handles[0], + Window = Desc.Surface.Handles[1] + }; + + Context.XlibSurface?.CreateXlibSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); + } break; case SurfaceType.Android: + { + AndroidSurfaceCreateInfoKHR surfaceCreateInfo = new() + { + SType = StructureType.AndroidSurfaceCreateInfoKhr, + Window = (nint*)Desc.Surface.Handles[0] + }; + + Context.AndroidSurface?.CreateAndroidSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); + } break; case SurfaceType.Apple: + { + MetalSurfaceCreateInfoEXT surfaceCreateInfo = new() + { + SType = StructureType.MetalSurfaceCreateInfoExt, + PLayer = (nint*)Desc.Surface.Handles[0] + }; + + Context.MetalSurface?.CreateMetalSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); + } break; } + + SurfaceCapabilitiesKHR capabilities = default; + Context.Surface?.GetPhysicalDeviceSurfaceCapabilities(Context.PhysicalDevice, Surface, &capabilities).Success(); + + uint surfaceFormatCount = 0; + Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, default).Success(); + + SurfaceFormatKHR* surfaceFormats = (SurfaceFormatKHR*)ZenithMarshal.Allocate(scope, surfaceFormatCount); + Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, surfaceFormats).Success(); + + uint presentModeCount = 0; + Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, null).Success(); + + PresentModeKHR* presentModes = (PresentModeKHR*)ZenithMarshal.Allocate(scope, presentModeCount); + Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, presentModes).Success(); + + uint minImageCount = capabilities.MinImageCount + 1; + if (capabilities.MaxImageCount > 0 && minImageCount > capabilities.MaxImageCount) + { + minImageCount = capabilities.MaxImageCount; + } + + SurfaceFormatKHR surfaceFormat = default; + foreach (SurfaceFormatKHR item in new ReadOnlySpan(surfaceFormats, (int)surfaceFormatCount)) + { + if (item.Format == VKFormats.Vulkan(Desc.Format).Format) + { + surfaceFormat = item; + + if (item.ColorSpace is ColorSpaceKHR.SpaceSrgbNonlinearKhr) + { + break; + } + } + } + + SurfaceTransformFlagsKHR preTransform = SurfaceTransformFlagsKHR.InheritBitKhr; + if (capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.IdentityBitKhr)) + { + preTransform = SurfaceTransformFlagsKHR.IdentityBitKhr; + } + + CompositeAlphaFlagsKHR compositeAlpha = CompositeAlphaFlagsKHR.InheritBitKhr; + if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr)) + { + compositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr; + } + + PresentModeKHR presentMode = PresentModeKHR.FifoKhr; + foreach (PresentModeKHR item in new ReadOnlySpan(presentModes, (int)presentModeCount)) + { + if (item is PresentModeKHR.MailboxKhr) + { + presentMode = PresentModeKHR.MailboxKhr; + + break; + } + } + + SwapchainCreateInfoKHR createInfo = new() + { + SType = StructureType.SwapchainCreateInfoKhr, + Surface = Surface, + MinImageCount = minImageCount, + ImageFormat = surfaceFormat.Format, + ImageColorSpace = surfaceFormat.ColorSpace, + ImageExtent = new() + { + Width = uint.Clamp(capabilities.MinImageExtent.Width, Desc.Surface.Width, capabilities.MaxImageExtent.Width), + Height = uint.Clamp(capabilities.MinImageExtent.Height, Desc.Surface.Height, capabilities.MaxImageExtent.Height) + }, + ImageArrayLayers = 1, + ImageUsage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.ColorAttachmentBit, + ImageSharingMode = Context.QueueFamilies.SharingMode, + QueueFamilyIndexCount = Context.QueueFamilies.IndexCount, + PQueueFamilyIndices = Context.QueueFamilies.Indices, + PreTransform = preTransform, + CompositeAlpha = compositeAlpha, + PresentMode = presentMode, + Clipped = true + }; + + Context.Swapchain?.CreateSwapchain(Context.Device, &createInfo, default, out Swapchain).Success(); } private void DestroySwapChain() { + Context.Swapchain?.DestroySwapchain(Context.Device, Swapchain, default); + Context.Surface?.DestroySurface(Context.Instance, Surface, default); + + index = 0; } private void CreateTextures() { + using ZenithMarshal.Scope scope = new(); + + uint swapchainImageCount = 0; + Context.Swapchain?.GetSwapchainImages(Context.Device, Swapchain, &swapchainImageCount, default).Success(); + + Image* swapchainImages = (Image*)ZenithMarshal.Allocate(scope, swapchainImageCount); + Context.Swapchain?.GetSwapchainImages(Context.Device, Swapchain, &swapchainImageCount, swapchainImages).Success(); + + TextureDesc desc = new() + { + Type = TextureType.Texture2D, + Format = Desc.Format, + Width = Desc.Surface.Width, + Height = Desc.Surface.Height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferDst + }; + + textures = new VKTexture[swapchainImageCount]; + for (uint i = 0; i < swapchainImageCount; i++) + { + textures[i] = new(Context, desc, swapchainImages[i], new(default, 0, false)); + } } private void DestroyTextures() { + for (int i = 0; i < textures.Length; i++) + { + textures[i].Dispose(); + } } } From d61ca955d97acc4e9b5fd2e189e65cfa0ab487ad Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 15:15:38 +0800 Subject: [PATCH 368/466] Switch Windows to Vulkan graphics context Replaced DirectX12 with Vulkan for Windows platform initialization. Removed the Zenith.NET.DirectX12 import and updated context creation logic. macOS and Linux context handling remains unchanged. --- sources/Experiments/CornellBox/App.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 54b7ef4e..5b21cd6d 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -6,7 +6,6 @@ using Silk.NET.Input; using Silk.NET.Windowing; using Zenith.NET; -using Zenith.NET.DirectX12; using Zenith.NET.Metal; using Zenith.NET.Vulkan; @@ -34,7 +33,7 @@ static App() if (OperatingSystem.IsWindows()) { - Context = GraphicsContext.CreateDirectX12(useValidationLayer: true); + Context = GraphicsContext.CreateVulkan(useValidationLayer: true); } else if (OperatingSystem.IsMacOS()) { From e21f010f60adbea21d7cf4167990e5ce5a65197a Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 15:40:01 +0800 Subject: [PATCH 369/466] Update buffer usages and sync barriers for Vulkan RT - Add TransferDst usage to material buffer for transfer operations in PathTracingRenderer. - Use AllCommandsBit for post-build sync barriers in VKTopLevelAccelerationStructure to ensure broader synchronization. --- .../Experiments/CornellBox/Renderers/PathTracingRenderer.cs | 2 +- sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index 9a7a1bbb..8e64ff26 100644 --- a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs @@ -121,7 +121,7 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Material) * materials.Length), StrideInBytes = (uint)sizeof(Material), - Usages = BufferUsages.StorageReadOnly, + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, Residency = MemoryResidency.GpuOnly }); diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index 6d83edaa..cbd0eb80 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -51,7 +51,7 @@ public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffe BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); - BuildSyncBarrier(commandBuffer, PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AllCommandsBit); AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() { @@ -95,7 +95,7 @@ public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureD BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); - BuildSyncBarrier(commandBuffer, PipelineStageFlags2.FragmentShaderBit | PipelineStageFlags2.ComputeShaderBit); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AllCommandsBit); } public override nint GetNativeObject(NativeObjectType type) From e0bfdf686f95c08a7b1f9dde51d0c92b479238a8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Tue, 7 Jul 2026 17:20:18 +0800 Subject: [PATCH 370/466] Rename WaitImpl to InsertWaitsImpl for clarity Refactor command queue wait handling by renaming WaitImpl and related methods to InsertWaitsImpl/InsertWaits. Updates all usages and overrides to clarify intent of inserting waits on command submissions rather than performing immediate waits. --- sources/Zenith.NET.DirectX12/DXCommandQueue.cs | 2 +- sources/Zenith.NET.Metal/MTLCommandQueue.cs | 2 +- sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 2 +- sources/Zenith.NET/CommandBuffer.cs | 2 +- sources/Zenith.NET/CommandQueue.cs | 6 +++--- sources/Zenith.NET/SwapChain.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index af53d0d7..a6a03f91 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -54,7 +54,7 @@ protected override void WaitImpl(ulong waitValue) @event.Reset(); } - protected override void WaitImpl(ReadOnlySpan submissions) + protected override void InsertWaitsImpl(ReadOnlySpan submissions) { foreach (CommandSubmission submission in submissions) { diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index 9acd0ca0..48a1ac65 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -48,7 +48,7 @@ protected override void WaitImpl(ulong waitValue) Event.Wait(waitValue, ulong.MaxValue); } - protected override void WaitImpl(ReadOnlySpan submissions) + protected override void InsertWaitsImpl(ReadOnlySpan submissions) { foreach (CommandSubmission submission in submissions) { diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 68741bfa..9f9e9b70 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -96,7 +96,7 @@ protected override void WaitImpl(ulong waitValue) } } - protected override void WaitImpl(ReadOnlySpan submissions) + protected override void InsertWaitsImpl(ReadOnlySpan submissions) { foreach (CommandSubmission submission in submissions) { diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index 3dd2c689..c92614bf 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -10,7 +10,7 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) public CommandSubmission Submit(params ReadOnlySpan submissions) { - queue.Wait(submissions); + queue.InsertWaits(submissions); queue.Submit(this); return queue.Signal(); diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 5920e34a..ed8ea607 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -64,11 +64,11 @@ internal void Wait(ulong value) } } - internal void Wait(ReadOnlySpan submissions) + internal void InsertWaits(ReadOnlySpan submissions) { using Lock.Scope _ = @lock.EnterScope(); - WaitImpl(submissions); + InsertWaitsImpl(submissions); } protected abstract ulong GetCompletedValue(); @@ -81,7 +81,7 @@ internal void Wait(ReadOnlySpan submissions) protected abstract void WaitImpl(ulong waitValue); - protected abstract void WaitImpl(ReadOnlySpan submissions); + protected abstract void InsertWaitsImpl(ReadOnlySpan submissions); protected override void Destroy() { diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 47389bc0..47b9e3fc 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -10,7 +10,7 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public CommandSubmission Present(params ReadOnlySpan submissions) { - Context.GraphicsQueue.Wait(submissions); + Context.GraphicsQueue.InsertWaits(submissions); PresentImpl(); From 0f5418b4994547fa6a5d7ff544d47ba93eb536d7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Jul 2026 20:21:50 +0800 Subject: [PATCH 371/466] Refactor to Timeline-based GPU synchronization Replace CommandSubmission/fence logic with Timeline and TimelineValue abstractions for command submission and synchronization. Move fence/event handling to DXTimeline. Update CommandBuffer, CommandQueue, and SwapChain interfaces to use TimelineValue. Improve command buffer reuse and synchronization clarity. Add Timeline extension for DXTimeline access. --- sources/Experiments/CornellBox/App.cs | 12 +-- .../Zenith.NET.DirectX12/DXCommandQueue.cs | 48 +++--------- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 4 +- sources/Zenith.NET.DirectX12/DXTimeline.cs | 50 +++++++++++++ sources/Zenith.NET.DirectX12/Extensions.cs | 8 ++ sources/Zenith.NET/CommandBuffer.cs | 7 +- sources/Zenith.NET/CommandQueue.cs | 74 ++++++------------- .../Zenith.NET/Structs/CommandSubmission.cs | 13 ---- sources/Zenith.NET/Structs/TimelineValue.cs | 15 ++++ sources/Zenith.NET/SwapChain.cs | 11 +-- sources/Zenith.NET/Timeline.cs | 33 +++++++++ 11 files changed, 145 insertions(+), 130 deletions(-) create mode 100644 sources/Zenith.NET.DirectX12/DXTimeline.cs delete mode 100644 sources/Zenith.NET/Structs/CommandSubmission.cs create mode 100644 sources/Zenith.NET/Structs/TimelineValue.cs create mode 100644 sources/Zenith.NET/Timeline.cs diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 5b21cd6d..6288f577 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -113,8 +113,6 @@ static App() public static void Run() { - CommandSubmission lastSubmission = default; - window.Update += delta => { if (Width is 0 || Height is 0) @@ -178,8 +176,6 @@ public static void Run() return; } - lastSubmission.Wait(); - CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); activeRenderer.Update(camera); @@ -191,7 +187,9 @@ public static void Run() commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Present); - lastSubmission = swapChain.Present(commandBuffer.Submit()); + commandBuffer.Submit().Wait(); + + swapChain.Present(); }; window.Resize += _ => @@ -201,8 +199,6 @@ public static void Run() return; } - lastSubmission.Wait(); - pathTracer?.Resize(Width, Height); rasterizer.Resize(Width, Height); swapChain.Resize(Width, Height); @@ -210,8 +206,6 @@ public static void Run() window.Run(); - lastSubmission.Wait(); - pathTracer?.Dispose(); rasterizer.Dispose(); imGui.Dispose(); diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index a6a03f91..3704cc75 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -5,66 +5,39 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXCommandQueue : CommandQueue { - private readonly ManualResetEvent @event = new(false); - public ComPtr CommandQueue; - public ComPtr Fence; - public DXCommandQueue(DXGraphicsContext context, CommandQueueType type) : base(context, type) { CommandQueueDesc commandQueueDesc = new() { Type = DXFormats.DirectX12(type) }; context.Device.CreateCommandQueue(&commandQueueDesc, SilkMarshal.GuidPtrOf(), (void**)CommandQueue.GetAddressOf()).Success(); - context.Device.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence.GetAddressOf()).Success(); + + Timeline = new DXTimeline(context, this); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + public override Timeline Timeline { get; } + public override nint GetNativeObject(NativeObjectType type) { return 0; } - protected override ulong GetCompletedValue() - { - return Fence.GetCompletedValue(); - } - protected override CommandBuffer CreateCommandBuffer() { return new DXCommandBuffer(Context, this); } - protected override void SignalImpl(ulong signalValue) - { - CommandQueue.Signal(Fence, signalValue).Success(); - } - - protected override void SubmitImpl(CommandBuffer commandBuffer) - { - CommandQueue.ExecuteCommandLists(1, (ID3D12CommandList**)commandBuffer.DirectX12().CommandList.GetAddressOf()); - } - - protected override void WaitImpl(ulong waitValue) + protected override void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer) { - Fence.SetEventOnCompletion(waitValue, (void*)@event.SafeWaitHandle.DangerousGetHandle()).Success(); - - @event.WaitOne(); - @event.Reset(); - } - - protected override void InsertWaitsImpl(ReadOnlySpan submissions) - { - foreach (CommandSubmission submission in submissions) + foreach (TimelineValue wait in waits) { - if (submission.Queue is null) - { - continue; - } - - CommandQueue.Wait(submission.Queue.DirectX12().Fence, submission.Value).Success(); + CommandQueue.Wait(wait.Timeline.DirectX12().Fence, wait.Value).Success(); } + + CommandQueue.ExecuteCommandLists(1, (ID3D12CommandList**)commandBuffer.DirectX12().CommandList.GetAddressOf()); } protected override void SetResourceName(string name) @@ -76,9 +49,6 @@ protected override void Destroy() { base.Destroy(); - Fence.Dispose(); CommandQueue.Dispose(); - - @event.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index c023f988..c6f2cfd6 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -27,10 +27,12 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - protected override void PresentImpl() + public override void Present() { SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); + Context.GraphicsQueue.Timeline.Signal().Wait(); + index = SwapChain.GetCurrentBackBufferIndex(); } diff --git a/sources/Zenith.NET.DirectX12/DXTimeline.cs b/sources/Zenith.NET.DirectX12/DXTimeline.cs new file mode 100644 index 00000000..8388dcea --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXTimeline.cs @@ -0,0 +1,50 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXTimeline : Timeline +{ + private readonly ManualResetEvent @event = new(false); + + public ComPtr Fence; + + public DXTimeline(DXGraphicsContext context, CommandQueue queue) : base(context, queue) + { + context.Device.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence.GetAddressOf()).Success(); + } + + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + + public override ulong CompletedValue => Fence.GetCompletedValue(); + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SignalImpl(CommandQueue queue, ulong value) + { + queue.DirectX12().CommandQueue.Signal(Fence, value).Success(); + } + + protected override void WaitImpl(ulong value) + { + Fence.SetEventOnCompletion(value, (void*)@event.SafeWaitHandle.DangerousGetHandle()).Success(); + + @event.WaitOne(); + @event.Reset(); + } + + protected override void SetResourceName(string name) + { + Fence.SetName(name).Success(); + } + + protected override void Destroy() + { + Fence.Dispose(); + + @event.Dispose(); + } +} diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index 2c739cb2..0f15535d 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -68,6 +68,14 @@ internal DXCommandQueue DirectX12() } } + extension(Timeline timeline) + { + internal DXTimeline DirectX12() + { + return (DXTimeline)timeline; + } + } + extension(ComputePipeline computePipeline) { internal DXComputePipeline DirectX12() diff --git a/sources/Zenith.NET/CommandBuffer.cs b/sources/Zenith.NET/CommandBuffer.cs index c92614bf..ca55ad51 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -8,12 +8,9 @@ public abstract class CommandBuffer(GraphicsContext context, CommandQueue queue) public CommandQueue Queue => queue; - public CommandSubmission Submit(params ReadOnlySpan submissions) + public TimelineValue Submit(params ReadOnlySpan waits) { - queue.InsertWaits(submissions); - queue.Submit(this); - - return queue.Signal(); + return queue.Submit(waits, this); } public void Barrier(BarrierStages before, BarrierStages after) diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index ed8ea607..84a6f7c4 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -6,85 +6,53 @@ public abstract class CommandQueue(GraphicsContext context, CommandQueueType typ private readonly Queue commandBuffers = []; private readonly Queue submitteds = []; - private ulong value; - public CommandQueueType Type { get; } = type; + public abstract Timeline Timeline { get; } + public CommandBuffer CommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); - CommandBuffer commandBuffer = commandBuffers.Count is 0 ? CreateCommandBuffer() : commandBuffers.Dequeue(); + while (submitteds.TryPeek(out Submitted submitted) && submitted.TimelineValue.IsCompleted) + { + submitteds.Dequeue(); - commandBuffer.Begin(); + submitted.CommandBuffer.Reset(); - return commandBuffer; - } + commandBuffers.Enqueue(submitted.CommandBuffer); + } - internal CommandSubmission Signal() - { - using Lock.Scope _ = @lock.EnterScope(); + CommandBuffer commandBuffer = commandBuffers.Count is 0 ? CreateCommandBuffer() : commandBuffers.Dequeue(); - SignalImpl(++value); + commandBuffer.Begin(); - return new(this, value); + return commandBuffer; } - internal void Submit(CommandBuffer commandBuffer) + internal TimelineValue Submit(ReadOnlySpan waits, CommandBuffer commandBuffer) { using Lock.Scope _ = @lock.EnterScope(); commandBuffer.End(); - SubmitImpl(commandBuffer); + SubmitImpl(waits, commandBuffer); - submitteds.Enqueue(new(commandBuffer, value)); - } + TimelineValue timelineValue = Timeline.Signal(); - internal void Wait(ulong value) - { - using Lock.Scope _ = @lock.EnterScope(); + submitteds.Enqueue(new(commandBuffer, timelineValue)); - ulong completedValue = GetCompletedValue(); - - if (completedValue < value) - { - WaitImpl(value); - - completedValue = GetCompletedValue(); - } - - while (submitteds.TryPeek(out Submitted submitted) && submitted.Value <= completedValue) - { - submitteds.Dequeue(); - - submitted.CommandBuffer.Reset(); - - commandBuffers.Enqueue(submitted.CommandBuffer); - } + return timelineValue; } - internal void InsertWaits(ReadOnlySpan submissions) - { - using Lock.Scope _ = @lock.EnterScope(); - - InsertWaitsImpl(submissions); - } - - protected abstract ulong GetCompletedValue(); - protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract void SignalImpl(ulong signalValue); - - protected abstract void SubmitImpl(CommandBuffer commandBuffer); - - protected abstract void WaitImpl(ulong waitValue); - - protected abstract void InsertWaitsImpl(ReadOnlySpan submissions); + protected abstract void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer); protected override void Destroy() { + Timeline.Dispose(); + while (commandBuffers.TryDequeue(out CommandBuffer? commandBuffer)) { commandBuffer.Dispose(); @@ -96,10 +64,10 @@ protected override void Destroy() } } - private readonly struct Submitted(CommandBuffer commandBuffer, ulong value) + private readonly struct Submitted(CommandBuffer commandBuffer, TimelineValue timelineValue) { public readonly CommandBuffer CommandBuffer = commandBuffer; - public readonly ulong Value = value; + public readonly TimelineValue TimelineValue = timelineValue; } } diff --git a/sources/Zenith.NET/Structs/CommandSubmission.cs b/sources/Zenith.NET/Structs/CommandSubmission.cs deleted file mode 100644 index e2b60e27..00000000 --- a/sources/Zenith.NET/Structs/CommandSubmission.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Zenith.NET; - -public readonly struct CommandSubmission(CommandQueue? queue, ulong value) -{ - public readonly CommandQueue? Queue = queue; - - public readonly ulong Value = value; - - public void Wait() - { - Queue?.Wait(Value); - } -} diff --git a/sources/Zenith.NET/Structs/TimelineValue.cs b/sources/Zenith.NET/Structs/TimelineValue.cs new file mode 100644 index 00000000..b7457f29 --- /dev/null +++ b/sources/Zenith.NET/Structs/TimelineValue.cs @@ -0,0 +1,15 @@ +namespace Zenith.NET; + +public readonly struct TimelineValue(Timeline timeline, ulong value) +{ + public readonly Timeline Timeline = timeline; + + public readonly ulong Value = value; + + public bool IsCompleted => Timeline.CompletedValue >= Value; + + public void Wait() + { + Timeline.Wait(Value); + } +} diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 47b9e3fc..54e4f254 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -8,14 +8,7 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture Drawable { get; } - public CommandSubmission Present(params ReadOnlySpan submissions) - { - Context.GraphicsQueue.InsertWaits(submissions); - - PresentImpl(); - - return Context.GraphicsQueue.Signal(); - } + public abstract void Present(); public void Resize(uint width, uint height) { @@ -36,8 +29,6 @@ public void Refresh(Surface surface) SetResourceName(Name); } - protected abstract void PresentImpl(); - protected abstract void ResizeImpl(); protected abstract void RefreshImpl(); diff --git a/sources/Zenith.NET/Timeline.cs b/sources/Zenith.NET/Timeline.cs new file mode 100644 index 00000000..fd277ba7 --- /dev/null +++ b/sources/Zenith.NET/Timeline.cs @@ -0,0 +1,33 @@ +namespace Zenith.NET; + +public abstract class Timeline(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + private readonly Lock @lock = new(); + + private ulong nextValue; + + public abstract ulong CompletedValue { get; } + + public TimelineValue Signal() + { + using Lock.Scope _ = @lock.EnterScope(); + + SignalImpl(queue, ++nextValue); + + return new(this, nextValue); + } + + public void Wait(ulong value) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (CompletedValue < value) + { + WaitImpl(value); + } + } + + protected abstract void SignalImpl(CommandQueue queue, ulong value); + + protected abstract void WaitImpl(ulong value); +} From b96f4b779619a9db7a90ded73fca0bf3ec2712a7 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Jul 2026 20:59:36 +0800 Subject: [PATCH 372/466] Refactor timeline sync for Metal and Vulkan backends Introduce MTLTimeline and VKTimeline classes to encapsulate platform-specific timeline semaphore/event logic. Update CommandQueue classes to own Timeline instances and refactor submission and synchronization to use TimelineValue waits for improved cross-queue sync. Add VKFence for Vulkan swapchain image acquisition. Simplify Timeline base class and add extension methods for backend-specific casting. Ensure proper disposal of synchronization resources. --- sources/Zenith.NET.DirectX12/DXTimeline.cs | 8 +- sources/Zenith.NET.Metal/Extensions.cs | 8 ++ sources/Zenith.NET.Metal/MTLCommandQueue.cs | 43 ++------- sources/Zenith.NET.Metal/MTLSwapChain.cs | 6 +- sources/Zenith.NET.Metal/MTLTimeline.cs | 35 ++++++++ sources/Zenith.NET.Vulkan/Extensions.cs | 8 ++ sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 96 ++++----------------- sources/Zenith.NET.Vulkan/VKFence.cs | 30 +++++++ sources/Zenith.NET.Vulkan/VKSwapChain.cs | 37 +++++++- sources/Zenith.NET.Vulkan/VKTimeline.cs | 92 ++++++++++++++++++++ sources/Zenith.NET/Timeline.cs | 8 +- 11 files changed, 251 insertions(+), 120 deletions(-) create mode 100644 sources/Zenith.NET.Metal/MTLTimeline.cs create mode 100644 sources/Zenith.NET.Vulkan/VKFence.cs create mode 100644 sources/Zenith.NET.Vulkan/VKTimeline.cs diff --git a/sources/Zenith.NET.DirectX12/DXTimeline.cs b/sources/Zenith.NET.DirectX12/DXTimeline.cs index 8388dcea..62113adf 100644 --- a/sources/Zenith.NET.DirectX12/DXTimeline.cs +++ b/sources/Zenith.NET.DirectX12/DXTimeline.cs @@ -9,13 +9,15 @@ internal unsafe class DXTimeline : Timeline public ComPtr Fence; - public DXTimeline(DXGraphicsContext context, CommandQueue queue) : base(context, queue) + public DXTimeline(DXGraphicsContext context, DXCommandQueue queue) : base(context, queue) { context.Device.CreateFence(0, FenceFlags.None, SilkMarshal.GuidPtrOf(), (void**)Fence.GetAddressOf()).Success(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + public new DXCommandQueue Queue => (DXCommandQueue)base.Queue; + public override ulong CompletedValue => Fence.GetCompletedValue(); public override nint GetNativeObject(NativeObjectType type) @@ -23,9 +25,9 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - protected override void SignalImpl(CommandQueue queue, ulong value) + protected override void SignalImpl(ulong value) { - queue.DirectX12().CommandQueue.Signal(Fence, value).Success(); + Queue.CommandQueue.Signal(Fence, value).Success(); } protected override void WaitImpl(ulong value) diff --git a/sources/Zenith.NET.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 30678c07..9c8a34ae 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -74,6 +74,14 @@ internal MTLCommandQueue Metal() } } + extension(Timeline timeline) + { + internal MTLTimeline Metal() + { + return (MTLTimeline)timeline; + } + } + extension(ComputePipeline computePipeline) { internal MTLComputePipeline Metal() diff --git a/sources/Zenith.NET.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index 48a1ac65..cb379a6f 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -6,59 +6,36 @@ internal class MTLCommandQueue : CommandQueue { public MTL4CommandQueue CommandQueue; - public MTLSharedEvent Event; - public MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type) : base(context, type) { CommandQueue = context.Device.MakeMTL4CommandQueue(); - Event = context.Device.MakeSharedEvent(); - CommandQueue.AddResidencySet(context.ResidencySet); + + Timeline = new MTLTimeline(context, this); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + public override Timeline Timeline { get; } + public override nint GetNativeObject(NativeObjectType type) { return 0; } - protected override ulong GetCompletedValue() - { - return Event.SignaledValue; - } - protected override CommandBuffer CreateCommandBuffer() { return new MTLCommandBuffer(Context, this); } - protected override void SignalImpl(ulong signalValue) + protected override void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer) { - CommandQueue.SignalEvent(Event, signalValue); - } - - protected override void SubmitImpl(CommandBuffer commandBuffer) - { - CommandQueue.Commit([commandBuffer.Metal().CommandBuffer]); - } - - protected override void WaitImpl(ulong waitValue) - { - Event.Wait(waitValue, ulong.MaxValue); - } - - protected override void InsertWaitsImpl(ReadOnlySpan submissions) - { - foreach (CommandSubmission submission in submissions) + foreach (TimelineValue wait in waits) { - if (submission.Queue is null) - { - continue; - } - - CommandQueue.WaitForEvent(submission.Queue.Metal().Event, submission.Value); + CommandQueue.WaitForEvent(wait.Timeline.Metal().Event, wait.Value); } + + CommandQueue.Commit([commandBuffer.Metal().CommandBuffer]); } protected override void SetResourceName(string name) @@ -70,8 +47,6 @@ protected override void Destroy() base.Destroy(); CommandQueue.RemoveResidencySet(Context.ResidencySet); - - Event.Dispose(); CommandQueue.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index a027efc5..7974ffd3 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -49,10 +49,14 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - protected override void PresentImpl() + public override void Present() { + Context.GraphicsQueue.Metal().CommandQueue.SignalDrawable(MetalDrawable); + MetalDrawable.Present(); + Context.GraphicsQueue.Timeline.Signal().Wait(); + MetalDrawable.Dispose(); MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); } diff --git a/sources/Zenith.NET.Metal/MTLTimeline.cs b/sources/Zenith.NET.Metal/MTLTimeline.cs new file mode 100644 index 00000000..94cedf24 --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLTimeline.cs @@ -0,0 +1,35 @@ +using Metal.NET; + +namespace Zenith.NET.Metal; + +internal class MTLTimeline(MTLGraphicsContext context, MTLCommandQueue queue) : Timeline(context, queue) +{ + public MTLSharedEvent Event = context.Device.MakeSharedEvent(); + + public override ulong CompletedValue => Event.SignaledValue; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SignalImpl(ulong value) + { + queue.CommandQueue.SignalEvent(Event, value); + } + + protected override void WaitImpl(ulong value) + { + Event.Wait(value, ulong.MaxValue); + } + + protected override void SetResourceName(string name) + { + Event.Label = name; + } + + protected override void Destroy() + { + Event.Dispose(); + } +} diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index 2412d882..1b537e1e 100644 --- a/sources/Zenith.NET.Vulkan/Extensions.cs +++ b/sources/Zenith.NET.Vulkan/Extensions.cs @@ -82,6 +82,14 @@ internal VKCommandQueue Vulkan() } } + extension(Timeline timeline) + { + internal VKTimeline Vulkan() + { + return (VKTimeline)timeline; + } + } + extension(ComputePipeline computePipeline) { internal VKComputePipeline Vulkan() diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 9f9e9b70..6cd21dfb 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -8,62 +8,44 @@ internal unsafe class VKCommandQueue : CommandQueue public uint QueueFamilyIndex; - public VkSemaphore Semaphore; - public VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : base(context, type) { Queue = queue; QueueFamilyIndex = queueFamilyIndex; - SemaphoreCreateInfo createInfo = new() { SType = StructureType.SemaphoreCreateInfo }; - createInfo.AddNext(out SemaphoreTypeCreateInfo typeCreateInfo); - typeCreateInfo.SemaphoreType = SemaphoreType.Timeline; - - context.Vk.CreateSemaphore(context.Device, &createInfo, default, out Semaphore).Success(); + Timeline = new VKTimeline(context, this); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + public override Timeline Timeline { get; } + public override nint GetNativeObject(NativeObjectType type) { return 0; } - protected override ulong GetCompletedValue() - { - ulong value; - Context.Vk.GetSemaphoreCounterValue(Context.Device, Semaphore, &value).Success(); - - return value; - } - protected override CommandBuffer CreateCommandBuffer() { return new VKCommandBuffer(Context, this); } - protected override void SignalImpl(ulong signalValue) + protected override void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer) { - SemaphoreSubmitInfo signalSemaphoreInfo = new() - { - SType = StructureType.SemaphoreSubmitInfo, - Semaphore = Semaphore, - Value = signalValue, - StageMask = PipelineStageFlags2.AllCommandsBit - }; - - SubmitInfo2 submitInfo = new() + SemaphoreSubmitInfo* waitSemaphoreInfos = stackalloc SemaphoreSubmitInfo[waits.Length]; + for (int i = 0; i < waits.Length; i++) { - SType = StructureType.SubmitInfo2, - SignalSemaphoreInfoCount = 1, - PSignalSemaphoreInfos = &signalSemaphoreInfo - }; + TimelineValue wait = waits[i]; - Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); - } + waitSemaphoreInfos[i] = new() + { + SType = StructureType.SemaphoreSubmitInfo, + Semaphore = wait.Timeline.Vulkan().Semaphore, + Value = wait.Value, + StageMask = PipelineStageFlags2.AllCommandsBit + }; + } - protected override void SubmitImpl(CommandBuffer commandBuffer) - { CommandBufferSubmitInfo commandBufferInfo = new() { SType = StructureType.CommandBufferSubmitInfo, @@ -73,6 +55,8 @@ protected override void SubmitImpl(CommandBuffer commandBuffer) SubmitInfo2 submitInfo = new() { SType = StructureType.SubmitInfo2, + WaitSemaphoreInfoCount = (uint)waits.Length, + PWaitSemaphoreInfos = waitSemaphoreInfos, CommandBufferInfoCount = 1, PCommandBufferInfos = &commandBufferInfo }; @@ -80,50 +64,6 @@ protected override void SubmitImpl(CommandBuffer commandBuffer) Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); } - protected override void WaitImpl(ulong waitValue) - { - fixed (VkSemaphore* pSemaphores = &Semaphore) - { - SemaphoreWaitInfo waitInfo = new() - { - SType = StructureType.SemaphoreWaitInfo, - SemaphoreCount = 1, - PSemaphores = pSemaphores, - PValues = &waitValue - }; - - Context.Vk.WaitSemaphores(Context.Device, &waitInfo, ulong.MaxValue).Success(); - } - } - - protected override void InsertWaitsImpl(ReadOnlySpan submissions) - { - foreach (CommandSubmission submission in submissions) - { - if (submission.Queue is null) - { - continue; - } - - SemaphoreSubmitInfo waitSemaphoreInfo = new() - { - SType = StructureType.SemaphoreSubmitInfo, - Semaphore = submission.Queue.Vulkan().Semaphore, - Value = submission.Value, - StageMask = PipelineStageFlags2.AllCommandsBit - }; - - SubmitInfo2 submitInfo = new() - { - SType = StructureType.SubmitInfo2, - WaitSemaphoreInfoCount = 1, - PWaitSemaphoreInfos = &waitSemaphoreInfo - }; - - Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); - } - } - protected override void SetResourceName(string name) { using ZenithMarshal.Scope scope = new(); @@ -143,6 +83,6 @@ protected override void Destroy() { base.Destroy(); - Context.Vk.DestroySemaphore(Context.Device, Semaphore, default); + Timeline.Dispose(); } } diff --git a/sources/Zenith.NET.Vulkan/VKFence.cs b/sources/Zenith.NET.Vulkan/VKFence.cs new file mode 100644 index 00000000..33eb87bd --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKFence.cs @@ -0,0 +1,30 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKFence : DisposableObject +{ + public Fence Fence; + + public VKFence(VKGraphicsContext context) + { + FenceCreateInfo createInfo = new() { SType = StructureType.FenceCreateInfo }; + + context.Vk.CreateFence(context.Device, &createInfo, default, out Fence).Success(); + + Context = context; + } + + public VKGraphicsContext Context { get; } + + public void Wait() + { + Context.Vk.WaitForFences(Context.Device, 1, ref Fence, true, ulong.MaxValue).Success(); + Context.Vk.ResetFences(Context.Device, 1, ref Fence).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroyFence(Context.Device, Fence, default); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index 3d42a7ad..649bdc80 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -5,6 +5,8 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKSwapChain : SwapChain { + private readonly VKFence fence; + public SurfaceKHR Surface; public SwapchainKHR Swapchain; @@ -14,8 +16,11 @@ internal unsafe class VKSwapChain : SwapChain public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context, desc) { + fence = new(context); + CreateSwapChain(); CreateTextures(); + AcquireNextImage(); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; @@ -27,8 +32,25 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - protected override void PresentImpl() + public override void Present() { + fixed (SwapchainKHR* swapchains = &Swapchain) + { + fixed (uint* imageIndices = &index) + { + PresentInfoKHR presentInfo = new() + { + SType = StructureType.PresentInfoKhr, + SwapchainCount = 1, + PSwapchains = swapchains, + PImageIndices = imageIndices + }; + + Context.Swapchain?.QueuePresent(Context.GraphicsQueue.Vulkan().Queue, &presentInfo).Success(); + } + } + + AcquireNextImage(); } protected override void ResizeImpl() @@ -38,6 +60,8 @@ protected override void ResizeImpl() CreateSwapChain(); CreateTextures(); + + AcquireNextImage(); } protected override void RefreshImpl() @@ -47,6 +71,8 @@ protected override void RefreshImpl() CreateSwapChain(); CreateTextures(); + + AcquireNextImage(); } protected override void SetResourceName(string name) @@ -57,6 +83,8 @@ protected override void Destroy() { DestroyTextures(); DestroySwapChain(); + + fence.Dispose(); } private void CreateSwapChain() @@ -258,4 +286,11 @@ private void DestroyTextures() textures[i].Dispose(); } } + + private void AcquireNextImage() + { + Context.Swapchain?.AcquireNextImage(Context.Device, Swapchain, ulong.MaxValue, default, fence.Fence, ref index).Success(); + + fence.Wait(); + } } diff --git a/sources/Zenith.NET.Vulkan/VKTimeline.cs b/sources/Zenith.NET.Vulkan/VKTimeline.cs new file mode 100644 index 00000000..d5315792 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKTimeline.cs @@ -0,0 +1,92 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKTimeline : Timeline +{ + public VkSemaphore Semaphore; + + public VKTimeline(VKGraphicsContext context, VKCommandQueue queue) : base(context, queue) + { + SemaphoreCreateInfo createInfo = new() { SType = StructureType.SemaphoreCreateInfo }; + createInfo.AddNext(out SemaphoreTypeCreateInfo typeCreateInfo); + typeCreateInfo.SemaphoreType = SemaphoreType.Timeline; + + context.Vk.CreateSemaphore(context.Device, &createInfo, default, out Semaphore).Success(); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public new VKCommandQueue Queue => (VKCommandQueue)base.Queue; + + public override ulong CompletedValue + { + get + { + Context.Vk.GetSemaphoreCounterValue(Context.Device, Semaphore, out field).Success(); + + return field; + } + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SignalImpl(ulong value) + { + SemaphoreSubmitInfo signalSemaphoreInfo = new() + { + SType = StructureType.SemaphoreSubmitInfo, + Semaphore = Semaphore, + Value = value, + StageMask = PipelineStageFlags2.AllCommandsBit + }; + + SubmitInfo2 submitInfo = new() + { + SType = StructureType.SubmitInfo2, + SignalSemaphoreInfoCount = 1, + PSignalSemaphoreInfos = &signalSemaphoreInfo + }; + + Context.Vk.QueueSubmit2(Queue.Queue, 1, &submitInfo, default).Success(); + } + + protected override void WaitImpl(ulong value) + { + fixed (VkSemaphore* pSemaphores = &Semaphore) + { + SemaphoreWaitInfo waitInfo = new() + { + SType = StructureType.SemaphoreWaitInfo, + SemaphoreCount = 1, + PSemaphores = pSemaphores, + PValues = &value + }; + + Context.Vk.WaitSemaphores(Context.Device, &waitInfo, ulong.MaxValue).Success(); + } + } + + protected override void SetResourceName(string name) + { + using ZenithMarshal.Scope scope = new(); + + DebugUtilsObjectNameInfoEXT nameInfo = new() + { + SType = StructureType.DebugUtilsObjectNameInfoExt, + ObjectType = ObjectType.Semaphore, + ObjectHandle = Semaphore.Handle, + PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) + }; + + Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + } + + protected override void Destroy() + { + Context.Vk.DestroySemaphore(Context.Device, Semaphore, default); + } +} diff --git a/sources/Zenith.NET/Timeline.cs b/sources/Zenith.NET/Timeline.cs index fd277ba7..91a02c80 100644 --- a/sources/Zenith.NET/Timeline.cs +++ b/sources/Zenith.NET/Timeline.cs @@ -1,4 +1,4 @@ -namespace Zenith.NET; +namespace Zenith.NET; public abstract class Timeline(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) { @@ -6,13 +6,15 @@ public abstract class Timeline(GraphicsContext context, CommandQueue queue) : Gr private ulong nextValue; + public CommandQueue Queue { get; } = queue; + public abstract ulong CompletedValue { get; } public TimelineValue Signal() { using Lock.Scope _ = @lock.EnterScope(); - SignalImpl(queue, ++nextValue); + SignalImpl(++nextValue); return new(this, nextValue); } @@ -27,7 +29,7 @@ public void Wait(ulong value) } } - protected abstract void SignalImpl(CommandQueue queue, ulong value); + protected abstract void SignalImpl(ulong value); protected abstract void WaitImpl(ulong value); } From 77eae98e837f5654dd58d239a02afd2d24437f24 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Jul 2026 21:09:45 +0800 Subject: [PATCH 373/466] Remove Destroy() override from VKCommandQueue The Destroy() method override was deleted from VKCommandQueue, so it no longer explicitly calls base.Destroy() or disposes of the Timeline object during destruction. --- sources/Zenith.NET.Vulkan/VKCommandQueue.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 6cd21dfb..7a1a5b2d 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -78,11 +78,4 @@ protected override void SetResourceName(string name) Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); } - - protected override void Destroy() - { - base.Destroy(); - - Timeline.Dispose(); - } } From 9e094d7b9396ba3d59b495c6e4c5bea595e49d85 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Jul 2026 21:30:24 +0800 Subject: [PATCH 374/466] Refactor allocation ownership handling in Vulkan wrappers Update VKAllocation to distinguish between resource and memory ownership with separate flags. Adjust VKBuffer, VKTexture, VKHeap, and VKSwapChain to use the new constructor and ownership logic, ensuring proper destruction and freeing of resources and memory based on ownership. This improves clarity and correctness in resource management. --- sources/Zenith.NET.Vulkan/VKAllocation.cs | 6 ++++-- sources/Zenith.NET.Vulkan/VKBuffer.cs | 11 +++++++---- sources/Zenith.NET.Vulkan/VKHeap.cs | 4 ++-- sources/Zenith.NET.Vulkan/VKSwapChain.cs | 2 +- sources/Zenith.NET.Vulkan/VKTexture.cs | 9 ++++++--- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKAllocation.cs b/sources/Zenith.NET.Vulkan/VKAllocation.cs index 6dc2b252..fbdba0de 100644 --- a/sources/Zenith.NET.Vulkan/VKAllocation.cs +++ b/sources/Zenith.NET.Vulkan/VKAllocation.cs @@ -2,11 +2,13 @@ namespace Zenith.NET.Vulkan; -internal readonly struct VKAllocation(DeviceMemory deviceMemory, ulong offsetInBytes, bool isOwned) +internal readonly struct VKAllocation(DeviceMemory deviceMemory, ulong offsetInBytes, bool ownsResource, bool ownsMemory) { public readonly DeviceMemory DeviceMemory = deviceMemory; public readonly ulong OffsetInBytes = offsetInBytes; - public readonly bool IsOwned = isOwned; + public readonly bool OwnsResource = ownsResource; + + public readonly bool OwnsMemory = ownsMemory; } diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 55954bc0..0370a18f 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -46,7 +46,7 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); - Allocation = new(deviceMemory, 0, true); + Allocation = new(deviceMemory, 0, true, true); BufferDeviceAddressInfo deviceAddressInfo = new() { @@ -101,7 +101,7 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usa context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); - Allocation = new(deviceMemory, 0, true); + Allocation = new(deviceMemory, 0, true, true); BufferDeviceAddressInfo deviceAddressInfo = new() { @@ -187,9 +187,12 @@ protected override void Destroy() { View.Dispose(); - Context.Vk.DestroyBuffer(Context.Device, Buffer, default); + if (Allocation.OwnsResource) + { + Context.Vk.DestroyBuffer(Context.Device, Buffer, default); + } - if (Allocation.IsOwned) + if (Allocation.OwnsMemory) { Context.Vk.FreeMemory(Context.Device, Allocation.DeviceMemory, default); } diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index d3c3098e..8971ba17 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -35,7 +35,7 @@ protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) Context.Vk.CreateBuffer(Context.Device, &createInfo, default, out VkBuffer buffer).Success(); Context.Vk.BindBufferMemory(Context.Device, buffer, DeviceMemory, offsetInBytes).Success(); - return new VKBuffer(Context, desc, buffer, new(DeviceMemory, offsetInBytes, false)); + return new VKBuffer(Context, desc, buffer, new(DeviceMemory, offsetInBytes, true, false)); } protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) @@ -45,7 +45,7 @@ protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc de Context.Vk.CreateImage(Context.Device, &createInfo, default, out Image image).Success(); Context.Vk.BindImageMemory(Context.Device, image, DeviceMemory, offsetInBytes).Success(); - return new VKTexture(Context, desc, image, new(DeviceMemory, offsetInBytes, false)); + return new VKTexture(Context, desc, image, new(DeviceMemory, offsetInBytes, true, false)); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index 649bdc80..b47c66d5 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -275,7 +275,7 @@ private void CreateTextures() textures = new VKTexture[swapchainImageCount]; for (uint i = 0; i < swapchainImageCount; i++) { - textures[i] = new(Context, desc, swapchainImages[i], new(default, 0, false)); + textures[i] = new(Context, desc, swapchainImages[i], new(default, 0, false, false)); } } diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index c5cfc98e..88945368 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -43,7 +43,7 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, de context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); context.Vk.BindImageMemory(context.Device, Image, deviceMemory, 0).Success(); - Allocation = new(deviceMemory, 0, true); + Allocation = new(deviceMemory, 0, true, true); View = new(context, new() { @@ -134,9 +134,12 @@ protected override void Destroy() View.Dispose(); - Context.Vk.DestroyImage(Context.Device, Image, default); + if (Allocation.OwnsResource) + { + Context.Vk.DestroyImage(Context.Device, Image, default); + } - if (Allocation.IsOwned) + if (Allocation.OwnsMemory) { Context.Vk.FreeMemory(Context.Device, Allocation.DeviceMemory, default); } From e0c59f989ac666bb957d02eae0ac385ce58e5ef1 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Jul 2026 21:46:43 +0800 Subject: [PATCH 375/466] Fix matrix layout for TransformMatrixKHR in VKFormats Correct mapping of Matrix4x4 elements to float array for TransformMatrixKHR to ensure proper row-major/column-major order for Vulkan interoperability. This addresses incorrect memory layout in the previous implementation. --- sources/Zenith.NET.Vulkan/VKFormats.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index eb7d57e2..c493fdcd 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -379,19 +379,19 @@ public static unsafe TransformMatrixKHR Vulkan(Matrix4x4 matrix4x4) float* pResult = (float*)&result; pResult[0] = matrix4x4.M11; - pResult[1] = matrix4x4.M21; - pResult[2] = matrix4x4.M31; - pResult[3] = matrix4x4.M41; + pResult[1] = matrix4x4.M12; + pResult[2] = matrix4x4.M13; + pResult[3] = matrix4x4.M14; - pResult[4] = matrix4x4.M12; + pResult[4] = matrix4x4.M21; pResult[5] = matrix4x4.M22; - pResult[6] = matrix4x4.M32; - pResult[7] = matrix4x4.M42; + pResult[6] = matrix4x4.M23; + pResult[7] = matrix4x4.M24; - pResult[8] = matrix4x4.M13; - pResult[9] = matrix4x4.M23; + pResult[8] = matrix4x4.M31; + pResult[9] = matrix4x4.M32; pResult[10] = matrix4x4.M33; - pResult[11] = matrix4x4.M43; + pResult[11] = matrix4x4.M34; return result; } From 14603c4921f0c2a7ecc0accecf568fd5e42f593b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Wed, 8 Jul 2026 21:47:33 +0800 Subject: [PATCH 376/466] Refactor Vulkan matrix conversion to remove unsafe code Replaced pointer-based assignment in Vulkan(Matrix4x4) with direct assignment to the Matrix array of TransformMatrixKHR, improving code safety and readability. --- sources/Zenith.NET.Vulkan/VKFormats.cs | 31 +++++++++++--------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index c493fdcd..fa02ca6e 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -374,24 +374,19 @@ public static AttachmentLoadOp Vulkan(LoadOp loadOp) public static unsafe TransformMatrixKHR Vulkan(Matrix4x4 matrix4x4) { - TransformMatrixKHR result; - - float* pResult = (float*)&result; - - pResult[0] = matrix4x4.M11; - pResult[1] = matrix4x4.M12; - pResult[2] = matrix4x4.M13; - pResult[3] = matrix4x4.M14; - - pResult[4] = matrix4x4.M21; - pResult[5] = matrix4x4.M22; - pResult[6] = matrix4x4.M23; - pResult[7] = matrix4x4.M24; - - pResult[8] = matrix4x4.M31; - pResult[9] = matrix4x4.M32; - pResult[10] = matrix4x4.M33; - pResult[11] = matrix4x4.M34; + TransformMatrixKHR result = new(); + result.Matrix[0] = matrix4x4.M11; + result.Matrix[1] = matrix4x4.M12; + result.Matrix[2] = matrix4x4.M13; + result.Matrix[3] = matrix4x4.M14; + result.Matrix[4] = matrix4x4.M21; + result.Matrix[5] = matrix4x4.M22; + result.Matrix[6] = matrix4x4.M23; + result.Matrix[7] = matrix4x4.M24; + result.Matrix[8] = matrix4x4.M31; + result.Matrix[9] = matrix4x4.M32; + result.Matrix[10] = matrix4x4.M33; + result.Matrix[11] = matrix4x4.M34; return result; } From c040c50990a42e8c6a8d779d4d19bcb54fc4da2b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Jul 2026 00:36:20 +0800 Subject: [PATCH 377/466] Update Slangc.NET to version 2026.12.2 Upgraded the Slangc.NET NuGet package from version 2026.10.3 to 2026.12.2 in Directory.Packages.props to include the latest features and fixes. --- sources/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 9ef207ee..f4a1b249 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -20,7 +20,7 @@ - + From 90734319fccacb70895bc33d2de53e6bc60bbbd0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Jul 2026 00:56:16 +0800 Subject: [PATCH 378/466] Switch Windows backend to DX12, improve swapchain cleanup Switched default graphics backend on Windows from Vulkan to DirectX 12. Refactored swapchain resource management: moved timeline signaling to Resize, Refresh, and Destroy methods, and ensured base.Destroy() is called in all platform swapchains. Improved Metal resource disposal. --- sources/Experiments/CornellBox/App.cs | 3 ++- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 4 ++-- sources/Zenith.NET.Metal/MTLSwapChain.cs | 5 ++--- sources/Zenith.NET.Vulkan/VKSwapChain.cs | 2 ++ sources/Zenith.NET/SwapChain.cs | 9 +++++++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 6288f577..b04be24e 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -6,6 +6,7 @@ using Silk.NET.Input; using Silk.NET.Windowing; using Zenith.NET; +using Zenith.NET.DirectX12; using Zenith.NET.Metal; using Zenith.NET.Vulkan; @@ -33,7 +34,7 @@ static App() if (OperatingSystem.IsWindows()) { - Context = GraphicsContext.CreateVulkan(useValidationLayer: true); + Context = GraphicsContext.CreateDirectX12(useValidationLayer: true); } else if (OperatingSystem.IsMacOS()) { diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index c6f2cfd6..c13af7dc 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -31,8 +31,6 @@ public override void Present() { SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); - Context.GraphicsQueue.Timeline.Signal().Wait(); - index = SwapChain.GetCurrentBackBufferIndex(); } @@ -60,6 +58,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + base.Destroy(); + DestroyTextures(); DestroySwapChain(); } diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 7974ffd3..7c824d3f 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -54,9 +54,6 @@ public override void Present() Context.GraphicsQueue.Metal().CommandQueue.SignalDrawable(MetalDrawable); MetalDrawable.Present(); - - Context.GraphicsQueue.Timeline.Signal().Wait(); - MetalDrawable.Dispose(); MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); } @@ -86,6 +83,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + base.Destroy(); + drawable?.Dispose(); MetalDrawable.Dispose(); diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index b47c66d5..a16a4276 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -81,6 +81,8 @@ protected override void SetResourceName(string name) protected override void Destroy() { + base.Destroy(); + DestroyTextures(); DestroySwapChain(); diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 54e4f254..70e5267e 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -12,6 +12,8 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public void Resize(uint width, uint height) { + Context.GraphicsQueue.Timeline.Signal().Wait(); + desc.Surface.Width = width; desc.Surface.Height = height; @@ -22,6 +24,8 @@ public void Resize(uint width, uint height) public void Refresh(Surface surface) { + Context.GraphicsQueue.Timeline.Signal().Wait(); + desc.Surface = surface; RefreshImpl(); @@ -32,4 +36,9 @@ public void Refresh(Surface surface) protected abstract void ResizeImpl(); protected abstract void RefreshImpl(); + + protected override void Destroy() + { + Context.GraphicsQueue.Timeline.Signal().Wait(); + } } From 144632771a8c75a6e0d853862f35b9e491ba61e8 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Thu, 9 Jul 2026 18:40:12 +0800 Subject: [PATCH 379/466] Update SkiaSharp and Slangc.NET package versions Updated SkiaSharp to 4.150.0 and Slangc.NET to 2026.13.0 for improved features and compatibility. --- sources/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index f4a1b249..402e5a6b 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -19,8 +19,8 @@ - - + + From 44cea9cc3f2c11cc5ec8e17a4f4295fd2dd8e1d0 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 11 Jul 2026 16:41:42 +0800 Subject: [PATCH 380/466] Refactor command buffer reclamation logic Moved buffer reclamation from CommandBuffer() to a new Reclaim() method in CommandQueue. Now, TimelineValue.Wait() triggers reclamation after waiting, ensuring timely cleanup of completed command buffers. CommandBuffer() now only handles buffer allocation and initialization. --- sources/Zenith.NET/CommandQueue.cs | 21 ++++++++++++--------- sources/Zenith.NET/Structs/TimelineValue.cs | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/sources/Zenith.NET/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 84a6f7c4..63c097fe 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -14,15 +14,6 @@ public CommandBuffer CommandBuffer() { using Lock.Scope _ = @lock.EnterScope(); - while (submitteds.TryPeek(out Submitted submitted) && submitted.TimelineValue.IsCompleted) - { - submitteds.Dequeue(); - - submitted.CommandBuffer.Reset(); - - commandBuffers.Enqueue(submitted.CommandBuffer); - } - CommandBuffer commandBuffer = commandBuffers.Count is 0 ? CreateCommandBuffer() : commandBuffers.Dequeue(); commandBuffer.Begin(); @@ -45,6 +36,18 @@ internal TimelineValue Submit(ReadOnlySpan waits, CommandBuffer c return timelineValue; } + internal void Reclaim() + { + while (submitteds.TryPeek(out Submitted submitted) && submitted.TimelineValue.IsCompleted) + { + submitteds.Dequeue(); + + submitted.CommandBuffer.Reset(); + + commandBuffers.Enqueue(submitted.CommandBuffer); + } + } + protected abstract CommandBuffer CreateCommandBuffer(); protected abstract void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer); diff --git a/sources/Zenith.NET/Structs/TimelineValue.cs b/sources/Zenith.NET/Structs/TimelineValue.cs index b7457f29..efe48fff 100644 --- a/sources/Zenith.NET/Structs/TimelineValue.cs +++ b/sources/Zenith.NET/Structs/TimelineValue.cs @@ -11,5 +11,6 @@ public readonly struct TimelineValue(Timeline timeline, ulong value) public void Wait() { Timeline.Wait(Value); + Timeline.Queue.Reclaim(); } } From 8f79ccb7570a28c19fc851c946349bb7737c7022 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 11 Jul 2026 19:07:09 +0800 Subject: [PATCH 381/466] Refactor SwapChain present and resize logic Centralize Present synchronization in SwapChain base class by introducing PresentImpl in derived classes. Refactor Resize and Refresh to use abstract implementations and remove explicit timeline signaling. Update Destroy methods in derived classes to avoid calling base.Destroy(). --- sources/Zenith.NET.DirectX12/DXSwapChain.cs | 4 +--- sources/Zenith.NET.Metal/MTLSwapChain.cs | 4 +--- sources/Zenith.NET.Vulkan/VKSwapChain.cs | 4 +--- sources/Zenith.NET/SwapChain.cs | 18 ++++++++---------- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index c13af7dc..c023f988 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -27,7 +27,7 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - public override void Present() + protected override void PresentImpl() { SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); @@ -58,8 +58,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - base.Destroy(); - DestroyTextures(); DestroySwapChain(); } diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 7c824d3f..2b2ff851 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -49,7 +49,7 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - public override void Present() + protected override void PresentImpl() { Context.GraphicsQueue.Metal().CommandQueue.SignalDrawable(MetalDrawable); @@ -83,8 +83,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - base.Destroy(); - drawable?.Dispose(); MetalDrawable.Dispose(); diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index a16a4276..c251c84f 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -32,7 +32,7 @@ public override nint GetNativeObject(NativeObjectType type) return 0; } - public override void Present() + protected override void PresentImpl() { fixed (SwapchainKHR* swapchains = &Swapchain) { @@ -81,8 +81,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - base.Destroy(); - DestroyTextures(); DestroySwapChain(); diff --git a/sources/Zenith.NET/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index 70e5267e..9e7ecb57 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -8,12 +8,15 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public abstract Texture Drawable { get; } - public abstract void Present(); - - public void Resize(uint width, uint height) + public void Present() { + PresentImpl(); + Context.GraphicsQueue.Timeline.Signal().Wait(); + } + public void Resize(uint width, uint height) + { desc.Surface.Width = width; desc.Surface.Height = height; @@ -24,8 +27,6 @@ public void Resize(uint width, uint height) public void Refresh(Surface surface) { - Context.GraphicsQueue.Timeline.Signal().Wait(); - desc.Surface = surface; RefreshImpl(); @@ -33,12 +34,9 @@ public void Refresh(Surface surface) SetResourceName(Name); } + protected abstract void PresentImpl(); + protected abstract void ResizeImpl(); protected abstract void RefreshImpl(); - - protected override void Destroy() - { - Context.GraphicsQueue.Timeline.Signal().Wait(); - } } From f1267a4e76107a8b74168d45e6c99ef4935c167b Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 11 Jul 2026 20:28:49 +0800 Subject: [PATCH 382/466] Replace KhrExternalFenceFd with KhrExternalMemoryFd support Switched Vulkan extension support from KhrExternalFenceFd to KhrExternalMemoryFd. Updated enabled extensions list, replaced the related property, and adjusted initialization logic accordingly. --- sources/Zenith.NET.Vulkan/VKGraphicsContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index ef06174b..42d3c0a0 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -33,7 +33,7 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte ExtMetalObjects.ExtensionName, KhrAccelerationStructure.ExtensionName, KhrDeferredHostOperations.ExtensionName, - KhrExternalFenceFd.ExtensionName, + KhrExternalMemoryFd.ExtensionName, KhrExternalMemoryWin32.ExtensionName, KhrFragmentShadingRate.ExtensionName, KhrRayQuery.ExtensionName, @@ -75,7 +75,7 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public KhrDeferredHostOperations? DeferredHostOperations { get; private set; } - public KhrExternalFenceFd? ExternalFenceFd { get; private set; } + public KhrExternalMemoryFd? ExternalMemoryFd { get; private set; } public KhrExternalMemoryWin32? ExternalMemoryWin32 { get; private set; } @@ -500,7 +500,7 @@ protected override void Initialize(bool useValidationLayer, MetalObjects = enabledExtensions.Contains(ExtMetalObjects.ExtensionName) ? new(context) : null; AccelerationStructure = enabledExtensions.Contains(KhrAccelerationStructure.ExtensionName) ? new(context) : null; DeferredHostOperations = enabledExtensions.Contains(KhrDeferredHostOperations.ExtensionName) ? new(context) : null; - ExternalFenceFd = enabledExtensions.Contains(KhrExternalFenceFd.ExtensionName) ? new(context) : null; + ExternalMemoryFd = enabledExtensions.Contains(KhrExternalMemoryFd.ExtensionName) ? new(context) : null; ExternalMemoryWin32 = enabledExtensions.Contains(KhrExternalMemoryWin32.ExtensionName) ? new(context) : null; FragmentShadingRate = enabledExtensions.Contains(KhrFragmentShadingRate.ExtensionName) ? new(context) : null; Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; From 2cbc6cb9127a8b3968284769985743ae1ce0fe49 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sat, 11 Jul 2026 20:57:01 +0800 Subject: [PATCH 383/466] Support external native texture import in Vulkan Added VKFormats.Vulkan(NativeTextureType) to map native texture types to Vulkan external memory handle flags. Implemented CreateTextureImpl in VKGraphicsContext to import and bind native textures from Metal, D3D11, D3D12, Vulkan, and Android hardware buffer sources, handling platform-specific memory allocation and image creation. --- sources/Zenith.NET.Vulkan/VKFormats.cs | 13 +++ .../Zenith.NET.Vulkan/VKGraphicsContext.cs | 92 ++++++++++++++++++- 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index fa02ca6e..a19cad65 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -391,6 +391,19 @@ public static unsafe TransformMatrixKHR Vulkan(Matrix4x4 matrix4x4) return result; } + public static ExternalMemoryHandleTypeFlags Vulkan(NativeTextureType nativeTextureType) + { + return nativeTextureType switch + { + NativeTextureType.D3D11TextureNtHandle => ExternalMemoryHandleTypeFlags.D3D11TextureBit, + NativeTextureType.D3D12ResourceNtHandle => ExternalMemoryHandleTypeFlags.D3D12ResourceBit, + NativeTextureType.VulkanOpaqueNtHandle => ExternalMemoryHandleTypeFlags.OpaqueWin32Bit, + NativeTextureType.VulkanOpaquePosixFileDescriptor => ExternalMemoryHandleTypeFlags.OpaqueFDBit, + NativeTextureType.VulkanAndroidHardwareBuffer => ExternalMemoryHandleTypeFlags.AndroidHardwareBufferBitAndroid, + _ => default + }; + } + public static (Format Format, ImageAspectFlags AspectFlags) Vulkan(PixelFormat pixelFormat) { Format format = pixelFormat switch diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 42d3c0a0..1b200450 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -581,7 +581,97 @@ protected override Texture CreateTextureImpl(TextureDesc desc) protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { - throw new NotImplementedException(); + ImageCreateInfo createInfo = VKTexture.CreateInfo(desc, QueueFamilies); + + if (nativeTextureType is NativeTextureType.MTLSharedTextureHandle or NativeTextureType.IOSurfaceRef) + { + switch (nativeTextureType) + { + case NativeTextureType.MTLSharedTextureHandle: + { + createInfo.AddNext(out ImportMetalTextureInfoEXT importInfo); + importInfo.Plane = ImageAspectFlags.ColorBit; + importInfo.MtlTexture = nativeTexture; + } + break; + + case NativeTextureType.IOSurfaceRef: + { + createInfo.AddNext(out ImportMetalIOSurfaceInfoEXT importInfo); + importInfo.IoSurface = nativeTexture; + } + break; + } + + Vk.CreateImage(Device, &createInfo, default, out Image metalImage).Success(); + + return new VKTexture(this, desc, metalImage, new(default, 0, true, false)); + } + else + { + createInfo.AddNext(out ExternalMemoryImageCreateInfo externalMemoryImageCreateInfo); + externalMemoryImageCreateInfo.HandleTypes = VKFormats.Vulkan(nativeTextureType); + + Vk.CreateImage(Device, &createInfo, default, out Image image).Success(); + + ImageMemoryRequirementsInfo2 requirementsInfo2 = new() + { + SType = StructureType.ImageMemoryRequirementsInfo2, + Image = image + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + Vk.GetImageMemoryRequirements2(Device, &requirementsInfo2, &requirements2); + + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, MemoryResidency.GpuOnly) + }; + + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Image = image; + + switch (nativeTextureType) + { + case NativeTextureType.D3D11TextureNtHandle: + case NativeTextureType.D3D12ResourceNtHandle: + case NativeTextureType.VulkanOpaqueNtHandle: + { + allocateInfo.AddNext(out ImportMemoryWin32HandleInfoKHR importInfo); + importInfo.HandleType = VKFormats.Vulkan(nativeTextureType); + importInfo.Handle = nativeTexture; + } + break; + + case NativeTextureType.VulkanOpaquePosixFileDescriptor: + { + allocateInfo.AddNext(out ImportMemoryFdInfoKHR importInfo); + importInfo.HandleType = VKFormats.Vulkan(nativeTextureType); + importInfo.Fd = (int)nativeTexture; + } + break; + + case NativeTextureType.VulkanAndroidHardwareBuffer: + { + AndroidHardwareBufferPropertiesANDROID properties = new() { SType = StructureType.AndroidHardwareBufferPropertiesAndroid }; + ExternalMemoryAndroidHardwareBuffer!.GetAndroidHardwareBufferProperties(Device, (nint*)nativeTexture, &properties).Success(); + + allocateInfo.AllocationSize = properties.AllocationSize; + allocateInfo.MemoryTypeIndex = FindMemoryTypeIndex(properties.MemoryTypeBits, MemoryResidency.GpuOnly); + + allocateInfo.AddNext(out ImportAndroidHardwareBufferInfoANDROID importInfo); + importInfo.Buffer = (nint*)nativeTexture; + } + break; + } + + Vk.AllocateMemory(Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); + Vk.BindImageMemory(Device, image, deviceMemory, 0).Success(); + + return new VKTexture(this, desc, image, new(deviceMemory, 0, true, true)); + } } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) From a09c6518984a0e8e1224ee70f9a533b7c84c8e94 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 12 Jul 2026 00:48:29 +0800 Subject: [PATCH 384/466] Refactor shader structs, standardize naming, misc fixes Refactored property getters in Slang shader structs to use explicit block bodies for clarity. Standardized function names to PascalCase and updated all references. Improved code formatting and clarified comments in .gitattributes. Added partial Surface class with CloseHandle P/Invoke in ZenithView.WinUI.cs. Adjusted order in Zenith.NET.csproj for proper project structure. --- .gitattributes | 6 +-- .../Assets/Shaders/PathTracing.slang | 51 ++++++++++++------- .../Assets/Shaders/Rasterization.slang | 30 +++++++---- .../ZenithView.WinUI.cs | 1 + sources/Zenith.NET/Zenith.NET.csproj | 4 +- 5 files changed, 59 insertions(+), 33 deletions(-) diff --git a/.gitattributes b/.gitattributes index af39116f..7c6293ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,7 +17,7 @@ # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following +# the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below @@ -46,9 +46,9 @@ ############################################################################### # diff behavior for common document formats -# +# # Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the +# is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain diff --git a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang index 5dd2780c..8f8d69bc 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang @@ -12,21 +12,24 @@ struct Vertex property float3 Position { - get { + get + { return PositionAndPadding.xyz; } } property float3 Normal { - get { + get + { return NormalAndMaterialId.xyz; } } property uint MaterialId { - get { + get + { return asuint(NormalAndMaterialId.w); } } @@ -40,28 +43,32 @@ struct Material property float3 Albedo { - get { + get + { return AlbedoAndEmission.xyz; } } property float Emission { - get { + get + { return AlbedoAndEmission.w; } } property float Metallic { - get { + get + { return MetallicRoughnessAndPadding.x; } } property float Roughness { - get { + get + { return MetallicRoughnessAndPadding.y; } } @@ -91,28 +98,32 @@ struct PathTracingConstants property float3 Position { - get { + get + { return PositionAndPadding.xyz; } } property uint FrameCount { - get { + get + { return FrameCountWidthHeightAndPadding.x; } } property uint Width { - get { + get + { return FrameCountWidthHeightAndPadding.y; } } property uint Height { - get { + get + { return FrameCountWidthHeightAndPadding.z; } } @@ -177,7 +188,7 @@ float PowerHeuristic(float pdfA, float pdfB) return a2 / (a2 + pdfB * pdfB + 0.0001); } -float ComputeBrdfPdf(float3 N, float3 V, float3 L, float roughness, float specProb) +float ComputeBRDFPDF(float3 N, float3 V, float3 L, float roughness, float specProb) { float NdotL = max(dot(N, L), 0.0); if (NdotL <= 0.0) @@ -200,7 +211,7 @@ float ComputeBrdfPdf(float3 N, float3 V, float3 L, float roughness, float specPr return specProb * pdfSpec + (1.0 - specProb) * pdfDiff; } -uint PcgHash(uint input) +uint PCGHash(uint input) { uint state = input * 747796405u + 2891336453u; uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; @@ -210,7 +221,7 @@ uint PcgHash(uint input) float RandomFloat(inout uint seed) { - seed = PcgHash(seed); + seed = PCGHash(seed); return float(seed) / 4294967295.0; } @@ -308,7 +319,7 @@ float3 SampleLightDirect(float3 hitPos, float3 hitNormal, float3 geoNormal, floa float3 lightEmission = lightMat.Albedo * lightMat.Emission; float pdfLight = (dist * dist) / (lightCosine * LightArea); - float pdfBrdf = ComputeBrdfPdf(hitNormal, V, L, roughness, specProb); + float pdfBrdf = ComputeBRDFPDF(hitNormal, V, L, roughness, specProb); float misWeight = PowerHeuristic(pdfLight, pdfBrdf); float3 brdf = EvaluateBRDF(hitNormal, V, L, mat); @@ -338,7 +349,9 @@ float3 TracePath(float3 origin, float3 direction, inout uint rng) if (query.CommittedStatus() == COMMITTED_NOTHING) { - float3 skyColor = lerp(float3(0.15, 0.12, 0.10), float3(0.30, 0.35, 0.45), saturate(direction.y * 0.5 + 0.5)); + float3 skyColor = lerp(float3(0.15, 0.12, 0.10), + float3(0.30, 0.35, 0.45), + saturate(direction.y * 0.5 + 0.5)); radiance += throughput * skyColor; break; @@ -472,7 +485,9 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) return; } - uint rng = PcgHash(pixel.x + pixel.y * pathTracing.Width + pathTracing.FrameCount * pathTracing.Width * pathTracing.Height); + uint rng = PCGHash(pixel.x + + pixel.y * pathTracing.Width + + pathTracing.FrameCount * pathTracing.Width * pathTracing.Height); uint sampleIndex = pathTracing.FrameCount + 1; float hx = Halton(sampleIndex, 2); @@ -514,5 +529,5 @@ void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) avg = saturate(a / b); avg = pow(avg, 1.0 / 2.2); - (*pathTracing.OutputTexture) [pixel] = float4(avg, 1.0); + (*pathTracing.OutputTexture)[pixel] = float4(avg, 1.0); } diff --git a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang index 35e8d422..78fdc734 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang +++ b/sources/Experiments/CornellBox/Assets/Shaders/Rasterization.slang @@ -8,28 +8,32 @@ struct Material property float3 Albedo { - get { + get + { return AlbedoAndEmission.xyz; } } property float Emission { - get { + get + { return AlbedoAndEmission.w; } } property float Metallic { - get { + get + { return MetallicRoughnessAndPadding.x; } } property float Roughness { - get { + get + { return MetallicRoughnessAndPadding.y; } } @@ -53,21 +57,24 @@ struct RasterizationConstants property float3 LightPosition { - get { + get + { return LightPositionAndPadding.xyz; } } property float3 LightColor { - get { + get + { return LightColorAndPadding.xyz; } } property float3 CameraPosition { - get { + get + { return CameraPositionAndPadding.xyz; } } @@ -81,21 +88,24 @@ struct VSInput property float3 Position { - get { + get + { return PositionAndPadding.xyz; } } property float3 Normal { - get { + get + { return NormalAndMaterialId.xyz; } } property uint MaterialId { - get { + get + { return asuint(NormalAndMaterialId.w); } } diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 1b674c81..22c256d7 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -99,6 +99,7 @@ public static void Success(int result) } } } + internal unsafe partial class Surface : DisposableObject { [LibraryImport("kernel32")] diff --git a/sources/Zenith.NET/Zenith.NET.csproj b/sources/Zenith.NET/Zenith.NET.csproj index a8044549..df2bd0f1 100644 --- a/sources/Zenith.NET/Zenith.NET.csproj +++ b/sources/Zenith.NET/Zenith.NET.csproj @@ -5,10 +5,10 @@ IDE0130 - - + + From 10e9af57ea5b3ae161ecd6fef25d376e1ca3da1e Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 12 Jul 2026 00:52:03 +0800 Subject: [PATCH 385/466] Add -spirv-unified-descriptor-heap-stride for Vulkan Added the "-spirv-unified-descriptor-heap-stride" argument to the Vulkan case in ZenithCompiler. This ensures the argument is included when compiling for the Vulkan graphics API, improving descriptor heap handling. --- sources/Zenith.NET/ZenithCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs index ff256527..9d807207 100644 --- a/sources/Zenith.NET/ZenithCompiler.cs +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -53,7 +53,7 @@ private static string[] Arguments(GraphicsApi graphicsApi, string name, string[] break; case GraphicsApi.Vulkan: - arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-fvk-use-entrypoint-name"]); + arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-spirv-unified-descriptor-heap-stride", "-fvk-use-entrypoint-name"]); break; } From 355596a8636bf085a854c6030591707783a3233f Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 12 Jul 2026 01:38:17 +0800 Subject: [PATCH 386/466] Remove '-spirv-unified-descriptor-heap-stride' for Vulkan The '-spirv-unified-descriptor-heap-stride' argument was removed from the Vulkan argument list in ZenithCompiler. This streamlines the Vulkan compilation options while keeping all other arguments unchanged. --- sources/Zenith.NET/ZenithCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs index 9d807207..ff256527 100644 --- a/sources/Zenith.NET/ZenithCompiler.cs +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -53,7 +53,7 @@ private static string[] Arguments(GraphicsApi graphicsApi, string name, string[] break; case GraphicsApi.Vulkan: - arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-spirv-unified-descriptor-heap-stride", "-fvk-use-entrypoint-name"]); + arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-fvk-use-entrypoint-name"]); break; } From 04502481cce387b0ec79fc2a04511003cfb64dc9 Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Sun, 12 Jul 2026 14:20:39 +0800 Subject: [PATCH 387/466] Add spvRayQueryKHR capability to Vulkan compiler args Enables support for SPIR-V ray query features in Vulkan shaders by adding the spvRayQueryKHR capability to the compiler arguments in ZenithCompiler. --- sources/Zenith.NET/ZenithCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs index ff256527..c55250b3 100644 --- a/sources/Zenith.NET/ZenithCompiler.cs +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -53,7 +53,7 @@ private static string[] Arguments(GraphicsApi graphicsApi, string name, string[] break; case GraphicsApi.Vulkan: - arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-fvk-use-entrypoint-name"]); + arguments.AddRange(["spirv", "-capability", "spirv_latest", "-capability", "spvDescriptorHeapEXT", "-capability", "spvRayQueryKHR", "-fvk-use-entrypoint-name"]); break; } From 74d838709815f4f002050394fe02298f6e16b85c Mon Sep 17 00:00:00 2001 From: qian-o <1324771795@qq.com> Date: Mon, 13 Jul 2026 02:00:45 +0800 Subject: [PATCH 388/466] Redesign DocFX documentation site --- documents/docfx.json | 3 +- documents/images/Zenith.NET-Logo-Dark.svg | 20 + documents/images/Zenith.NET-Logo.svg | 59 +- documents/images/Zenith.NET.svg | 61 +- documents/index.md | 303 +- documents/templates/public/main.css | 5790 ++++++++++++----- documents/templates/public/main.js | 56 + documents/toc.yml | 5 +- ...ET-session-handoff-2026-07-13-f256c9cb.zip | Bin 0 -> 1341220 bytes 9 files changed, 4400 insertions(+), 1897 deletions(-) create mode 100644 documents/images/Zenith.NET-Logo-Dark.svg create mode 100644 handoffs/Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip diff --git a/documents/docfx.json b/documents/docfx.json index dc96a069..064adaf8 100644 --- a/documents/docfx.json +++ b/documents/docfx.json @@ -44,8 +44,9 @@ "globalMetadata": { "_appTitle": "Zenith.NET", "_appLogoPath": "images/Zenith.NET-Logo.svg", - "_appFaviconPath": "images/Zenith.NET.png", + "_appFaviconPath": "images/Zenith.NET.svg", "_enableSearch": true, + "_appFooter": "Zenith.NET is an open-source, cross-platform rendering hardware interface for .NET.", "_gitContribute": { "repo": "https://github.com/qian-o/Zenith.NET", "branch": "master" diff --git a/documents/images/Zenith.NET-Logo-Dark.svg b/documents/images/Zenith.NET-Logo-Dark.svg new file mode 100644 index 00000000..726de00d --- /dev/null +++ b/documents/images/Zenith.NET-Logo-Dark.svg @@ -0,0 +1,20 @@ + + Zenith.NET + + + + + + + + + + + + + + + + Zenith + .NET + \ No newline at end of file diff --git a/documents/images/Zenith.NET-Logo.svg b/documents/images/Zenith.NET-Logo.svg index 91836d0c..ba1fad07 100644 --- a/documents/images/Zenith.NET-Logo.svg +++ b/documents/images/Zenith.NET-Logo.svg @@ -1,45 +1,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Zenith.NET + + + + + + + + + + + + + - - - - - - Zenith.NET - + Zenith + .NET \ No newline at end of file diff --git a/documents/images/Zenith.NET.svg b/documents/images/Zenith.NET.svg index 0e2e45db..15fcb116 100644 --- a/documents/images/Zenith.NET.svg +++ b/documents/images/Zenith.NET.svg @@ -1,47 +1,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Zenith.NET + + + + + + + + + + + + + + - - \ No newline at end of file diff --git a/documents/index.md b/documents/index.md index 1ee6696f..3722bef4 100644 --- a/documents/index.md +++ b/documents/index.md @@ -1,147 +1,178 @@ --- -title: Zenith.NET - Unified Cross-Platform GPU Programming Interface +title: Zenith.NET - Cross-Platform Graphics for .NET +description: A modern rendering hardware interface for DirectX 12, Metal 4, and Vulkan 1.4. _layout: landing --- -
-
- -

Zenith.NET

-

Unified Cross-Platform GPU Programming for .NET

-

A modern graphics and compute library that provides a single API for DirectX 12, Metal 4, and Vulkan 1.4. Build high-performance rendering and GPU compute applications with ease.

-
- Get Started - API Reference +
+
+ Ray-traced scene rendered with Zenith.NET +
+
+
+
+

One rendering model.Every modern GPU.

+

A precise, low-level .NET interface for graphics, compute, ray queries, and mesh shading across DirectX 12, Metal 4, and Vulkan 1.4.

+ +
+ BACKENDS + DirectX 12 + Metal 4 + Vulkan 1.4 +
+
-
- GitHub Stars - NuGet Version - License +
+
+
+
+ CORE FEATURES +

Everything required formodern GPU rendering

+

Keep explicit control of the hardware while sharing one precise, strongly typed programming model across every backend.

+
+
+
+
+

Cross-platform rendering

+

Target DirectX 12, Metal 4, and Vulkan 1.4 through one consistent, strongly typed .NET API.

+
+
+
+

Explicit graphics API

+

Record command buffers, manage queues, synchronize timelines, and control resource transitions directly.

+
+
+
+

Bindless by design

+

Pass compact resource handles to Slang shaders without maintaining per-draw binding tables.

+
+
+
+

Advanced workloads

+

Build ray queries, mesh shaders, compute pipelines, and indirect workloads on the same command model.

+
+
+
+

Native .NET performance

+

Use spans, native memory, SIMD-friendly data, and deterministic disposal without a managed rendering layer.

+
+
+
+

Extensible ecosystem

+

Integrate rendering with Avalonia, MAUI, WinForms, WinUI, WPF, ImageSharp, ImGui, and Skia.

+
+
-
-
- ---- - -
-
- Graphics APIs - DirectX 12 · Metal 4 · Vulkan 1.4 -
-
-
- Platforms - Windows · Apple · Android · Linux -
-
- ---- - -## Why Zenith.NET? - -
-
-
🎯
-
- Unified API — Write once, run everywhere. A single API abstracts DirectX 12, Metal 4, and Vulkan 1.4. -
-
-
-
-
- High Performance — Direct access to ray tracing, mesh shading, and compute pipelines. -
-
-
-
🔌
-
- Framework Integration — Works with Avalonia, MAUI, WinForms, WinUI, WPF, and Uno Platform. -
-
-
-
🚀
-
- Modern .NET — Built for .NET 10.0+ with nullable types, spans, and the latest C# features. -
-
-
- ---- - -## GPU Features at a Glance - -
-
- Graphics - Vertex and pixel shaders with custom render states. -
-
- Compute - General-purpose GPU computing for image processing, simulations, and more. -
-
- Ray Tracing - Hardware-accelerated BLAS/TLAS with RayQuery in any shader stage. -
-
- Mesh Shading - GPU-driven geometry with mesh and amplification shaders for meshlet processing. -
-
- ---- - -## How It Works - -
-
-
1
-
-

Initialize

-

Create a graphics context with your preferred backend (DirectX 12, Metal 4, or Vulkan 1.4).

+ +
+
+
+
+ + RenderFrame.cs + Zenith.NET RHI +
+
using Zenith.NET;
+using Zenith.NET.Vulkan;
+
using GraphicsContext context = GraphicsContext.CreateVulkan(bool: useValidationLayer);
+
// Build portable shader state once.
+Shader shader = LoadShader("RenderFrame.slang");
+ComputePipeline pipeline = context.CreateComputePipeline(shader);
+
// Rent transient commands for this frame.
+CommandBuffer commands = context.GraphicsQueue.CommandBuffer();
+commands.SetPipeline(pipeline);
+
// Push bindless handles and frame constants directly.
+commands.PushConstants(CreateFrameData(output.Handle, frameIndex));
+
// Record backend-neutral GPU work.
+commands.Dispatch(threadCountX, threadCountY, 1);
+
// ... pass bindless resources as compact handles ...
+// ... mix graphics, compute, mesh, and ray-query work ...
+
// Submit explicitly and synchronize through the queue timeline.
+commands.Submit().Wait();
+
+
+ ARCHITECTURE +

A small API.
Complete GPU control.

+

Zenith.NET keeps the public surface deliberate and close to modern graphics hardware. Build portable renderers without hiding queues, synchronization, or resource state.

+ +
-
-
-
2
-
-

Create Resources

-

Define buffers, textures, shaders, and pipelines.

+ +
+
+
+ QUICK START +

One command.Start rendering.

+

Add a backend package from NuGet, then follow the first-renderer tutorial.

+
+
+ $ dotnet add package Zenith.NET.Vulkan + +
+

Continue with Getting started or browse the API reference.

-
-
-
3
- -
-
4
-
-

Submit & Present

-

Execute on GPU and present to screen or export to textures.

+ +
-
- ---- - -## Get Started - -
-
-

Ready to build?

-

Follow our step-by-step tutorials from Hello Triangle to advanced ray tracing and mesh shading.

- Start Learning → -
- + +
diff --git a/documents/templates/public/main.css b/documents/templates/public/main.css index 7fdbe873..8233982b 100644 --- a/documents/templates/public/main.css +++ b/documents/templates/public/main.css @@ -1,2061 +1,4507 @@ -/** - * Zenith.NET Documentation — "Signal" Theme - * A modern, luminous design language for GPU programming docs. +/** + * Zenith.NET documentation theme. + * Brand-led landing page plus a compact, readable DocFX shell. */ /* ========================================================================== - 1. Design Tokens + Design tokens ========================================================================== */ :root { - /* Brand gradient endpoints */ - --brand-blue: #2563EB; - --brand-violet: #7C3AED; - /* Functional accent (flat fallback) */ - --accent: #2563EB; - --accent-hover: #1D4ED8; - --accent-soft: rgba(37, 99, 235, 0.08); - --accent-soft-mid: rgba(37, 99, 235, 0.14); - --accent-ring: rgba(37, 99, 235, 0.30); - /* Text hierarchy */ - --text-primary: #0F172A; - --text-secondary: #475569; - --text-tertiary: #94A3B8; - /* Surfaces */ - --bg: #FAFAFA; - --surface: #FFFFFF; - --surface-raised: #FFFFFF; - --surface-sunken: #F1F5F9; - /* Borders */ - --border: #E2E8F0; - --border-strong: #CBD5E1; - /* Elevations */ - --shadow-xs: 0 1px 2px rgba(0,0,0,0.05); - --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04); - --shadow-md: 0 4px 12px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.04); - --shadow-lg: 0 12px 32px rgba(0,0,0,0.08), 0 4px 8px rgba(0,0,0,0.04); - /* Radii */ - --radius-xs: 4px; - --radius-sm: 6px; - --radius-md: 10px; - --radius-lg: 14px; - --radius-full: 9999px; - /* Motion */ - --ease: cubic-bezier(0.22, 1, 0.36, 1); - --duration: 0.18s; - /* Gradients */ - --gradient-brand: linear-gradient(135deg, var(--brand-blue), var(--brand-violet)); - --gradient-subtle: linear-gradient(135deg, rgba(37,99,235,0.05), rgba(124,58,237,0.05)); - --gradient-glow: linear-gradient(135deg, rgba(37,99,235,0.12), rgba(124,58,237,0.12)); + --zenith-navy-950: #041a36; + --zenith-navy-900: #062a57; + --zenith-navy-800: #0d3b70; + --zenith-purple-700: #7138e8; + --zenith-purple-600: #8330e7; + --zenith-magenta-600: #c600bd; + --zenith-magenta-500: #e000b8; + --zenith-pink-500: #f02a9a; + --zenith-blue-500: #3178f6; + --zenith-cyan-500: #08a9c4; + --zenith-orange-500: #eb7b27; + --zenith-green-500: #199b68; + + --zenith-text: #0a2b55; + --zenith-text-soft: #62718a; + --zenith-text-faint: #8793a7; + --zenith-surface: #ffffff; + --zenith-surface-soft: #f8f9fd; + --zenith-surface-tint: #fbf8ff; + --zenith-border: #e7e8f0; + --zenith-border-strong: #d9dce8; + --zenith-code: #071c39; + + --zenith-brand-gradient: linear-gradient(110deg, var(--zenith-purple-700) 0%, var(--zenith-magenta-500) 100%); + --zenith-hero-wash: linear-gradient(120deg, #f8f4ff 0%, #fff3fb 48%, #f2f8ff 100%); + --zenith-shadow-small: 0 2px 8px rgba(17, 30, 61, 0.07); + --zenith-shadow-medium: 0 12px 30px rgba(37, 27, 76, 0.11); + --zenith-shadow-large: 0 22px 58px rgba(77, 38, 117, 0.17); + --zenith-radius-small: 5px; + --zenith-radius: 8px; + --zenith-header-height: 68px; + --zenith-content-width: 1200px; + --zenith-copy-width: 780px; + --zenith-ease: cubic-bezier(0.22, 1, 0.36, 1); + + --bs-body-font-family: "Aptos", "Segoe UI Variable Text", sans-serif; + --bs-body-color: var(--zenith-text); + --bs-body-bg: var(--zenith-surface); + --bs-link-color: var(--zenith-purple-700); + --bs-link-hover-color: var(--zenith-magenta-600); + --bs-border-color: var(--zenith-border); + --bs-border-radius: var(--zenith-radius-small); } [data-bs-theme="dark"] { - --brand-blue: #60A5FA; - --brand-violet: #A78BFA; - --accent: #60A5FA; - --accent-hover: #93C5FD; - --accent-soft: rgba(96, 165, 250, 0.10); - --accent-soft-mid: rgba(96, 165, 250, 0.18); - --accent-ring: rgba(96, 165, 250, 0.30); - --text-primary: #F1F5F9; - --text-secondary: #94A3B8; - --text-tertiary: #64748B; - --bg: #09090B; - --surface: #18181B; - --surface-raised: #1E1E22; - --surface-sunken: #111114; - --border: rgba(255,255,255,0.08); - --border-strong: rgba(255,255,255,0.14); - --shadow-xs: 0 1px 2px rgba(0,0,0,0.30); - --shadow-sm: 0 1px 3px rgba(0,0,0,0.40), 0 1px 2px rgba(0,0,0,0.30); - --shadow-md: 0 4px 12px rgba(0,0,0,0.40), 0 2px 4px rgba(0,0,0,0.30); - --shadow-lg: 0 12px 32px rgba(0,0,0,0.50), 0 4px 8px rgba(0,0,0,0.35); - --gradient-subtle: linear-gradient(135deg, rgba(96,165,250,0.06), rgba(167,139,250,0.06)); - --gradient-glow: linear-gradient(135deg, rgba(96,165,250,0.15), rgba(167,139,250,0.15)); + --zenith-text: #eef2fb; + --zenith-text-soft: #aab5ca; + --zenith-text-faint: #78849b; + --zenith-surface: #11131a; + --zenith-surface-soft: #171a23; + --zenith-surface-tint: #1b1722; + --zenith-border: #292d39; + --zenith-border-strong: #3b4050; + --zenith-code: #090d17; + --zenith-hero-wash: linear-gradient(120deg, #191321 0%, #21141f 48%, #111c29 100%); + --zenith-shadow-small: 0 2px 8px rgba(0, 0, 0, 0.24); + --zenith-shadow-medium: 0 12px 30px rgba(0, 0, 0, 0.34); + --zenith-shadow-large: 0 22px 58px rgba(0, 0, 0, 0.42); + --bs-body-color: var(--zenith-text); + --bs-body-bg: var(--zenith-surface); + --bs-border-color: var(--zenith-border); } /* ========================================================================== - 2. Base + Base and typography ========================================================================== */ +html { + scroll-padding-top: calc(var(--zenith-header-height) + 24px); +} + body { - background-color: var(--bg); - color: var(--text-primary); - padding-top: 60px !important; + min-width: 320px; + padding-top: var(--zenith-header-height) !important; + background: var(--zenith-surface); + color: var(--zenith-text); + font-family: var(--bs-body-font-family); + font-size: 16px; + line-height: 1.68; + letter-spacing: 0; -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - position: relative; + text-rendering: optimizeLegibility; } - /* Page-wide decorative mesh gradient */ - body::before { - content: ''; - position: fixed; - inset: 0; - pointer-events: none; - z-index: 0; - background: radial-gradient(50% 40% at 20% 20%, rgba(37,99,235,0.06) 0%, transparent 70%), radial-gradient(45% 35% at 75% 30%, rgba(124,58,237,0.05) 0%, transparent 70%), radial-gradient(40% 40% at 50% 80%, rgba(37,99,235,0.04) 0%, transparent 70%); - } +::selection { + background: rgba(198, 0, 189, 0.18); + color: var(--zenith-text); +} -[data-bs-theme="dark"] body::before { - background: radial-gradient(50% 40% at 20% 20%, rgba(96,165,250,0.08) 0%, transparent 70%), radial-gradient(45% 35% at 75% 30%, rgba(167,139,250,0.07) 0%, transparent 70%), radial-gradient(40% 40% at 50% 80%, rgba(96,165,250,0.05) 0%, transparent 70%); +a { + color: var(--zenith-purple-700); + text-decoration-color: rgba(113, 56, 232, 0.34); + text-underline-offset: 3px; + transition: color 160ms ease, text-decoration-color 160ms ease; } -::selection { - background: var(--accent-soft-mid); - color: var(--text-primary); +a:hover { + color: var(--zenith-magenta-600); + text-decoration-color: currentColor; } -/* Full-width layout */ -main.container-xxl, -header .container-xxl { - max-width: 100% !important; - padding-left: 2rem !important; - padding-right: 2rem !important; +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--zenith-text); + font-family: "Aptos Display", "Segoe UI Variable Display", "Bahnschrift", sans-serif; + font-weight: 750; + letter-spacing: 0; +} + +img { + max-width: 100%; +} + +:focus-visible { + outline: 3px solid rgba(113, 56, 232, 0.42) !important; + outline-offset: 3px; } /* ========================================================================== - 3. Navbar + Global header ========================================================================== */ -header.bg-body, -.navbar { - --bs-bg-opacity: 0; +body > header.bg-body, +body > header { position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - z-index: 1030 !important; - background: color-mix(in srgb, var(--bg) 85%, transparent) !important; - backdrop-filter: blur(12px) saturate(1.4) !important; - -webkit-backdrop-filter: blur(12px) saturate(1.4) !important; - border: none !important; - border-bottom: 1px solid var(--border) !important; - box-shadow: none !important; + inset: 0 0 auto 0; + z-index: 1030; + height: var(--zenith-header-height); + background: rgba(255, 255, 255, 0.96) !important; + border: 0 !important; + border-bottom: 1px solid rgba(14, 36, 72, 0.08) !important; + box-shadow: 0 1px 6px rgba(20, 28, 58, 0.04); + backdrop-filter: blur(16px) saturate(1.25); + -webkit-backdrop-filter: blur(16px) saturate(1.25); } -.dropdown-menu { - --bs-dropdown-bg: transparent; - position: absolute !important; - top: 100% !important; - background: color-mix(in srgb, var(--surface) 95%, transparent) !important; - backdrop-filter: blur(16px) !important; - -webkit-backdrop-filter: blur(16px) !important; - border: 1px solid var(--border) !important; - border-radius: var(--radius-md) !important; - box-shadow: var(--shadow-lg) !important; - padding: 4px !important; - margin-top: 4px !important; - overflow: visible; - z-index: 1040 !important; +[data-bs-theme="dark"] body > header.bg-body, +[data-bs-theme="dark"] body > header { + background: rgba(17, 19, 26, 0.94) !important; + border-bottom-color: var(--zenith-border) !important; } -.dropdown-item { - border-radius: var(--radius-sm) !important; - transition: all var(--duration) var(--ease) !important; - color: var(--text-primary) !important; - padding: 6px 10px !important; +.navbar { + min-height: var(--zenith-header-height); + padding: 0 !important; } - .dropdown-item:hover, - .dropdown-item:focus { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } +.navbar > .container-xxl { + width: 100%; + max-width: var(--zenith-content-width) !important; + min-height: var(--zenith-header-height); + margin: 0 auto; + padding: 0 18px !important; +} + +.navbar-brand { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + margin-right: 34px !important; + padding: 0 !important; +} -.navbar #navbar, -.navbar #navbar > * { +#logo { + display: block; + width: 224px; + height: 46px; + object-fit: contain; + object-position: left center; +} + +.navbar-collapse, +#navbar { + min-width: 0; +} + +#navbar { display: flex; + flex: 1 1 auto; align-items: center; + justify-content: flex-end; + gap: 16px; } -.navbar .navbar-nav { +#navbar .navbar-nav { display: flex; + flex: 0 1 auto; + align-items: center; gap: 2px; + margin-right: auto; } -.navbar .nav-link { +#navbar .nav-link { position: relative; - padding: 6px 12px !important; - border-radius: var(--radius-sm) !important; - font-size: 0.875rem !important; - font-weight: 500 !important; - color: var(--text-secondary) !important; - transition: all var(--duration) var(--ease) !important; - text-decoration: none !important; + padding: 22px 12px 21px !important; + color: #6d778a !important; + border-radius: 0 !important; + font-size: 14px; + font-weight: 600; + line-height: 1; + letter-spacing: 0; + text-decoration: none; } - .navbar .nav-link:hover { - background: var(--accent-soft) !important; - color: var(--text-primary) !important; - } +[data-bs-theme="dark"] #navbar .nav-link { + color: var(--zenith-text-soft) !important; +} - .navbar .nav-link.active { - color: var(--accent) !important; - font-weight: 600 !important; - } +#navbar .nav-link::after { + content: ""; + position: absolute; + right: 12px; + bottom: 14px; + left: 12px; + height: 2px; + background: var(--zenith-brand-gradient); + opacity: 0; + transform: scaleX(0.3); + transition: opacity 160ms ease, transform 160ms ease; +} + +#navbar .nav-link:hover, +#navbar .nav-link.active { + color: var(--zenith-navy-900) !important; +} + +[data-bs-theme="dark"] #navbar .nav-link:hover, +[data-bs-theme="dark"] #navbar .nav-link.active { + color: #ffffff !important; +} + +#navbar .nav-link:hover::after, +#navbar .nav-link.active::after { + opacity: 1; + transform: scaleX(1); +} + +#navbar form.search { + order: 3 !important; + width: 210px; + margin: 0 !important; +} + +#navbar form.search > i { + left: 12px; + color: var(--zenith-text-faint); +} + +#navbar form.search > input { + width: 100%; + height: 36px; + padding-left: 36px; + background: var(--zenith-surface-soft); + color: var(--zenith-text); + border: 1px solid var(--zenith-border); + border-radius: 6px; + box-shadow: none; + font-size: 13px; +} + +#navbar form.search > input:focus { + background: var(--zenith-surface); + border-color: var(--zenith-purple-700); + box-shadow: 0 0 0 3px rgba(113, 56, 232, 0.12); +} .navbar .icons { + order: 4; display: flex; + flex: 0 0 auto; align-items: center; - gap: 2px; + gap: 3px; } - .navbar .icons > a.btn, - .navbar .icons .dropdown > a.btn { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - width: 34px !important; - height: 34px !important; - padding: 0 !important; - border-radius: var(--radius-sm) !important; - color: var(--text-secondary) !important; - transition: all var(--duration) var(--ease) !important; - } +.navbar .icons .btn, +.navbar .icons > a.btn, +.navbar .icons .dropdown > a.btn { + display: inline-flex !important; + align-items: center; + justify-content: center; + min-width: 36px; + height: 36px; + padding: 0 9px !important; + color: var(--zenith-text-soft) !important; + background: transparent !important; + border: 0 !important; + border-radius: 6px !important; + box-shadow: none !important; +} + +.navbar .icons .btn:hover, +.navbar .icons > a.btn:hover, +.navbar .icons .dropdown > a.btn:hover { + color: var(--zenith-purple-700) !important; + background: rgba(113, 56, 232, 0.08) !important; +} - .navbar .icons > a.btn:hover, - .navbar .icons .dropdown > a.btn:hover { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } +.navbar .nav-cta { + order: 5; + display: inline-flex; + align-items: center; + justify-content: center; + height: 40px; + margin-left: 8px; + padding: 0 16px; + color: #ffffff !important; + background: var(--zenith-brand-gradient); + border-radius: 6px; + box-shadow: 0 6px 16px rgba(179, 12, 190, 0.22); + font-size: 13px; + font-weight: 750; + line-height: 1; + text-decoration: none !important; + white-space: nowrap; + transition: transform 160ms ease, box-shadow 160ms ease; +} - .navbar .icons > a.btn i, - .navbar .icons .dropdown > a.btn i { - font-size: 1.05rem; - } +.navbar .nav-cta:hover { + color: #ffffff !important; + box-shadow: 0 8px 20px rgba(179, 12, 190, 0.3); + transform: translateY(-1px); +} - /* Theme switcher dropdown — match icon button size */ - .navbar .icons .dropdown > a.btn { - width: auto !important; - height: 34px !important; - padding: 6px 10px !important; - } +.navbar .nav-cta i { + margin-left: 8px; +} -/* Search box — smaller width and spacing from theme switcher */ -.navbar #navbar form.search { - margin-left: 8px !important; +.dropdown-menu { + padding: 5px !important; + background: var(--zenith-surface) !important; + border: 1px solid var(--zenith-border) !important; + border-radius: 7px !important; + box-shadow: var(--zenith-shadow-medium) !important; } - .navbar #navbar form.search > input { - max-width: 180px !important; - height: 34px !important; - font-size: 0.85rem !important; - } +.dropdown-item { + color: var(--zenith-text) !important; + border-radius: 5px !important; +} -#logo { - height: 50px; +.dropdown-item:hover, +.dropdown-item:focus { + color: var(--zenith-purple-700) !important; + background: rgba(113, 56, 232, 0.08) !important; } /* ========================================================================== - 4. Hero Section + Landing layout reset ========================================================================== */ -.hero { - text-align: center; - padding: 4rem 2rem 4.5rem; +body[data-layout="landing"] { + overflow-x: clip; } -.hero-content { - max-width: 720px; - margin: 0 auto; +body[data-layout="landing"] > main.container-xxl { + width: 100%; + max-width: none !important; + margin: 0 !important; + padding: 0 !important; } -.hero-logo { - width: 80px; - height: 80px; - margin-bottom: 1.75rem; - filter: drop-shadow(0 4px 12px rgba(37,99,235,0.20)); +body[data-layout="landing"] main > .content { + width: 100%; + max-width: none; + margin: 0; + padding: 0; } -[data-bs-theme="dark"] .hero-logo { - filter: drop-shadow(0 4px 16px rgba(96,165,250,0.30)); +body[data-layout="landing"] .actionbar, +body[data-layout="landing"] .contribution, +body[data-layout="landing"] .next-article, +body[data-layout="landing"] main > .affix { + display: none !important; } -.hero h1 { - width: fit-content; - margin: 0 auto 0.5rem; - font-size: 3.5rem; - font-weight: 800; - letter-spacing: -0.035em; - line-height: 1.1; - border: none; - background: var(--gradient-brand); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +body[data-layout="landing"] article { + width: 100%; + max-width: none; + margin: 0; + padding: 0; } -.hero-tagline { - font-size: 1.25rem; - font-weight: 500; - margin: 0 0 1rem; - color: var(--text-primary); - letter-spacing: -0.01em; +.landing-page { + width: 100%; + color: var(--zenith-text); } -.hero-description { - font-size: 1.05rem; - max-width: 560px; - margin: 0 auto 2.25rem; - color: var(--text-secondary); - line-height: 1.7; +.landing-shell { + width: calc(100% - clamp(36px, 4vw, 64px)); + max-width: 1440px; + margin: 0 auto; } -.hero-buttons { - display: flex; - justify-content: center; - gap: 0.75rem; - flex-wrap: wrap; +.landing-page p { + color: var(--zenith-text-soft); } - .hero-buttons .btn, - .btn, - button.btn, - a.btn { - border-radius: var(--radius-sm) !important; - } - - .hero-buttons .btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.625rem 1.5rem; - font-size: 0.9rem; - font-weight: 600; - text-decoration: none; - border: none; - outline: none; - transition: all var(--duration) var(--ease); - gap: 0.375rem; - } - - .hero-buttons .btn:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } - - .hero-buttons .btn-primary { - background: var(--gradient-brand); - color: #FFFFFF; - box-shadow: var(--shadow-sm), 0 0 0 1px rgba(37,99,235,0.10); - } - - .hero-buttons .btn-primary:hover { - box-shadow: var(--shadow-md), 0 0 20px rgba(37,99,235,0.20); - transform: translateY(-1px); - } - - .hero-buttons .btn-secondary { - background: var(--surface) !important; - color: var(--text-primary); - border: 1px solid var(--border-strong) !important; - box-shadow: var(--shadow-xs); - } - - .hero-buttons .btn-secondary:hover { - border-color: var(--accent) !important; - color: var(--accent); - transform: translateY(-1px); - box-shadow: var(--shadow-sm); - } - /* ========================================================================== - 5. Features Grid + Landing hero ========================================================================== */ -.features { +.landing-hero { + position: relative; + min-height: 610px; + overflow: hidden; + background: var(--zenith-hero-wash); + border-bottom: 1px solid rgba(113, 56, 232, 0.07); +} + +.landing-hero::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background-image: linear-gradient(rgba(113, 56, 232, 0.028) 1px, transparent 1px), linear-gradient(90deg, rgba(113, 56, 232, 0.028) 1px, transparent 1px); + background-size: 42px 42px; + -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), transparent 90%); + mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), transparent 90%); +} + +.hero-grid { + position: relative; + z-index: 1; display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1rem; - margin: 2rem 0; + grid-template-columns: minmax(0, 1.02fr) minmax(420px, 0.98fr); + align-items: center; + gap: 56px; + min-height: 610px; + padding-top: 30px; + padding-bottom: 34px; } -.feature { - padding: 1.5rem; - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--surface); - transition: all var(--duration) var(--ease); +.hero-copy { + min-width: 0; + padding: 24px 0; + animation: zenith-copy-in 700ms var(--zenith-ease) both; } - .feature:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-md); - transform: translateY(-2px); - } +.hero-eyebrow { + display: inline-flex; + align-items: center; + min-height: 28px; + margin-bottom: 25px; + padding: 5px 11px; + color: #5d49ad; + background: rgba(255, 255, 255, 0.72); + border: 1px solid rgba(113, 56, 232, 0.15); + border-radius: 999px; + box-shadow: 0 2px 8px rgba(55, 30, 94, 0.04); + font-family: "Aptos", "Segoe UI Variable Text", sans-serif; + font-size: 9px; + font-weight: 800; + line-height: 1; + letter-spacing: 0; +} - .feature h3 { - font-size: 1.05rem; - font-weight: 600; - margin: 0 0 0.5rem; - color: var(--text-primary); - } +[data-bs-theme="dark"] .hero-eyebrow { + background: rgba(27, 23, 34, 0.78); +} - .feature p { - font-size: 0.875rem; - color: var(--text-secondary); - margin: 0; - line-height: 1.6; - } +.eyebrow-dot { + width: 7px; + height: 7px; + margin-right: 8px; + background: var(--zenith-purple-700); + border-radius: 50%; + box-shadow: 0 0 0 3px rgba(113, 56, 232, 0.1); +} -/* ========================================================================== - 6. Technology Section - ========================================================================== */ +.eyebrow-separator { + width: 3px; + height: 3px; + margin: 0 8px; + background: #b4a2d8; + border-radius: 50%; +} -.tech-section { - display: flex; - flex-direction: column; - gap: 2rem; - margin: 2rem 0; +.hero-copy h1 { + max-width: 660px; + margin: 0 0 22px; + color: var(--zenith-navy-900); + border: 0; + font-size: 53px; + font-weight: 800; + line-height: 1.04; + letter-spacing: 0; } -.tech-group { - text-align: center; +[data-bs-theme="dark"] .hero-copy h1 { + color: #f3f6ff; } - .tech-group h4 { - font-size: 0.7rem; - font-weight: 600; - color: var(--text-tertiary); - margin: 0 0 0.75rem; - text-transform: uppercase; - letter-spacing: 0.1em; - } +.hero-copy h1 span { + color: var(--zenith-magenta-600); + background: var(--zenith-brand-gradient); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.hero-lede { + max-width: 590px; + margin: 0 0 26px; + font-size: 17px; + line-height: 1.75; +} -.tech-icons { +.hero-actions, +.quickstart-links, +.cta-actions { display: flex; + align-items: center; flex-wrap: wrap; - justify-content: center; - gap: 0.625rem; + gap: 12px; } -.tech-item { - display: flex; +.landing-button { + display: inline-flex; align-items: center; justify-content: center; - padding: 0.5rem 1rem; - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - transition: all var(--duration) var(--ease); + min-height: 46px; + padding: 0 19px; + border: 1px solid transparent; + border-radius: 6px; + font-size: 14px; + font-weight: 750; + line-height: 1; + text-decoration: none !important; + white-space: nowrap; + transition: color 170ms ease, background 170ms ease, border-color 170ms ease, box-shadow 170ms ease, transform 170ms ease; } - .tech-item:hover { - border-color: var(--accent-ring); - background: var(--accent-soft); - } +.landing-button i { + margin-left: 9px; + font-size: 14px; +} - .tech-item .tech-text { - font-size: 0.875rem; - font-weight: 600; - color: var(--accent); - } +.landing-button-primary { + color: #ffffff !important; + background: var(--zenith-brand-gradient); + box-shadow: 0 8px 18px rgba(181, 12, 189, 0.22); +} -/* ========================================================================== - 7. Cards Grid - ========================================================================== */ +.landing-button-primary:hover { + color: #ffffff !important; + box-shadow: 0 11px 24px rgba(181, 12, 189, 0.3); + transform: translateY(-2px); +} -.cards { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1rem; - margin: 2rem 0; +.landing-button-secondary { + color: var(--zenith-navy-900) !important; + background: rgba(255, 255, 255, 0.86); + border-color: rgba(30, 44, 76, 0.13); + box-shadow: var(--zenith-shadow-small); +} + +[data-bs-theme="dark"] .landing-button-secondary { + color: var(--zenith-text) !important; + background: rgba(27, 29, 38, 0.9); + border-color: var(--zenith-border-strong); } -.card { +.landing-button-secondary:hover { + color: var(--zenith-purple-700) !important; + background: var(--zenith-surface); + border-color: rgba(113, 56, 232, 0.36); + transform: translateY(-2px); +} + +.backend-support { display: flex; - flex-direction: column; - padding: 1.5rem; - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--surface); - transition: all var(--duration) var(--ease); + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-top: 30px; } - .card:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-md); - transform: translateY(-2px); - } +.backend-label { + margin-right: 3px; + color: var(--zenith-text-faint); + font-size: 11px; + font-weight: 650; +} - .card h3 { - font-size: 1.05rem; - font-weight: 600; - margin: 0 0 0.5rem; - color: var(--text-primary); - } +.backend-pill { + display: inline-flex; + align-items: center; + min-height: 26px; + padding: 3px 10px; + color: #384966; + background: rgba(255, 255, 255, 0.82); + border: 1px solid rgba(26, 46, 79, 0.09); + border-radius: 999px; + font-size: 10px; + font-weight: 750; + line-height: 1; +} - .card p { - font-size: 0.875rem; - color: var(--text-secondary); - margin: 0; - line-height: 1.6; - flex-grow: 1; - } +[data-bs-theme="dark"] .backend-pill { + color: var(--zenith-text-soft); + background: rgba(24, 26, 34, 0.82); + border-color: var(--zenith-border); +} - .card a { - display: inline-block; - margin-top: 1rem; - color: var(--accent); - font-size: 0.8125rem; - font-weight: 600; - text-decoration: none; - transition: color var(--duration) var(--ease); - } +.backend-pill > span { + width: 7px; + height: 7px; + margin-right: 7px; + border-radius: 50%; +} - .card a:hover { - color: var(--accent-hover); - } +.backend-directx > span { + background: var(--zenith-blue-500); +} -/* ========================================================================== - 8. Workflow Section - ========================================================================== */ +.backend-vulkan > span { + background: var(--zenith-pink-500); +} -.workflow { - display: flex; - flex-direction: column; - margin: 2rem 0; - --step-size: 44px; - --step-line: 2px; +.backend-metal > span { + background: var(--zenith-purple-600); } -.workflow-step { - display: flex; - align-items: center; - gap: 1.25rem; +.hero-product { position: relative; - padding-bottom: 1.25rem; + min-width: 0; + height: 418px; + overflow: hidden; + background: rgba(255, 255, 255, 0.94); + border: 1px solid rgba(86, 65, 130, 0.08); + border-radius: 8px; + box-shadow: var(--zenith-shadow-large); + animation: zenith-product-in 850ms 90ms var(--zenith-ease) both; } - .workflow-step:last-child { - padding-bottom: 0; - } +[data-bs-theme="dark"] .hero-product { + background: #151821; + border-color: var(--zenith-border); +} - .workflow-step:not(:last-child)::after { - content: ''; - position: absolute; - left: calc((var(--step-size) - var(--step-line)) / 2); - top: 50%; - width: var(--step-line); - height: 100%; - background: var(--border-strong); - z-index: 0; - } - -.step-number { - width: var(--step-size); - height: var(--step-size); - min-width: var(--step-size); - border-radius: 50%; - background: var(--gradient-brand); - color: #FFFFFF; - font-size: 1rem; - font-weight: 700; +.product-toolbar { + position: relative; + z-index: 5; display: flex; align-items: center; - justify-content: center; - z-index: 1; - flex-shrink: 0; - box-shadow: var(--shadow-sm), 0 0 0 3px var(--bg); + height: 52px; + padding: 0 18px; + background: rgba(255, 255, 255, 0.94); + border-bottom: 1px solid rgba(30, 44, 76, 0.06); } -.step-content { - flex: 1; - padding: 1rem 1.25rem; - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--surface); - transition: all var(--duration) var(--ease); +[data-bs-theme="dark"] .product-toolbar { + background: rgba(21, 24, 33, 0.96); + border-bottom-color: var(--zenith-border); } - .step-content:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-sm); - } - - .step-content h4 { - font-size: 0.95rem; - font-weight: 600; - margin: 0 0 0.25rem; - color: var(--text-primary); - } +.product-mark { + display: inline-flex; + align-items: center; + margin-right: auto; + color: var(--zenith-navy-900); + font-size: 13px; +} - .step-content p { - font-size: 0.875rem; - color: var(--text-secondary); - margin: 0; - line-height: 1.6; - } +[data-bs-theme="dark"] .product-mark { + color: var(--zenith-text); +} -/* ========================================================================== - 9. Badges - ========================================================================== */ +.product-mark img { + width: 21px; + height: 21px; + margin-right: 6px; +} -.community-badges, -.hero-badges { +.product-tabs { display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 0.5rem; + align-items: center; + gap: 19px; + margin-right: 18px; + color: #9aa2b2; + font-size: 8px; + font-weight: 650; } -.community-badges { - gap: 0.875rem; - margin: 2rem 0; +.product-status { + padding: 5px 10px; + color: #ffffff; + background: var(--zenith-brand-gradient); + border-radius: 999px; + font-size: 8px; + font-weight: 800; + line-height: 1; } -.hero-badges { - margin-top: 1.75rem; +.product-canvas { + position: relative; + height: calc(100% - 52px); + overflow: hidden; + background: linear-gradient(145deg, #f7f5ff, #f9effb 56%, #eef5ff); } - .community-badges a, - .hero-badges a { - transition: opacity var(--duration) var(--ease), transform var(--duration) var(--ease); - } +[data-bs-theme="dark"] .product-canvas { + background: linear-gradient(145deg, #171923, #231724 56%, #13202c); +} - .community-badges a:hover, - .hero-badges a:hover { - transform: translateY(-1px); - opacity: 0.80; - } +.render-preview { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + filter: saturate(1.2) contrast(1.02) brightness(1.05); + transform: scale(1.02); +} - .hero-badges img { - height: 22px; - } +.render-vignette { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(15, 14, 37, 0.42), transparent 42%), linear-gradient(90deg, rgba(111, 40, 189, 0.1), transparent 32%, rgba(222, 0, 184, 0.08)); + pointer-events: none; +} -/* ========================================================================== - 10. Technology Bar - ========================================================================== */ +.code-float { + position: absolute; + z-index: 3; + width: 205px; + padding: 12px 14px; + color: #f7f4ff; + background: rgba(36, 14, 65, 0.88); + border: 1px solid rgba(238, 208, 255, 0.23); + border-radius: 6px; + box-shadow: 0 14px 30px rgba(17, 7, 31, 0.24); + backdrop-filter: blur(9px); + -webkit-backdrop-filter: blur(9px); +} -.tech-bar { - display: flex; - justify-content: center; - align-items: center; - gap: 2rem; - flex-wrap: wrap; - padding: 1rem 2rem; - background: var(--surface); - border-radius: var(--radius-lg); - border: 1px solid var(--border); - margin-bottom: 2rem; +.code-float-left { + bottom: 34px; + left: 22px; } -.tech-bar-group { - display: flex; - align-items: center; - gap: 0.5rem; +.code-float-right { + top: 28px; + right: 20px; } -.tech-bar-label { - font-size: 0.7rem; - font-weight: 600; - color: var(--text-tertiary); - text-transform: uppercase; - letter-spacing: 0.1em; +.code-float-label { + display: block; + margin-bottom: 6px; + color: #df9fff; + font-size: 8px; + font-weight: 800; } -.tech-bar-items { - font-size: 0.875rem; - font-weight: 600; - background: var(--gradient-brand); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; +.code-float code { + padding: 0; + color: #ffffff; + background: transparent; + font-size: 10px; + white-space: nowrap; +} + +.code-float code b { + color: #ff69d4; + font-weight: 700; +} + +.backend-float { + position: absolute; + right: 20px; + bottom: 26px; + z-index: 3; + display: flex; + gap: 6px; } -.tech-bar-divider { - width: 1px; - height: 20px; - background: var(--border-strong); +.backend-float span { + padding: 6px 8px; + color: #4e3d71; + background: rgba(255, 255, 255, 0.88); + border: 1px solid rgba(89, 58, 130, 0.13); + border-radius: 4px; + box-shadow: 0 4px 12px rgba(19, 13, 43, 0.09); + font-size: 8px; + font-weight: 800; } /* ========================================================================== - 11. Highlight Grid + Landing feature section ========================================================================== */ -.highlight-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.75rem; - margin: 2rem 0; +.features-section { + padding: 88px 0 96px; + background: var(--zenith-surface); } -.highlight-item { - display: flex; - align-items: flex-start; - gap: 0.875rem; - padding: 1.125rem 1.25rem; - background: var(--surface); - border-radius: var(--radius-md); - border: 1px solid var(--border); - transition: all var(--duration) var(--ease); +.section-heading { + max-width: 670px; + margin: 0 auto 50px; + text-align: center; } - .highlight-item:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-sm); - } - -.highlight-icon { - font-size: 1.2rem; +.section-kicker { + display: block; + margin-bottom: 11px; + color: var(--zenith-purple-700); + font-size: 11px; + font-weight: 850; line-height: 1; - flex-shrink: 0; - margin-top: 1px; + letter-spacing: 0; } -.highlight-text { - font-size: 0.875rem; - color: var(--text-secondary); - line-height: 1.55; +.section-heading h2, +.quickstart-copy h2, +.landing-cta h2 { + margin: 0; + border: 0; + font-size: 37px; + font-weight: 800; + line-height: 1.14; + letter-spacing: 0; } - .highlight-text strong { - color: var(--text-primary); - font-weight: 600; - } +.section-heading h2 span { + color: var(--zenith-purple-700); + background: var(--zenith-brand-gradient); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} -/* ========================================================================== - 12. Feature List - ========================================================================== */ +.section-heading p { + max-width: 620px; + margin: 18px auto 0; + font-size: 15px; + line-height: 1.7; +} -.feature-list { +.feature-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 18px; +} + +.feature-card { display: flex; + min-height: 250px; flex-direction: column; - gap: 0.375rem; - margin: 2rem 0; + padding: 24px; + background: var(--zenith-surface); + border: 1px solid var(--zenith-border); + border-radius: 8px; + box-shadow: 0 5px 16px rgba(34, 40, 69, 0.035); + transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease; } -.feature-list-item { - display: flex; - align-items: baseline; - gap: 1rem; - padding: 0.75rem 1.125rem; - background: var(--surface); - border-radius: var(--radius-sm); - border: 1px solid var(--border); - transition: all var(--duration) var(--ease); +.feature-card:hover { + border-color: rgba(113, 56, 232, 0.32); + box-shadow: var(--zenith-shadow-medium); + transform: translateY(-4px); } - .feature-list-item:hover { - border-color: var(--border-strong); - background: var(--accent-soft); +.feature-icon { + display: inline-flex; + width: 42px; + height: 42px; + align-items: center; + justify-content: center; + margin-bottom: 20px; + border-radius: 8px; + font-size: 19px; +} + +.feature-icon-violet { + color: #7138e8; + background: #f0eaff; +} + +.feature-icon-pink { + color: #dc188c; + background: #ffedf7; +} + +.feature-icon-blue { + color: #2472ee; + background: #eaf3ff; +} + +.feature-icon-cyan { + color: #078aa4; + background: #e6f8fb; +} + +.feature-icon-orange { + color: #d66b17; + background: #fff2e7; +} + +.feature-icon-green { + color: #16885d; + background: #e9f8f1; +} + +[data-bs-theme="dark"] .feature-icon-violet, +[data-bs-theme="dark"] .feature-icon-pink, +[data-bs-theme="dark"] .feature-icon-blue, +[data-bs-theme="dark"] .feature-icon-cyan, +[data-bs-theme="dark"] .feature-icon-orange, +[data-bs-theme="dark"] .feature-icon-green { + background: #252836; +} + +.feature-card h3 { + margin: 0 0 10px; + font-size: 18px; + line-height: 1.3; +} + +.feature-card p { + margin: 0 0 18px; + font-size: 14px; + line-height: 1.65; +} + +.feature-card > a { + display: inline-flex; + align-items: center; + align-self: flex-start; + margin-top: auto; + font-size: 13px; + font-weight: 750; + text-decoration: none; +} + +.feature-card > a i { + margin-left: 7px; + transition: transform 160ms ease; +} + +.feature-card > a:hover i { + transform: translateX(3px); +} + +/* ========================================================================== + Quick start and CTA + ========================================================================== */ + +.quickstart-section { + padding: 92px 0; + background: linear-gradient(110deg, #f7f3ff, #fff8fc 52%, #f1f7ff); + border-top: 1px solid rgba(113, 56, 232, 0.08); + border-bottom: 1px solid rgba(113, 56, 232, 0.08); +} + +[data-bs-theme="dark"] .quickstart-section { + background: linear-gradient(110deg, #18141f, #1c151d 52%, #121b26); + border-color: var(--zenith-border); +} + +.quickstart-grid { + display: grid; + grid-template-columns: minmax(0, 0.82fr) minmax(520px, 1.18fr); + align-items: center; + gap: 72px; +} + +.quickstart-copy h2 { + margin-bottom: 18px; + color: var(--zenith-navy-900); +} + +[data-bs-theme="dark"] .quickstart-copy h2 { + color: var(--zenith-text); +} + +.quickstart-copy > p { + margin: 0 0 26px; + font-size: 15px; +} + +.text-link { + padding: 10px 5px; + font-size: 14px; + font-weight: 700; + text-decoration: none; +} + +.code-window { + overflow: hidden; + background: var(--zenith-code); + border: 1px solid rgba(187, 163, 233, 0.2); + border-radius: 8px; + box-shadow: 0 24px 50px rgba(29, 12, 50, 0.2); +} + +.code-window-bar { + display: flex; + align-items: center; + height: 44px; + padding: 0 15px; + background: #0c264b; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.window-dot { + width: 9px; + height: 9px; + margin-right: 6px; + border-radius: 50%; +} + +.window-dot-red { + background: #ff6780; +} + +.window-dot-yellow { + background: #ffc65b; +} + +.window-dot-green { + background: #4fd69c; +} + +.code-window-title { + margin-left: 12px; + color: #becce1; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 11px; +} + +.code-window-lang { + margin-left: auto; + color: #8e9db5; + font-size: 10px; + font-weight: 700; +} + +.code-window pre { + min-height: 330px; + margin: 0; + padding: 30px 32px; + overflow: auto; + color: #dfe8f7; + background: transparent; + border: 0; + border-radius: 0; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 13px; + line-height: 1.78; +} + +.code-window code { + padding: 0; + color: inherit; + background: transparent; +} + +.code-keyword { + color: #dc8cff; +} + +.code-value { + color: #7de8df; +} + +.code-comment { + color: #7589a6; +} + +.landing-cta { + padding: 68px 0; + color: #ffffff; + background: linear-gradient(105deg, #42207e, #7d22bd 48%, #c407b6); +} + +.cta-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 42px; +} + +.landing-cta .section-kicker, +.landing-cta h2, +.landing-cta p { + color: #ffffff; +} + +.landing-cta h2 { + margin-bottom: 8px; +} + +.landing-cta p { + margin: 0; + opacity: 0.78; +} + +.landing-button-light { + color: #5c238d !important; + background: #ffffff; + box-shadow: 0 8px 18px rgba(37, 10, 58, 0.18); +} + +.landing-button-light:hover { + color: #8c1ba0 !important; + transform: translateY(-2px); +} + +.landing-button-ghost { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.36); +} + +.landing-button-ghost:hover { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.6); +} + +@keyframes zenith-copy-in { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes zenith-product-in { + from { + opacity: 0; + transform: translateY(22px) scale(0.985); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* ========================================================================== + Documentation shell + ========================================================================== */ + +body:not([data-layout="landing"]) > main.container-xxl { + display: grid; + grid-template-columns: minmax(190px, 250px) minmax(0, 1fr) minmax(160px, 210px); + width: 100%; + max-width: 1480px !important; + min-height: calc(100vh - var(--zenith-header-height)); + margin: 0 auto; + padding: 0 24px !important; + column-gap: 34px; +} + +body:not([data-layout="landing"]) main > .content { + grid-column: 2; + min-width: 0; + width: auto; + margin-right: 0; + margin-left: 0; + padding: 24px 0 70px; +} + +body:not([data-layout="landing"]) main > .affix { + grid-column: 3; + min-width: 0; + padding-top: 40px; +} + +.actionbar { + min-height: 34px; + margin-bottom: 18px; +} + +.breadcrumb { + margin: 0; + padding: 0; + background: transparent; + font-size: 12px; +} + +.breadcrumb-item, +.breadcrumb-item a { + color: var(--zenith-text-faint); + text-decoration: none; +} + +.breadcrumb-item.active, +.breadcrumb-item a:hover { + color: var(--zenith-purple-700); +} + +.breadcrumb-item + .breadcrumb-item::before { + color: var(--zenith-border-strong); +} + +body:not([data-layout="landing"]) article { + width: 100%; + max-width: var(--zenith-copy-width); + margin: 0 auto; +} + +article > h1:first-child, +article > h1:first-of-type { + margin-top: 0; +} + +article h1 { + margin: 0 0 18px; + padding: 0 0 16px; + border-bottom: 1px solid var(--zenith-border); + font-size: 36px; + font-weight: 800; + line-height: 1.18; +} + +article h2 { + margin: 42px 0 17px; + padding: 0 0 10px; + border-bottom: 1px solid var(--zenith-border); + font-size: 25px; + line-height: 1.25; +} + +article h3 { + margin: 30px 0 12px; + font-size: 19px; + line-height: 1.35; +} + +article h4 { + margin: 24px 0 10px; + font-size: 16px; +} + +article p, +article li, +article dd, +article td { + color: var(--zenith-text-soft); +} + +article p { + margin-bottom: 16px; +} + +article strong { + color: var(--zenith-text); + font-weight: 750; +} + +article ul, +article ol { + padding-left: 1.35rem; +} + +article li { + margin: 5px 0; +} + +article li::marker { + color: var(--zenith-purple-700); +} + +article img:not(.svg) { + border-radius: 7px; +} + +article a:not(.btn):not(.landing-button) { + font-weight: 600; +} + +article hr { + margin: 34px 0; + border-color: var(--zenith-border); + opacity: 1; +} + +article blockquote { + margin: 22px 0; + padding: 14px 18px; + color: var(--zenith-text-soft); + background: var(--zenith-surface-tint); + border-left: 3px solid var(--zenith-purple-700); + border-radius: 0 6px 6px 0; +} + +article blockquote > :last-child { + margin-bottom: 0; +} + +code { + padding: 0.14em 0.38em; + color: #8a208f; + background: rgba(198, 0, 189, 0.07); + border-radius: 4px; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 0.88em; +} + +[data-bs-theme="dark"] code { + color: #efa4ee; + background: rgba(224, 0, 184, 0.11); +} + +article pre, +.codewrapper pre { + position: relative; + margin: 20px 0; + padding: 20px 22px; + overflow: auto; + color: #dce6f6; + background: var(--zenith-code) !important; + border: 1px solid rgba(108, 126, 157, 0.22) !important; + border-radius: 7px !important; + box-shadow: 0 8px 22px rgba(17, 25, 43, 0.08); + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 13px; + line-height: 1.7; +} + +article pre code, +.codewrapper pre code { + padding: 0; + color: inherit; + background: transparent; + border-radius: 0; + font-size: inherit; +} + +.codewrapper { + margin: 18px 0; +} + +.codewrapper .btn-copy, +pre .btn-copy { + color: #9baac2 !important; + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 5px !important; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-section, +.hljs-link { + color: #dd8dff; +} + +.hljs-string, +.hljs-title, +.hljs-name, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #80dfd2; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #7f91ad; +} + +/* ========================================================================== + Tables, alerts, tabs, and API surfaces + ========================================================================== */ + +.table-responsive { + margin: 22px 0; + overflow-x: auto; + border: 1px solid var(--zenith-border); + border-radius: 7px; +} + +article table, +article .table { + width: 100%; + margin: 22px 0; + color: var(--zenith-text-soft); + background: var(--zenith-surface); + border-collapse: separate; + border-spacing: 0; + border: 1px solid var(--zenith-border); + border-radius: 7px; + overflow: hidden; + font-size: 14px; +} + +.table-responsive > table { + margin: 0; + border: 0; + border-radius: 0; +} + +article table th, +article .table th { + padding: 11px 13px; + color: var(--zenith-text); + background: var(--zenith-surface-soft); + border-color: var(--zenith-border); + font-size: 12px; + font-weight: 800; + text-align: left; +} + +article table td, +article .table td { + padding: 11px 13px; + border-color: var(--zenith-border); + vertical-align: top; +} + +article table tr:last-child td, +article .table tr:last-child td { + border-bottom: 0; +} + +.alert { + margin: 22px 0; + padding: 15px 17px; + color: var(--zenith-text-soft); + background: var(--zenith-surface-soft); + border: 1px solid var(--zenith-border); + border-left-width: 4px; + border-radius: 6px; + font-size: 14px; +} + +.alert > h5, +.alert > .alert-title { + margin-bottom: 5px; + color: var(--zenith-text); + font-size: 13px; + font-weight: 850; +} + +.alert-info, +.alert-note { + background: rgba(49, 120, 246, 0.06); + border-left-color: var(--zenith-blue-500); +} + +.alert-tip, +.alert-success { + background: rgba(25, 155, 104, 0.07); + border-left-color: var(--zenith-green-500); +} + +.alert-warning, +.alert-important { + background: rgba(235, 123, 39, 0.07); + border-left-color: var(--zenith-orange-500); +} + +.alert-danger, +.alert-caution { + background: rgba(240, 42, 154, 0.07); + border-left-color: var(--zenith-pink-500); +} + +.tabGroup { + margin: 22px 0; + border: 1px solid var(--zenith-border); + border-radius: 7px; + overflow: hidden; +} + +.tabGroup > ul.nav-tabs { + padding: 0 10px; + background: var(--zenith-surface-soft); + border-bottom: 1px solid var(--zenith-border); +} + +.tabGroup > ul.nav-tabs > li > a { + padding: 10px 12px; + color: var(--zenith-text-soft); + border: 0; + border-bottom: 2px solid transparent; + font-size: 13px; + text-decoration: none; +} + +.tabGroup > ul.nav-tabs > li.active > a, +.tabGroup > ul.nav-tabs > li > a:hover { + color: var(--zenith-purple-700); + border-bottom-color: var(--zenith-purple-700); +} + +.tabGroup > section { + padding: 18px; +} + +article .inheritance, +article .implements, +article .inheritedMembers, +article .derivedClasses { + color: var(--zenith-text-soft); +} + +article dl.typelist, +article dl.parameters, +article dl.returns { + margin: 18px 0; +} + +article dl dt { + color: var(--zenith-text); + font-weight: 750; +} + +article .section { + scroll-margin-top: calc(var(--zenith-header-height) + 20px); +} + +article h3[id], +article a[data-uid] + h3 { + padding-top: 5px; +} + +/* ========================================================================== + Side navigation and in-page navigation + ========================================================================== */ + +#sidetoggle { + position: sticky; + top: var(--zenith-header-height); + align-self: start; + height: calc(100vh - var(--zenith-header-height)); + overflow: hidden; + border-right: 1px solid var(--zenith-border); +} + +#sidetoggle > div { + height: 100%; +} + +.sidefilter { + padding: 18px 14px 10px 0; +} + +.toc-filter { + position: relative; +} + +.toc-filter > input, +#toc_filter_input { + width: 100%; + height: 34px; + padding: 7px 12px 7px 33px; + color: var(--zenith-text); + background: var(--zenith-surface-soft); + border: 1px solid var(--zenith-border); + border-radius: 6px; + box-shadow: none; + font-size: 12px; +} + +.toc-filter .filter-icon { + position: absolute; + top: 9px; + left: 11px; + z-index: 1; + color: var(--zenith-text-faint); +} + +.sidetoc { + height: calc(100% - 62px); + padding: 4px 12px 30px 0; + overflow: auto; +} + +.toc { + padding: 0; + font-size: 13px; +} + +.toc ul { + padding-left: 0; + list-style: none; +} + +.toc .nav > li > a { + display: block; + margin: 1px 0; + padding: 7px 10px; + color: var(--zenith-text-soft); + border-radius: 5px; + font-weight: 550; + line-height: 1.35; + text-decoration: none; +} + +.toc .nav > li > a:hover { + color: var(--zenith-purple-700); + background: rgba(113, 56, 232, 0.06); +} + +.toc .nav > li.active > a, +.toc .nav > li > a.active { + color: var(--zenith-purple-700); + background: rgba(113, 56, 232, 0.1); + font-weight: 750; +} + +.toc .nav.level1 > li > a { + color: var(--zenith-text); + font-weight: 750; +} + +.toc .nav.level2 { + margin: 2px 0 9px 10px; + padding-left: 7px; + border-left: 1px solid var(--zenith-border); +} + +.toc .expand-stub { + color: var(--zenith-text-faint); +} + +#affix { + position: sticky; + top: calc(var(--zenith-header-height) + 28px); + max-height: calc(100vh - var(--zenith-header-height) - 56px); + padding-left: 16px; + overflow: auto; + border-left: 1px solid var(--zenith-border); +} + +#affix::before { + content: "ON THIS PAGE"; + display: block; + margin-bottom: 10px; + color: var(--zenith-text-faint); + font-size: 10px; + font-weight: 850; +} + +#affix ul { + padding: 0; + list-style: none; +} + +#affix a { + display: block; + padding: 4px 0; + color: var(--zenith-text-faint); + font-size: 12px; + line-height: 1.4; + text-decoration: none; +} + +#affix a:hover, +#affix a.active { + color: var(--zenith-purple-700); +} + +/* ========================================================================== + Search, footer, contribution, pagination + ========================================================================== */ + +#search-results { + max-width: 940px !important; + min-height: 65vh; + padding: 42px 24px !important; +} + +#search-results > .sr-items { + display: grid; + gap: 12px; +} + +#search-results .sr-item { + padding: 18px 20px; + background: var(--zenith-surface); + border: 1px solid var(--zenith-border); + border-radius: 7px; + transition: border-color 160ms ease, box-shadow 160ms ease; +} + +#search-results .sr-item:hover { + border-color: rgba(113, 56, 232, 0.36); + box-shadow: var(--zenith-shadow-small); +} + +#search-results .item-title { + color: var(--zenith-purple-700); + font-size: 17px; + font-weight: 750; + text-decoration: none; +} + +#search-results .item-href { + color: var(--zenith-text-faint); + font-size: 11px; +} + +#search-results .item-brief { + color: var(--zenith-text-soft); + font-size: 14px; +} + +.contribution { + max-width: var(--zenith-copy-width); + margin: 48px auto 0; + padding-top: 15px; + border-top: 1px solid var(--zenith-border); +} + +.edit-link { + color: var(--zenith-text-faint); + font-size: 12px; + text-decoration: none; +} + +.edit-link::before { + margin-right: 6px; +} + +.next-article { + max-width: var(--zenith-copy-width); + margin: 26px auto 0; + padding-top: 18px; + border-color: var(--zenith-border) !important; +} + +body > footer { + position: relative; + z-index: 2; + color: var(--zenith-text-faint) !important; + background: var(--zenith-surface-soft); + border-color: var(--zenith-border) !important; + font-size: 12px; +} + +body > footer .container-xxl { + max-width: var(--zenith-content-width) !important; + padding: 22px 18px !important; +} + +body > footer a { + color: var(--zenith-purple-700); +} + +/* ========================================================================== + Responsive behavior + ========================================================================== */ + +@media (max-width: 1180px) { + #navbar form.search { + width: 165px; + } + + .hero-grid { + grid-template-columns: minmax(0, 1fr) minmax(390px, 0.92fr); + gap: 34px; + } + + .hero-copy h1 { + font-size: 46px; + } + + .hero-product { + height: 390px; + } + + body:not([data-layout="landing"]) > main.container-xxl { + grid-template-columns: minmax(180px, 220px) minmax(0, 1fr); + column-gap: 28px; + } + + body:not([data-layout="landing"]) main > .affix { + display: none; + } +} + +@media (max-width: 991.98px) { + :root { + --zenith-header-height: 64px; + } + + #logo { + width: 196px; + height: 42px; + } + + .navbar-brand { + margin-right: 20px !important; + } + + #navbar .nav-link { + padding-right: 8px !important; + padding-left: 8px !important; + font-size: 13px; + } + + #navbar .nav-link::after { + right: 8px; + left: 8px; + } + + #navbar form.search { + display: none; + } + + .hero-grid { + grid-template-columns: 1fr; + gap: 22px; + padding-top: 52px; + padding-bottom: 56px; + } + + .hero-copy { + max-width: 690px; + margin: 0 auto; + text-align: center; + } + + .hero-copy h1, + .hero-lede { + margin-right: auto; + margin-left: auto; + } + + .hero-actions, + .backend-support { + justify-content: center; + } + + .hero-product { + width: 100%; + max-width: 680px; + height: 430px; + margin: 0 auto; + } + + .quickstart-grid { + grid-template-columns: 1fr; + gap: 42px; + } + + .quickstart-copy { + max-width: 680px; + } + + body:not([data-layout="landing"]) > main.container-xxl { + grid-template-columns: minmax(170px, 205px) minmax(0, 1fr); + padding-right: 18px !important; + padding-left: 18px !important; + column-gap: 24px; + } +} + +@media (max-width: 767.98px) { + body > header.bg-body, + body > header { + height: auto; + min-height: var(--zenith-header-height); + } + + .navbar > .container-xxl { + min-height: var(--zenith-header-height); + } + + .navbar .navbar-toggler, + .navbar button[data-bs-toggle="collapse"] { + color: var(--zenith-text); + border: 0 !important; + border-radius: 6px !important; + } + + .navbar-collapse { + position: absolute; + top: var(--zenith-header-height); + right: 0; + left: 0; + padding: 10px 18px 16px; + background: var(--zenith-surface); + border-bottom: 1px solid var(--zenith-border); + box-shadow: var(--zenith-shadow-medium); + } + + #navbar { + display: flex; + align-items: stretch; + flex-direction: column; + gap: 8px; + } + + #navbar .navbar-nav { + order: 1; + width: 100%; + align-items: stretch; + flex-direction: column; + gap: 2px; + } + + #navbar .nav-link { + padding: 10px 12px !important; + border-radius: 5px !important; + } + + #navbar .nav-link::after { + display: none; + } + + #navbar form.search { + display: block; + order: 2 !important; + width: 100%; + } + + .navbar .icons { + order: 3 !important; + justify-content: flex-start; + } + + .navbar .nav-cta { + order: 4; + width: 100%; + min-height: 40px; + margin-left: 0; + flex: 1 1 auto; + } + + .landing-shell { + width: calc(100% - 28px); + } + + .hero-grid { + padding-top: 42px; + padding-bottom: 44px; + } + + .hero-copy h1 { + font-size: 40px; + line-height: 1.08; + } + + .hero-lede { + font-size: 16px; + } + + .hero-product { + height: 390px; + } + + .product-tabs { + display: none; + } + + .feature-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .features-section, + .quickstart-section { + padding-top: 68px; + padding-bottom: 72px; + } + + .section-heading h2, + .quickstart-copy h2, + .landing-cta h2 { + font-size: 32px; + } + + .cta-inner { + align-items: flex-start; + flex-direction: column; + } + + body:not([data-layout="landing"]) > main.container-xxl { + display: block; + padding: 0 16px !important; + } + + body:not([data-layout="landing"]) main > .content { + padding-top: 18px; + } + + #sidetoggle { + position: static; + height: auto; + border-right: 0; + } + + .sidetoc { + height: auto; + } + + article h1 { + font-size: 31px; + } + + article h2 { + font-size: 23px; + } +} + +@media (max-width: 575.98px) { + #logo { + width: 178px; + height: 40px; + } + + .navbar > .container-xxl { + padding-right: 12px !important; + padding-left: 12px !important; + } + + .hero-eyebrow { + max-width: 100%; + flex-wrap: wrap; + justify-content: center; + gap: 5px; + border-radius: 7px; + line-height: 1.3; + } + + .eyebrow-separator { + margin: 0 2px; + } + + .hero-copy h1 { + font-size: 34px; + } + + .hero-actions .landing-button { + width: 100%; + } + + .backend-label { + width: 100%; + } + + .hero-product { + height: 330px; + } + + .product-toolbar { + height: 46px; + padding: 0 12px; + } + + .product-canvas { + height: calc(100% - 46px); + } + + .code-float { + width: 176px; + padding: 9px 10px; + } + + .code-float-left { + bottom: 17px; + left: 12px; + } + + .code-float-right { + top: 16px; + right: 12px; + } + + .backend-float { + display: none; + } + + .feature-grid { + grid-template-columns: 1fr; + } + + .feature-card { + min-height: 225px; + } + + .section-heading h2, + .quickstart-copy h2, + .landing-cta h2 { + font-size: 29px; + } + + .code-window pre { + min-height: 300px; + padding: 22px 18px; + font-size: 11px; + } + + .quickstart-links .landing-button, + .cta-actions .landing-button { + width: 100%; + } + + .cta-actions { + width: 100%; + } + + article h1 { + font-size: 28px; + } + + article h2 { + margin-top: 35px; + font-size: 21px; + } + + article pre, + .codewrapper pre { + padding: 16px; + font-size: 11px; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +@media print { + body { + padding-top: 0 !important; + } + + body > header, + #sidetoggle, + main > .affix, + body > footer, + .contribution, + .next-article { + display: none !important; + } + + body:not([data-layout="landing"]) > main.container-xxl { + display: block; + max-width: none !important; + padding: 0 !important; + } + + article { + max-width: none !important; + } +} + +/* ========================================================================== + Renderer Console V2 + ========================================================================== */ + +:root { + --zenith-navy-950: #071211; + --zenith-navy-900: #102a2b; + --zenith-navy-800: #173f41; + --zenith-purple-700: #166fbd; + --zenith-purple-600: #2457e6; + --zenith-magenta-600: #13866d; + --zenith-magenta-500: #14a07d; + --zenith-pink-500: #e86f51; + --zenith-blue-500: #2457e6; + --zenith-cyan-500: #1689c2; + --zenith-orange-500: #e86f51; + --zenith-green-500: #13a67a; + --zenith-text: #102a2b; + --zenith-text-soft: #536a6b; + --zenith-text-faint: #829293; + --zenith-surface: #f7f8f6; + --zenith-surface-soft: #eef1ee; + --zenith-surface-tint: #edf5f1; + --zenith-border: #dce2df; + --zenith-border-strong: #c8d1cd; + --zenith-code: #0b1717; + --zenith-brand-gradient: linear-gradient(110deg, #2457e6 0%, #1689c2 48%, #13a67a 100%); + --zenith-shadow-small: 0 2px 8px rgba(10, 31, 29, 0.07); + --zenith-shadow-medium: 0 12px 30px rgba(10, 31, 29, 0.12); + --zenith-shadow-large: 0 28px 70px rgba(2, 14, 13, 0.3); +} + +[data-bs-theme="dark"] { + --zenith-text: #e9f3f0; + --zenith-text-soft: #a9bbb7; + --zenith-text-faint: #708480; + --zenith-surface: #0d1514; + --zenith-surface-soft: #131e1c; + --zenith-surface-tint: #14241f; + --zenith-border: #273633; + --zenith-border-strong: #3a4d49; + --zenith-code: #07100f; +} + +body { + background: var(--zenith-surface); +} + +a { + color: #166fbd; + text-decoration-color: rgba(22, 111, 189, 0.32); +} + +a:hover { + color: #13866d; +} + +body > header.bg-body, +body > header { + background: rgba(247, 248, 246, 0.95) !important; + border-bottom-color: rgba(16, 42, 43, 0.13) !important; + box-shadow: none; +} + +[data-bs-theme="dark"] body > header.bg-body, +[data-bs-theme="dark"] body > header { + background: rgba(13, 21, 20, 0.95) !important; +} + +#navbar .nav-link { + color: #647575 !important; +} + +#navbar .nav-link::after { + height: 3px; + background: #13a67a; +} + +#navbar .nav-link:hover, +#navbar .nav-link.active { + color: #102a2b !important; +} + +[data-bs-theme="dark"] #navbar .nav-link:hover, +[data-bs-theme="dark"] #navbar .nav-link.active { + color: #eef8f5 !important; +} + +#navbar form.search > input { + background: #edf0ed; + border-color: #dce2df; +} + +[data-bs-theme="dark"] #navbar form.search > input { + background: #131e1c; + border-color: #273633; +} + +#navbar form.search > input:focus { + border-color: #1689c2; + box-shadow: 0 0 0 3px rgba(22, 137, 194, 0.13); +} + +.navbar .nav-cta { + color: #071211 !important; + background: #48d7a6; + box-shadow: none; +} + +.navbar .nav-cta:hover { + color: #071211 !important; + background: #64e3b6; + box-shadow: 0 8px 20px rgba(19, 166, 122, 0.2); +} + +.navbar .icons .btn:hover, +.navbar .icons > a.btn:hover, +.navbar .icons .dropdown > a.btn:hover { + color: #13866d !important; + background: rgba(19, 166, 122, 0.09) !important; +} + +.landing-page { + background: #f7f8f6; +} + +[data-bs-theme="dark"] .landing-page { + background: #0d1514; +} + +.render-hero { + position: relative; + min-height: 620px; + overflow: hidden; + color: #ffffff; + background: #071211; +} + +.render-hero > a:has(> .render-hero-media) { + position: absolute; + inset: 0; + display: block; + color: transparent; + text-decoration: none; + pointer-events: none; + -webkit-mask-image: linear-gradient(to right, transparent 40%, #000 64%); + mask-image: linear-gradient(to right, transparent 40%, #000 64%); +} + +.render-hero-media { + position: absolute; + top: 0; + right: -21%; + bottom: auto; + left: auto; + width: 82%; + height: 100%; + max-width: none; + object-fit: cover; + object-position: 58% center; + border-radius: 0 !important; + filter: saturate(0.95) contrast(1.06) brightness(0.9); + transform: none; + -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 72%, transparent 100%); + mask-image: linear-gradient(to bottom, #000 0%, #000 72%, transparent 100%); + animation: render-scene-in 1000ms var(--zenith-ease) both; +} + +.render-hero-scrim { + position: absolute; + inset: 0; + background: linear-gradient(90deg, #071211 0%, #071211 30%, rgba(7, 18, 17, 0.96) 42%, rgba(7, 18, 17, 0.55) 62%, rgba(7, 18, 17, 0.08) 100%), linear-gradient(0deg, rgba(7, 18, 17, 0.62) 0%, transparent 38%); +} + +.render-grid { + position: absolute; + inset: 0 55% 0 0; + opacity: 0.16; + background-image: linear-gradient(rgba(72, 215, 166, 0.14) 1px, transparent 1px), linear-gradient(90deg, rgba(72, 215, 166, 0.14) 1px, transparent 1px); + background-size: 48px 48px; + -webkit-mask-image: linear-gradient(to right, #000, transparent); + mask-image: linear-gradient(to right, #000, transparent); +} + +.render-hero-inner { + position: relative; + z-index: 2; + display: flex; + min-height: 620px; + align-items: center; + justify-content: space-between; + padding-top: 48px; + padding-bottom: 48px; +} + +.render-hero .hero-copy { + width: min(650px, 58%); + margin: 0; + padding: 0; + text-align: left; +} + +.render-hero .hero-eyebrow { + margin-bottom: 28px; + color: #9db2ae; + background: rgba(6, 25, 22, 0.6); + border-color: rgba(72, 215, 166, 0.28); + border-radius: 4px; + box-shadow: none; + backdrop-filter: blur(8px); +} + +.render-hero .eyebrow-dot { + background: #48d7a6; + box-shadow: 0 0 0 3px rgba(72, 215, 166, 0.14); +} + +.render-hero .eyebrow-separator { + background: #52716a; +} + +.render-hero .hero-copy h1 { + max-width: 650px; + margin: 0 0 24px; + padding: 0 0 6px; + color: #f4faf8; + font-size: 62px; + font-weight: 760; + line-height: 1.08; + overflow: visible; + text-shadow: 0 2px 24px rgba(0, 0, 0, 0.28); +} + +.render-hero .hero-copy h1 span { + display: block; + width: fit-content; + margin-top: -4px; + padding: 10px 0; + color: #48d7a6; + background: none; + -webkit-text-fill-color: currentColor; +} + +.render-hero .hero-lede { + max-width: 620px; + margin-bottom: 30px; + padding-bottom: 2px; + color: #b8c8c4; + font-size: 17px; + line-height: 1.8; + overflow: visible; +} + +.render-hero .landing-button-primary { + color: #071211 !important; + background: #48d7a6; + box-shadow: none; +} + +.render-hero .landing-button-primary:hover { + color: #071211 !important; + background: #65e4b8; + box-shadow: 0 12px 28px rgba(19, 166, 122, 0.23); +} + +.render-hero .landing-button-secondary { + color: #eef8f5 !important; + background: rgba(7, 18, 17, 0.46); + border-color: rgba(209, 228, 223, 0.34); + box-shadow: none; + backdrop-filter: blur(8px); +} + +.render-hero .landing-button-secondary:hover { + color: #ffffff !important; + background: rgba(25, 51, 47, 0.8); + border-color: rgba(72, 215, 166, 0.6); +} + +.render-hero .backend-support { + gap: 7px; + margin-top: 34px; +} + +.render-hero .backend-label { + color: #718b85; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 9px; + letter-spacing: 0; +} + +.render-hero .backend-pill { + min-height: 25px; + color: #c8d7d3; + background: rgba(7, 18, 17, 0.54); + border-color: rgba(173, 202, 194, 0.22); + border-radius: 4px; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 9px; + font-weight: 650; + backdrop-filter: blur(8px); +} + +.render-caption { + position: absolute; + right: 0; + bottom: 42px; + display: flex; + min-width: 230px; + flex-direction: column; + padding: 14px 16px; + color: #d8e6e2; + background: rgba(5, 15, 14, 0.66); + border-left: 2px solid #48d7a6; + backdrop-filter: blur(10px); +} + +.render-caption span { + margin-bottom: 3px; + color: #70a99a; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 9px; + font-weight: 700; +} + +.render-caption strong { + color: #edf7f4; + font-size: 13px; +} + +.signal-rail { + color: #dbe8e4; + background: #102a2b; + border-top: 1px solid #1f4945; + border-bottom: 1px solid #1f4945; +} + +.signal-rail-inner { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.signal-rail-inner > div { + display: flex; + min-height: 94px; + align-items: center; + padding: 18px 24px; + border-right: 1px solid #28504b; +} + +.signal-rail-inner > div:first-child { + border-left: 1px solid #28504b; +} + +.signal-rail strong { + min-width: 72px; + color: #48d7a6; + font-family: "Aptos Display", "Segoe UI Variable Display", sans-serif; + font-size: 25px; + font-weight: 760; +} + +.signal-rail span { + color: #8ea7a1; + font-size: 11px; + font-weight: 650; + text-transform: uppercase; +} + +.features-section { + padding: 102px 0 112px; + background: #f7f8f6; +} + +[data-bs-theme="dark"] .features-section { + background: #0d1514; +} + +.section-heading { + max-width: 760px; + margin: 0 0 54px; + text-align: left; +} + +.section-kicker { + margin-bottom: 14px; + color: #13866d; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 10px; + font-weight: 750; +} + +.section-heading h2, +.quickstart-copy h2, +.landing-cta h2 { + color: #102a2b; + font-size: 41px; + line-height: 1.08; +} + +[data-bs-theme="dark"] .section-heading h2, +[data-bs-theme="dark"] .quickstart-copy h2 { + color: #e9f3f0; +} + +.section-heading h2 span { + color: #166fbd; + background: none; + -webkit-text-fill-color: currentColor; +} + +.section-heading p { + max-width: 660px; + margin: 18px 0 0; +} + +.feature-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0; + border-top: 1px solid var(--zenith-border-strong); + border-left: 1px solid var(--zenith-border-strong); +} + +.feature-card { + position: relative; + min-height: 274px; + padding: 28px; + background: transparent; + border: 0; + border-right: 1px solid var(--zenith-border-strong); + border-bottom: 1px solid var(--zenith-border-strong); + border-radius: 0; + box-shadow: none; +} + +.feature-card::before { + position: absolute; + top: 28px; + right: 28px; + color: #a3afac; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 10px; + font-weight: 650; +} + +.feature-card:nth-child(1)::before { content: "01"; } +.feature-card:nth-child(2)::before { content: "02"; } +.feature-card:nth-child(3)::before { content: "03"; } +.feature-card:nth-child(4)::before { content: "04"; } +.feature-card:nth-child(5)::before { content: "05"; } +.feature-card:nth-child(6)::before { content: "06"; } + +.feature-card:hover { + background: #eef4f1; + border-color: var(--zenith-border-strong); + box-shadow: none; + transform: none; +} + +[data-bs-theme="dark"] .feature-card:hover { + background: #13211e; +} + +.feature-icon { + width: 38px; + height: 38px; + margin-bottom: 26px; + color: #eaf7f3; + background: #173f41 !important; + border: 1px solid #2a5a56; + border-radius: 5px; + font-size: 17px; +} + +.feature-card:nth-child(2) .feature-icon, +.feature-card:nth-child(5) .feature-icon { + color: #eef3ff; + background: #17346f !important; + border-color: #28539c; +} + +.feature-card:nth-child(4) .feature-icon { + color: #fff0eb; + background: #6d3025 !important; + border-color: #9b4f3c; +} + +.feature-card h3 { + color: var(--zenith-text); + font-size: 19px; +} + +.feature-card > a { + color: #13866d; +} + +.quickstart-section { + padding: 102px 0; + background: #e8edeb; + border-color: #d3dcda; +} + +[data-bs-theme="dark"] .quickstart-section { + background: #111c1a; + border-color: #273633; +} + +.code-window { + background: #0b1717; + border-color: #263e3a; + border-radius: 5px; + box-shadow: 20px 22px 0 #cad5d1; +} + +[data-bs-theme="dark"] .code-window { + box-shadow: 20px 22px 0 #182724; +} + +.code-window-bar { + background: #102a2b; + border-bottom-color: #284946; +} + +.code-keyword { + color: #72a1ff; +} + +.code-value { + color: #64e3b6; +} + +.code-comment { + color: #64807a; +} + +.landing-cta { + color: #071211; + background: #48d7a6; +} + +.landing-cta .section-kicker, +.landing-cta h2, +.landing-cta p { + color: #071211; +} + +.landing-cta p { + opacity: 0.68; +} + +.landing-button-light { + color: #f4faf8 !important; + background: #102a2b; + box-shadow: none; +} + +.landing-button-light:hover { + color: #ffffff !important; + background: #071211; +} + +.landing-button-ghost { + color: #102a2b !important; + background: transparent; + border-color: rgba(7, 18, 17, 0.42); +} + +.landing-button-ghost:hover { + color: #071211 !important; + background: rgba(255, 255, 255, 0.24); + border-color: #071211; +} + +article h2 { + border-bottom-color: var(--zenith-border); +} + +article li::marker, +.breadcrumb-item.active, +.breadcrumb-item a:hover, +.toc .nav > li > a:hover, +.toc .nav > li.active > a, +.toc .nav > li > a.active, +#affix a:hover, +#affix a.active, +.edit-link:hover, +body > footer a, +#search-results .item-title { + color: #13866d; +} + +.toc .nav > li > a:hover { + background: rgba(19, 166, 122, 0.07); +} + +.toc .nav > li.active > a, +.toc .nav > li > a.active { + background: rgba(19, 166, 122, 0.11); +} + +code { + color: #166fbd; + background: rgba(22, 111, 189, 0.08); +} + +[data-bs-theme="dark"] code { + color: #7faaff; + background: rgba(36, 87, 230, 0.14); +} + +article blockquote { + background: #edf5f1; + border-left-color: #13a67a; +} + +[data-bs-theme="dark"] article blockquote { + background: #14241f; +} + +@keyframes render-scene-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@media (max-width: 991.98px) { + .render-hero, + .render-hero-inner { + min-height: 720px; + } + + .render-hero-media { + inset: 0; + width: 100%; + opacity: 0.62; + object-position: 58% center; + transform: scale(1.015); + transform-origin: center; + -webkit-mask-image: none; + mask-image: none; + animation: none; + } + + .render-hero > a:has(> .render-hero-media) { + -webkit-mask-image: none; + mask-image: none; + } + + .render-hero-scrim { + background: linear-gradient(90deg, rgba(7, 18, 17, 0.98), rgba(7, 18, 17, 0.82) 65%, rgba(7, 18, 17, 0.4)), linear-gradient(0deg, rgba(7, 18, 17, 0.78), transparent 50%); + } + + .render-hero .hero-copy { + width: min(680px, 86%); + max-width: none; + margin: 0; + text-align: left; + } + + .render-hero .hero-actions, + .render-hero .backend-support { + justify-content: flex-start; + } + + .render-caption { + right: 0; + bottom: 30px; + } + + .signal-rail-inner { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .signal-rail-inner > div:nth-child(3) { + border-left: 1px solid #28504b; + border-top: 1px solid #28504b; + } + + .signal-rail-inner > div:nth-child(4) { + border-top: 1px solid #28504b; + } +} + +@media (max-width: 767.98px) { + .render-hero, + .render-hero-inner { + min-height: 680px; + } + + .render-hero-inner { + align-items: flex-start; + padding-top: 62px; + padding-bottom: 48px; + } + + .render-hero .hero-copy { + width: 100%; + } + + .render-hero .hero-copy h1 { + font-size: 43px; + } + + .render-caption { + display: none; + } + + .features-section, + .quickstart-section { + padding-top: 76px; + padding-bottom: 80px; + } + + .feature-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 575.98px) { + .render-hero, + .render-hero-inner { + min-height: 720px; + } + + .render-hero-media { + object-position: 62% center; + } + + .render-hero .hero-copy h1 { + font-size: 34px; + line-height: 1.04; + } + + .render-hero .hero-copy h1 span { + margin-top: 0; + padding: 6px 0 7px; + } + + .render-hero .hero-eyebrow { + justify-content: flex-start; + font-size: 8px; + } + + .render-hero .hero-lede { + font-size: 15px; + } + + .render-hero .backend-label { + width: 100%; + } + + .signal-rail-inner { + grid-template-columns: 1fr; + } + + .signal-rail-inner > div, + .signal-rail-inner > div:first-child, + .signal-rail-inner > div:nth-child(3) { + min-height: 70px; + border-top: 1px solid #28504b; + border-right: 1px solid #28504b; + border-left: 1px solid #28504b; + } + + .signal-rail-inner > div:first-child { + border-top: 0; + } + + .section-heading h2, + .quickstart-copy h2, + .landing-cta h2 { + font-size: 31px; + } + + .feature-grid { + grid-template-columns: 1fr; + } + + .feature-card { + min-height: 240px; + } + + .code-window { + box-shadow: 10px 12px 0 #cad5d1; + } +} + +/* ========================================================================== + Spectral .NET Palette + ========================================================================== */ + +:root { + --zenith-navy-950: #100d16; + --zenith-navy-900: #231e2e; + --zenith-navy-800: #342b45; + --zenith-purple-700: #512bd4; + --zenith-purple-600: #6d44e5; + --zenith-magenta-600: #512bd4; + --zenith-magenta-500: #7c4dff; + --zenith-pink-500: #ff7166; + --zenith-blue-500: #536dfe; + --zenith-cyan-500: #34c9f5; + --zenith-orange-500: #ff8a5c; + --zenith-green-500: #34c9f5; + --zenith-text: #231e2e; + --zenith-text-soft: #655f70; + --zenith-text-faint: #918a9d; + --zenith-surface: #fbfafc; + --zenith-surface-soft: #f3f1f7; + --zenith-surface-tint: #f7f3ff; + --zenith-border: #e1dce8; + --zenith-border-strong: #cec6da; + --zenith-code: #100d16; + --zenith-brand-gradient: linear-gradient(110deg, #512bd4 0%, #7150e8 56%, #34c9f5 100%); + --zenith-shadow-small: 0 2px 8px rgba(41, 27, 69, 0.07); + --zenith-shadow-medium: 0 12px 30px rgba(41, 27, 69, 0.12); + --zenith-shadow-large: 0 28px 70px rgba(10, 6, 18, 0.34); +} + +[data-bs-theme="dark"] { + --zenith-text: #f5f1fa; + --zenith-text-soft: #b9b1c4; + --zenith-text-faint: #81798d; + --zenith-surface: #100d16; + --zenith-surface-soft: #17121f; + --zenith-surface-tint: #1d1728; + --zenith-border: #312940; + --zenith-border-strong: #463a5a; + --zenith-code: #0b0910; +} + +body, +.landing-page, +.features-section { + background: #fbfafc; +} + +[data-bs-theme="dark"] body, +[data-bs-theme="dark"] .landing-page, +[data-bs-theme="dark"] .features-section { + background: #100d16; +} + +a { + color: #512bd4; + text-decoration-color: rgba(81, 43, 212, 0.3); +} + +a:hover { + color: #7652e8; +} + +body > header.bg-body, +body > header { + background: rgba(251, 250, 252, 0.95) !important; + border-bottom-color: rgba(35, 30, 46, 0.12) !important; +} + +[data-bs-theme="dark"] body > header.bg-body, +[data-bs-theme="dark"] body > header { + background: rgba(16, 13, 22, 0.95) !important; +} + +#navbar .nav-link { + color: #70697c !important; +} + +#navbar .nav-link::after { + background: #512bd4; +} + +#navbar .nav-link:hover, +#navbar .nav-link.active { + color: #231e2e !important; +} + +[data-bs-theme="dark"] #navbar .nav-link:hover, +[data-bs-theme="dark"] #navbar .nav-link.active { + color: #f7f3ff !important; +} + +#navbar form.search > input { + background: #f2f0f5; + border-color: #e1dce8; +} + +[data-bs-theme="dark"] #navbar form.search > input { + background: #191421; + border-color: #312940; +} + +#navbar form.search > input:focus { + border-color: #7150e8; + box-shadow: 0 0 0 3px rgba(81, 43, 212, 0.13); +} + +.navbar .nav-cta { + color: #ffffff !important; + background: #512bd4; +} + +.navbar .nav-cta:hover { + color: #ffffff !important; + background: #6540dd; + box-shadow: 0 8px 20px rgba(81, 43, 212, 0.24); +} + +.navbar .icons .btn:hover, +.navbar .icons > a.btn:hover, +.navbar .icons .dropdown > a.btn:hover { + color: #512bd4 !important; + background: rgba(81, 43, 212, 0.08) !important; +} + +.render-hero { + background: #100d16; +} + +.render-hero-scrim { + background: linear-gradient(90deg, #100d16 0%, #100d16 30%, rgba(16, 13, 22, 0.96) 42%, rgba(16, 13, 22, 0.54) 63%, rgba(16, 13, 22, 0.06) 100%), linear-gradient(0deg, #100d16 0%, rgba(16, 13, 22, 0.9) 7%, rgba(16, 13, 22, 0.42) 17%, transparent 30%); +} + +.features-section { + background: #fbfafc; +} + +[data-bs-theme="dark"] .features-section { + background: #100d16; +} + +.render-grid { + opacity: 0.14; + background-image: linear-gradient(rgba(52, 201, 245, 0.18) 1px, transparent 1px), linear-gradient(90deg, rgba(52, 201, 245, 0.18) 1px, transparent 1px); +} + +.render-hero .hero-eyebrow { + color: #bdb5ca; + background: rgba(22, 16, 31, 0.62); + border-color: rgba(155, 123, 255, 0.36); +} + +.render-hero .eyebrow-dot { + background: #9b7bff; + box-shadow: 0 0 0 3px rgba(155, 123, 255, 0.15); +} + +.render-hero .eyebrow-separator { + background: #655873; +} + +.render-hero .hero-copy h1 span { + color: #43d8ff; +} + +.render-hero .hero-lede { + color: #c5bdce; +} + +.render-hero .landing-button-primary { + color: #ffffff !important; + background: #512bd4; +} + +.render-hero .landing-button-primary:hover { + color: #ffffff !important; + background: #6844df; + box-shadow: 0 12px 28px rgba(81, 43, 212, 0.3); +} + +.render-hero .landing-button-secondary { + color: #f8f5fc !important; + background: rgba(16, 13, 22, 0.5); + border-color: rgba(222, 213, 235, 0.36); +} + +.render-hero .landing-button-secondary:hover { + background: rgba(43, 32, 57, 0.84); + border-color: rgba(67, 216, 255, 0.7); +} + +.render-hero .backend-label { + color: #8b8298; +} + +.render-hero .backend-pill { + color: #d2cadb; + background: rgba(16, 13, 22, 0.58); + border-color: rgba(206, 194, 221, 0.24); +} + +.render-caption { + color: #e2dce8; + background: rgba(13, 10, 19, 0.68); + border-left-color: #43d8ff; +} + +.render-caption span { + color: #8bdff5; +} + +.signal-rail { + color: #ebe5f1; + background: #1a1423; + border-color: #392d4c; +} + +.signal-rail-inner > div, +.signal-rail-inner > div:first-child { + border-color: #392d4c; +} + +.signal-rail strong { + color: #9b7bff; +} + +.signal-rail-inner > div:nth-child(2) strong { + color: #43d8ff; +} + +.signal-rail-inner > div:nth-child(3) strong { + color: #ff8a70; +} + +.signal-rail-inner > div:nth-child(4) strong { + color: #c2aaff; +} + +.signal-rail span { + color: #9e94aa; +} + +.section-kicker { + color: #512bd4; +} + +.section-heading h2, +.quickstart-copy h2 { + color: #231e2e; +} + +[data-bs-theme="dark"] .section-heading h2, +[data-bs-theme="dark"] .quickstart-copy h2 { + color: #f5f1fa; +} + +.section-heading h2 span { + color: #512bd4; +} + +.feature-card:hover { + background: #f4f0fa; +} + +[data-bs-theme="dark"] .feature-card:hover { + background: #1a1423; +} + +.feature-icon { + color: #f8f5ff; + background: #512bd4 !important; + border-color: #7652e8; +} + +.feature-card:nth-child(2) .feature-icon, +.feature-card:nth-child(5) .feature-icon { + color: #071116; + background: #43d8ff !important; + border-color: #7ae5ff; +} + +.feature-card:nth-child(4) .feature-icon { + color: #24100d; + background: #ff8a70 !important; + border-color: #ffad9d; +} + +.feature-card > a { + color: #512bd4; +} + +.quickstart-section { + background: #f0edf4; + border-color: #ddd6e5; +} + +[data-bs-theme="dark"] .quickstart-section { + background: #17121f; + border-color: #312940; +} + +.code-window { + background: #100d16; + border-color: #3b2f4f; + box-shadow: 20px 22px 0 #d8d1e1; +} + +[data-bs-theme="dark"] .code-window { + box-shadow: 20px 22px 0 #251d31; +} + +.code-window-bar { + background: #21182d; + border-bottom-color: #46365d; +} + +.code-keyword { + color: #b69cff; +} + +.code-value { + color: #64dcff; +} + +.code-comment { + color: #81778e; +} + +.landing-cta { + color: #ffffff; + background: #512bd4; +} + +.landing-cta .section-kicker, +.landing-cta h2, +.landing-cta p { + color: #ffffff; +} + +.landing-button-light { + color: #512bd4 !important; + background: #ffffff; +} + +.landing-button-light:hover { + color: #3e1faa !important; + background: #f6f1ff; +} + +.landing-button-ghost { + color: #ffffff !important; + border-color: rgba(255, 255, 255, 0.48); +} + +.landing-button-ghost:hover { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.13); + border-color: #ffffff; +} + +article li::marker, +.breadcrumb-item.active, +.breadcrumb-item a:hover, +.toc .nav > li > a:hover, +.toc .nav > li.active > a, +.toc .nav > li > a.active, +#affix a:hover, +#affix a.active, +.edit-link:hover, +body > footer a, +#search-results .item-title { + color: #512bd4; +} + +.toc .nav > li > a:hover { + background: rgba(81, 43, 212, 0.07); +} + +.toc .nav > li.active > a, +.toc .nav > li > a.active { + background: rgba(81, 43, 212, 0.11); +} + +code { + color: #512bd4; + background: rgba(81, 43, 212, 0.08); +} + +[data-bs-theme="dark"] code { + color: #b69cff; + background: rgba(155, 123, 255, 0.13); +} + +article blockquote { + background: #f6f1ff; + border-left-color: #512bd4; +} + +[data-bs-theme="dark"] article blockquote { + background: #1d1728; +} + +@media (max-width: 991.98px) { + .render-hero-scrim { + background: linear-gradient(90deg, rgba(16, 13, 22, 0.98), rgba(16, 13, 22, 0.83) 65%, rgba(16, 13, 22, 0.42)), linear-gradient(0deg, rgba(16, 13, 22, 0.8), transparent 50%); } -.feature-list-title { - font-size: 0.875rem; - font-weight: 600; - color: var(--text-primary); + .signal-rail-inner > div:nth-child(3), + .signal-rail-inner > div:nth-child(4) { + border-color: #392d4c; + } +} + +@media (max-width: 575.98px) { + .signal-rail-inner > div, + .signal-rail-inner > div:first-child, + .signal-rail-inner > div:nth-child(3) { + border-color: #392d4c; + } + + .code-window { + box-shadow: 10px 12px 0 #d8d1e1; + } + + [data-bs-theme="dark"] .code-window { + box-shadow: 10px 12px 0 #251d31; + } +} + +/* ========================================================================== + Layout and border refinement + ========================================================================== */ + +.landing-button { + gap: 9px; +} + +.landing-button i { + flex: 0 0 auto; + margin: 0; +} + +.signal-rail-inner > div { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-content: center; + align-items: center; + gap: 16px; + padding: 22px 26px; +} + +.signal-rail strong { + min-width: 0; + line-height: 1; + transform: translateY(-4px); white-space: nowrap; - min-width: 140px; } -.feature-list-desc { - font-size: 0.875rem; - color: var(--text-secondary); - line-height: 1.55; +.signal-rail span { + min-width: 0; + line-height: 1; + transform: translateY(-4px); +} + +@media (min-width: 768px) { + .navbar > .container-xxl { + max-width: none !important; + padding-right: clamp(18px, 1.75vw, 32px) !important; + padding-left: clamp(18px, 1.75vw, 32px) !important; + } + + #logo { + width: 168px; + } + + .navbar-brand { + margin-right: 26px !important; + } + + #navbar { + gap: 14px; + } + + #navbar .navbar-nav { + margin-right: 0 !important; + } + + #navbar form.search { + margin-left: auto !important; + } + + .navbar .icons { + margin-left: 0 !important; + } + + .navbar .nav-cta { + margin-left: 0; + } +} + +.feature-grid { + gap: 16px; + border: 0; +} + +.landing-page .feature-card { + min-height: 280px; + padding: 30px; + background: var(--zenith-surface); + border: 1px solid var(--zenith-border); + border-radius: 7px; +} + +[data-bs-theme="dark"] .landing-page .feature-card { + background: #15101d; + border-color: #312940; +} + +.landing-page .feature-card::before { + top: 30px; + right: 30px; +} + +.landing-page .feature-card:hover { + border-color: rgba(81, 43, 212, 0.42); + box-shadow: 0 10px 28px rgba(35, 30, 46, 0.08); + transform: translateY(-2px); +} + +[data-bs-theme="dark"] .landing-page .feature-card:hover { + border-color: rgba(155, 123, 255, 0.46); + box-shadow: 0 12px 30px rgba(4, 3, 7, 0.28); +} + +article pre, +.codewrapper pre { + padding: 22px 24px; + border-color: rgba(81, 43, 212, 0.24) !important; + border-radius: 6px !important; + box-shadow: none; +} + +article pre code.hljs, +.codewrapper pre code.hljs { + padding: 0; + overflow: visible; + color: inherit; + background: transparent !important; +} + +@media (max-width: 991.98px) { + .feature-grid { + gap: 14px; + } +} + +@media (max-width: 575.98px) { + .signal-rail-inner > div { + padding: 18px 20px; + } + + .landing-page .feature-card { + min-height: 0; + padding: 24px; + } + + .landing-page .feature-card::before { + top: 24px; + right: 24px; + } + + article pre, + .codewrapper pre { + padding: 18px; + } +} + +/* ========================================================================== + Theme-aware landing hero + ========================================================================== */ + +:root:not([data-bs-theme="dark"]) .render-hero { + color: #231e2e; + background-color: #fbfafc; + background-image: linear-gradient(135deg, rgba(81, 43, 212, 0.07) 0%, rgba(81, 43, 212, 0.025) 34%, rgba(52, 201, 245, 0.055) 72%, transparent 100%); +} + +:root:not([data-bs-theme="dark"]) .render-hero-media { + filter: saturate(0.82) contrast(0.92) brightness(1.08); +} + +:root:not([data-bs-theme="dark"]) .render-hero-scrim { + background: linear-gradient(90deg, #fbfafc 0%, #fbfafc 30%, rgba(251, 250, 252, 0.97) 43%, rgba(251, 250, 252, 0.7) 65%, rgba(251, 250, 252, 0.08) 100%), linear-gradient(0deg, #fbfafc 0%, rgba(251, 250, 252, 0.88) 7%, rgba(251, 250, 252, 0.34) 17%, transparent 30%); +} + +:root:not([data-bs-theme="dark"]) .render-grid { + inset: 0; + opacity: 0.22; + background-image: linear-gradient(rgba(81, 43, 212, 0.12) 1px, transparent 1px), linear-gradient(90deg, rgba(52, 201, 245, 0.14) 1px, transparent 1px); + background-size: 64px 64px; + -webkit-mask-image: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0.72) 42%, transparent 76%); + mask-image: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0.72) 42%, transparent 76%); +} + +:root:not([data-bs-theme="dark"]) .features-section { + background-color: #fbfafc; + background-image: linear-gradient(to bottom, #fbfafc 0%, rgba(251, 250, 252, 0.92) 24px, rgba(251, 250, 252, 0) 88px, rgba(251, 250, 252, 0) calc(100% - 88px), #fbfafc 100%), linear-gradient(145deg, rgba(81, 43, 212, 0.055) 0%, transparent 34%), linear-gradient(35deg, transparent 62%, rgba(52, 201, 245, 0.055) 100%), linear-gradient(rgba(81, 43, 212, 0.035) 1px, transparent 1px), linear-gradient(90deg, rgba(81, 43, 212, 0.035) 1px, transparent 1px); + background-size: auto, auto, auto, 72px 72px, 72px 72px; +} + +[data-bs-theme="dark"] .render-hero { + background-color: #100d16; + background-image: linear-gradient(135deg, rgba(155, 123, 255, 0.1) 0%, rgba(155, 123, 255, 0.025) 38%, rgba(67, 216, 255, 0.06) 78%, transparent 100%); +} + +[data-bs-theme="dark"] .render-grid { + inset: 0; + opacity: 0.18; + background-image: linear-gradient(rgba(155, 123, 255, 0.14) 1px, transparent 1px), linear-gradient(90deg, rgba(67, 216, 255, 0.12) 1px, transparent 1px); + background-size: 64px 64px; + -webkit-mask-image: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0.68) 42%, transparent 76%); + mask-image: linear-gradient(90deg, #000 0%, rgba(0, 0, 0, 0.68) 42%, transparent 76%); +} + +[data-bs-theme="dark"] .features-section { + background-color: #100d16; + background-image: linear-gradient(to bottom, #100d16 0%, rgba(16, 13, 22, 0.92) 24px, rgba(16, 13, 22, 0) 88px, rgba(16, 13, 22, 0) calc(100% - 88px), #100d16 100%), linear-gradient(145deg, rgba(155, 123, 255, 0.075) 0%, transparent 34%), linear-gradient(35deg, transparent 62%, rgba(67, 216, 255, 0.045) 100%), linear-gradient(rgba(155, 123, 255, 0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(155, 123, 255, 0.04) 1px, transparent 1px); + background-size: auto, auto, auto, 72px 72px, 72px 72px; +} + +:root:not([data-bs-theme="dark"]) .render-hero .hero-eyebrow { + color: #62586e; + background: rgba(255, 255, 255, 0.72); + border-color: rgba(81, 43, 212, 0.24); +} + +:root:not([data-bs-theme="dark"]) .render-hero .eyebrow-dot { + background: #512bd4; + box-shadow: 0 0 0 3px rgba(81, 43, 212, 0.12); +} + +:root:not([data-bs-theme="dark"]) .render-hero .eyebrow-separator { + background: #c6bdcf; +} + +:root:not([data-bs-theme="dark"]) .render-hero .hero-copy h1 { + color: #2b2435; + text-shadow: none; +} + +:root:not([data-bs-theme="dark"]) .render-hero .hero-copy h1 span { + color: #512bd4; +} + +:root:not([data-bs-theme="dark"]) .render-hero .hero-lede { + color: #655d70; +} + +:root:not([data-bs-theme="dark"]) .render-hero .landing-button-secondary { + color: #342b45 !important; + background: rgba(255, 255, 255, 0.72); + border-color: rgba(52, 43, 69, 0.22); +} + +:root:not([data-bs-theme="dark"]) .render-hero .landing-button-secondary:hover { + color: #512bd4 !important; + background: rgba(255, 255, 255, 0.94); + border-color: rgba(81, 43, 212, 0.46); +} + +:root:not([data-bs-theme="dark"]) .render-hero .backend-label { + color: #746b7e; +} + +:root:not([data-bs-theme="dark"]) .render-hero .backend-pill { + color: #40374b; + background: rgba(255, 255, 255, 0.68); + border-color: rgba(64, 52, 81, 0.2); +} + +@media (max-width: 991.98px) { + :root:not([data-bs-theme="dark"]) .render-hero-scrim { + background: linear-gradient(90deg, rgba(251, 250, 252, 0.98), rgba(251, 250, 252, 0.88) 65%, rgba(251, 250, 252, 0.52)), linear-gradient(0deg, rgba(251, 250, 252, 0.92), transparent 54%); + } +} + +@media (min-width: 768px) and (max-width: 991.98px) { + [data-bs-theme="dark"] .render-hero-media { + opacity: 0.5; + } + + [data-bs-theme="dark"] .render-hero-scrim { + background: + radial-gradient(ellipse 92% 145% at 0% 46%, #100d16 0%, rgba(16, 13, 22, 0.98) 46%, rgba(16, 13, 22, 0.82) 70%, rgba(16, 13, 22, 0.5) 100%), + linear-gradient(0deg, rgba(16, 13, 22, 0.82), transparent 46%); + } +} + +/* ========================================================================== + Reference-inspired landing sections + ========================================================================== */ + +body[data-layout="landing"] > footer { + display: none; +} + +.landing-page .features-section { + padding: 72px 0 104px; +} + +.landing-page .section-heading, +.landing-page .install-heading, +.landing-page .resources-heading { + max-width: 760px; + margin: 0 auto 56px; + text-align: center; +} + +.landing-page .section-kicker { + display: block; + margin-bottom: 14px; + color: #512bd4; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 10px; + font-weight: 800; + line-height: 1; + letter-spacing: 0; +} + +.landing-page .section-heading h2, +.landing-page .architecture-copy h2, +.landing-page .install-heading h2, +.landing-page .resources-heading h2 { + margin: 0; + padding: 0; + color: #182746; + border: 0; + font-size: 39px; + font-weight: 800; + line-height: 1.08; + letter-spacing: 0; +} + +.landing-page .section-heading h2 span, +.landing-page .architecture-copy h2 span, +.landing-page .install-heading h2 span { + padding: 6px 5px 7px; + color: #512bd4; + background: linear-gradient(100deg, #512bd4, #c416b9); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.landing-page .section-heading h2 > span, +.landing-page .install-heading h2 > span { + display: block; + width: fit-content; + margin-inline: auto; +} + +.landing-page .architecture-copy h2 > span { + display: inline-block; + margin-left: -5px; } -/* ========================================================================== - 13. CTA Section - ========================================================================== */ +.landing-page .section-heading h2 > .anchorjs-link, +.landing-page .install-heading h2 > .anchorjs-link { + display: none; +} -.cta-section { - display: flex; - align-items: stretch; - gap: 2.5rem; - padding: 2rem 2.25rem; - background: var(--gradient-subtle); - border-radius: var(--radius-lg); - border: 1px solid var(--border); - margin: 2rem 0; - position: relative; - overflow: hidden; +.landing-page .section-heading p, +.landing-page .install-heading p { + max-width: 680px; + margin: 18px auto 0; + color: #6b7180; + font-size: 15px; + line-height: 1.65; } - /* Subtle accent line at top */ - .cta-section::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: var(--gradient-brand); - } +[data-bs-theme="dark"] .landing-page .section-heading h2, +[data-bs-theme="dark"] .landing-page .architecture-copy h2, +[data-bs-theme="dark"] .landing-page .install-heading h2, +[data-bs-theme="dark"] .landing-page .resources-heading h2 { + color: #f4f1fa; +} -.cta-main { - flex: 1; +[data-bs-theme="dark"] .landing-page .section-heading p, +[data-bs-theme="dark"] .landing-page .install-heading p { + color: #aca4b7; } - .cta-main h3 { - font-size: 1.375rem; - font-weight: 700; - color: var(--text-primary); - margin: 0 0 0.5rem; - letter-spacing: -0.01em; - } - - .cta-main p { - font-size: 0.95rem; - color: var(--text-secondary); - margin: 0 0 1.25rem; - line-height: 1.6; - } - - .cta-main .btn-primary { - display: inline-flex; - align-items: center; - padding: 0.625rem 1.5rem; - font-size: 0.9rem; - font-weight: 600; - text-decoration: none; - border-radius: var(--radius-sm) !important; - border: none; - outline: none; - background: var(--gradient-brand); - color: #FFFFFF; - transition: all var(--duration) var(--ease); - box-shadow: var(--shadow-sm); - } - - .cta-main .btn-primary:hover { - box-shadow: var(--shadow-md), 0 0 20px rgba(37,99,235,0.18); - transform: translateY(-1px); - } - - .cta-main .btn-primary:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } - -.cta-links { - display: flex; - flex-direction: column; - align-items: flex-end; - justify-content: center; - margin-left: auto; - gap: 0.5rem; +.landing-page .feature-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 22px; } -.cta-link { - display: flex; - align-items: center; +.landing-page .feature-card { + min-height: 190px; + padding: 28px; justify-content: flex-start; - gap: 0.625rem; - padding: 0.625rem 1.125rem; - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - text-decoration: none; - transition: all var(--duration) var(--ease); - min-width: 200px; + background: rgba(255, 255, 255, 0.84); + border: 1px solid #e4e6ee; + border-radius: 8px; + box-shadow: 0 8px 24px rgba(31, 43, 74, 0.045); } - .cta-link:hover { - border-color: var(--accent-ring); - background: var(--accent-soft); - text-decoration: none; - transform: translateY(-1px); - box-shadow: var(--shadow-sm); - } - -.cta-link-icon { - font-size: 1rem; +.landing-page .feature-card::before { + display: none; } -.cta-link-text { - font-size: 0.875rem; - font-weight: 600; - color: var(--text-primary); +.landing-page .feature-card:hover { + background: #ffffff; + border-color: rgba(81, 43, 212, 0.24); + box-shadow: 0 12px 30px rgba(31, 43, 74, 0.08); + transform: translateY(-2px); } -/* ========================================================================== - 14. Article Content - ========================================================================== */ - -article { - font-size: 0.95rem; - color: var(--text-primary); - line-height: 1.75; +.landing-page .feature-icon { + width: 40px; + height: 40px; + margin-bottom: 22px; + color: #512bd4; + background: #f1edff !important; + border: 0; + border-radius: 8px; + font-size: 18px; } - article h1 { - font-size: 2rem; - font-weight: 750; - color: var(--text-primary); - margin: 0 0 1.5rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid var(--border); - letter-spacing: -0.025em; - } - - article h2 { - font-size: 1.4rem; - font-weight: 700; - color: var(--text-primary); - margin: 2.5rem 0 1rem; - position: relative; - padding-left: 0.875rem; - letter-spacing: -0.02em; - } - - article h2::before { - content: ''; - position: absolute; - left: 0; - top: 0.15em; - bottom: 0.15em; - width: 3px; - background: var(--gradient-brand); - border-radius: 2px; - } - - article h3 { - font-size: 1.15rem; - font-weight: 650; - color: var(--text-primary); - margin: 2rem 0 0.625rem; - letter-spacing: -0.01em; - } - - article h4 { - font-size: 1rem; - font-weight: 600; - color: var(--text-primary); - margin: 1.5rem 0 0.5rem; - } - - article p { - margin: 0 0 1rem; - color: var(--text-primary); - } - - article a { - color: var(--accent); - text-decoration: none; - transition: color var(--duration) var(--ease); - } - - article a:hover { - color: var(--accent-hover); - text-decoration: underline; - text-underline-offset: 2px; - } - - article ul, - article ol { - margin: 0 0 1rem; - padding-left: 1.5rem; - } - - article li { - margin-bottom: 0.375rem; - color: var(--text-primary); - } - - /* Code blocks */ - article pre { - position: relative; - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--surface-sunken); - margin: 1.5rem 0; - overflow: hidden; - box-shadow: var(--shadow-xs); - } - - article pre .code-action, - article pre .btn.code-action, - article pre button.btn { - position: absolute !important; - top: 0.5rem !important; - right: 0.5rem !important; - z-index: 10 !important; - padding: 0.25rem 0.5rem !important; - } - - article pre code { - display: block; - padding: 1.125rem; - margin: 0; - overflow-x: auto; - font-size: 0.8125rem; - line-height: 1.7; - background: transparent; - } - - article pre code.hljs, - article pre .hljs { - background: transparent !important; - } - - article pre code::-webkit-scrollbar { - height: 6px; - background: transparent; - } - - article pre code::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: 3px; - } - - article pre code::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); - } - - article code { - font-size: 0.8125rem; - font-family: 'Cascadia Code', 'JetBrains Mono', 'Fira Code', Consolas, monospace; - } - - /* Inline code */ - article p code, - article li code, - article td code { - background: var(--accent-soft); - color: var(--accent); - padding: 0.125rem 0.4rem; - border-radius: var(--radius-xs); - font-size: 0.8rem; - border: 1px solid var(--accent-ring); - font-weight: 500; - } - - article hr { - border: none; - border-top: 1px solid var(--border); - margin: 2.5rem 0; - } - - article blockquote { - position: relative; - padding: 0.875rem 1.25rem; - margin: 1.5rem 0; - background: var(--gradient-subtle); - border-radius: 0 var(--radius-sm) var(--radius-sm) 0; - border-left: 3px solid var(--accent); - } - - article blockquote p { - margin: 0; - color: var(--text-secondary); - font-size: 0.9rem; - line-height: 1.65; - } - - article blockquote p:not(:last-child) { - margin-bottom: 0.5rem; - } - - /* Images */ - article img:not(#logo):not(.hero-logo):not([src*="shields.io"]):not([src*="badge"]) { - max-width: 100%; - height: auto; - border-radius: var(--radius-md); - margin: 1.5rem 0; - box-shadow: var(--shadow-md); - border: 1px solid var(--border); - transition: box-shadow var(--duration) var(--ease); - } - - article img:not(#logo):not(.hero-logo):not([src*="shields.io"]):not([src*="badge"]):hover { - box-shadow: var(--shadow-lg); - } - -#logo, -.hero-logo, -.hero-badges img, -.community-badges img { - box-shadow: none !important; - border: none !important; - transform: none !important; +.landing-page .feature-card:nth-child(2) .feature-icon, +.landing-page .feature-card:nth-child(4) .feature-icon, +.landing-page .feature-card:nth-child(5) .feature-icon { + color: #d31491; + background: #fff0f8 !important; } - #logo:hover, - .hero-badges img:hover, - .community-badges img:hover { - box-shadow: none !important; - transform: none !important; - } +.landing-page .feature-card:nth-child(3) .feature-icon, +.landing-page .feature-card:nth-child(6) .feature-icon { + color: #1671e9; + background: #edf5ff !important; +} -article figure { - margin: 1.5rem 0; +.landing-page .feature-card h3 { + margin: 0 0 8px; padding: 0; + color: #172a4d; + font-size: 18px; + line-height: 1.3; } - article figure img { - margin: 0; - } - -article figcaption { - text-align: center; - font-size: 0.8125rem; - color: var(--text-secondary); - margin-top: 0.5rem; - font-style: italic; +.landing-page .feature-card p { + margin: 0; + color: #697184; + font-size: 14px; + line-height: 1.6; } -/* ========================================================================== - 15. Table Styles - ========================================================================== */ - -article .table-responsive, -article > table { - margin: 1.5rem 0 !important; +[data-bs-theme="dark"] .landing-page .feature-card { + background: rgba(25, 20, 34, 0.88); + border-color: #352c45; + box-shadow: none; } -article table { - width: 100% !important; - margin: 0 !important; - font-size: 0.875rem !important; - border-collapse: separate !important; - border-spacing: 0 !important; - border: 1px solid var(--border) !important; - border-radius: var(--radius-md) !important; - background: var(--surface) !important; - box-shadow: var(--shadow-xs) !important; +[data-bs-theme="dark"] .landing-page .feature-card:hover { + background: #20182b; + border-color: rgba(155, 123, 255, 0.38); } -.table-responsive { - overflow: visible !important; +[data-bs-theme="dark"] .landing-page .feature-card h3 { + color: #f4f1fa; } -article thead { - background: var(--surface-sunken) !important; +[data-bs-theme="dark"] .landing-page .feature-card p { + color: #aaa3b4; } -article th { - padding: 0.75rem 1rem !important; - text-align: center !important; - font-size: 0.75rem !important; - font-weight: 600 !important; - text-transform: uppercase !important; - letter-spacing: 0.06em !important; - color: var(--text-tertiary) !important; - background: transparent !important; - border: none !important; - white-space: nowrap !important; - vertical-align: middle !important; +.architecture-section { + padding: 96px 0; + background: linear-gradient(120deg, #f0edff 0%, #fff0fb 100%); } -article thead tr th:first-child { - border-top-left-radius: var(--radius-md) !important; +.architecture-grid { + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr); + align-items: center; + gap: 56px; } -article thead tr th:last-child { - border-top-right-radius: var(--radius-md) !important; +.architecture-code { + overflow: hidden; + background: #092a5e; + border: 1px solid rgba(81, 43, 212, 0.18); + border-radius: 8px; + box-shadow: 0 18px 40px rgba(43, 29, 92, 0.12); } -article tbody tr { - transition: background var(--duration) var(--ease) !important; +.architecture-code-bar { + display: flex; + align-items: center; + height: 44px; + padding: 0 16px; + color: #8d96ad; + background: #15182a; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 10px; } - article tbody tr:hover { - background: var(--accent-soft) !important; - } - -article td { - padding: 0.625rem 1rem !important; - text-align: left !important; - color: var(--text-primary) !important; - background: transparent !important; - border: none !important; - border-top: 1px solid var(--border) !important; - vertical-align: middle !important; +.architecture-code-title { + margin-left: 12px; } -article tbody tr:first-child td { - border-top: none !important; +.architecture-code-brand { + margin-left: auto; + color: #ef45c6; + font-weight: 700; } -article tbody tr:last-child td:first-child { - border-bottom-left-radius: var(--radius-md) !important; +.landing-page .architecture-code pre { + min-height: 430px; + margin: 0; + padding: 30px 32px; + color: #d4d4d4; + background: transparent !important; + border: 0 !important; + border-radius: 0 !important; + box-shadow: none; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 12px; + line-height: 1.72; + white-space: pre; } -article tbody tr:last-child td:last-child { - border-bottom-right-radius: var(--radius-md) !important; +@media (min-width: 768px) { + .render-hero-inner { + align-items: flex-start; + padding-top: 36px; + } } -article th:not(:last-child), -article td:not(:last-child) { - background-image: linear-gradient(to bottom, transparent 15%, var(--border) 15%, var(--border) 85%, transparent 85%) !important; - background-size: 1px 100% !important; - background-position: right !important; - background-repeat: no-repeat !important; +.landing-page .architecture-code code { + padding: 0; + color: inherit; + background: transparent; } -article td a { - white-space: nowrap !important; +.landing-page .architecture-code .code-keyword { + color: #d98cef; } -/* ========================================================================== - 16. Status Badges - ========================================================================== */ +.landing-page .architecture-code .code-namespace, +.landing-page .architecture-code .code-type { + color: #6ed6c1; +} -.status-yes, -.status-no { - display: inline-block; - padding: 0.15rem 0.5rem; - border-radius: var(--radius-full); - font-size: 0.75rem; +.landing-page .architecture-code .code-method { + color: #f0d775; font-weight: 600; } -.status-yes { - background: var(--accent-soft); - color: var(--accent); - border: 1px solid var(--accent-ring); +.landing-page .architecture-code .code-value { + color: #d9a084; } -.status-no { - background: var(--surface-sunken); - color: var(--text-tertiary); - border: 1px solid var(--border); +.landing-page .architecture-code .code-comment { + color: #8fbc7a; } -/* ========================================================================== - 17. Alerts - ========================================================================== */ - -.alert { - padding: 0.875rem 1.125rem; - margin: 1.5rem 0; - border-radius: var(--radius-sm); - border-left-width: 3px !important; - font-size: 0.875rem; - line-height: 1.6; - color: var(--text-primary); +.landing-page .architecture-code .code-number { + color: #b8d7a3; } - .alert p:last-child { - margin-bottom: 0; - } - - .alert a { - font-weight: 600; - text-decoration: underline; - text-underline-offset: 2px; - } - - .alert a:hover { - text-decoration: none; - } - -.alert-primary { - background: rgba(37, 99, 235, 0.05); - border: 1px solid rgba(37, 99, 235, 0.15); - border-left-color: var(--accent) !important; +.architecture-copy .section-kicker { + margin-bottom: 14px; + text-align: left; } - .alert-primary a { - color: var(--accent); - } +.architecture-copy h2 { + max-width: 560px; +} -.alert-secondary { - background: var(--surface-sunken); - border: 1px solid var(--border); - border-left-color: var(--border-strong) !important; +.architecture-copy > p { + max-width: 610px; + margin: 20px 0 28px; + color: #626a7a; + font-size: 15px; + line-height: 1.7; } - .alert-secondary a { - color: var(--text-secondary); - } +.architecture-points { + display: grid; + gap: 12px; +} -.alert-success { - background: rgba(22, 163, 74, 0.05); - border: 1px solid rgba(22, 163, 74, 0.15); - border-left-color: #16A34A !important; +.architecture-points > a { + display: grid; + grid-template-columns: 22px minmax(0, 1fr); + gap: 12px; + align-items: start; + padding: 14px 16px; + color: #172a4d; + background: rgba(255, 255, 255, 0.82); + border: 1px solid rgba(81, 43, 212, 0.1); + border-radius: 8px; + text-decoration: none; + transition: border-color 160ms ease, transform 160ms ease; } - .alert-success a { - color: #16A34A; - } +.architecture-points > a:hover { + border-color: rgba(81, 43, 212, 0.32); + transform: translateX(3px); +} -.alert-danger { - background: rgba(220, 38, 38, 0.05); - border: 1px solid rgba(220, 38, 38, 0.15); - border-left-color: #DC2626 !important; +.architecture-points i { + margin-top: 2px; + color: #512bd4; + font-size: 16px; } - .alert-danger a { - color: #DC2626; - } +.architecture-points span, +.architecture-points strong, +.architecture-points small { + display: block; +} -.alert-warning { - background: rgba(217, 119, 6, 0.05); - border: 1px solid rgba(217, 119, 6, 0.15); - border-left-color: #D97706 !important; +.architecture-points strong { + margin-bottom: 3px; + font-size: 13px; } - .alert-warning a { - color: #D97706; - } +.architecture-points small { + color: #777d8c; + font-size: 11px; + line-height: 1.45; +} -.alert-info { - background: rgba(6, 182, 212, 0.05); - border: 1px solid rgba(6, 182, 212, 0.15); - border-left-color: #0891B2 !important; +[data-bs-theme="dark"] .architecture-section { + background: linear-gradient(120deg, #181327 0%, #261426 100%); } - .alert-info a { - color: #0891B2; - } +[data-bs-theme="dark"] .architecture-copy > p { + color: #aaa3b4; +} -.alert-light { - background: var(--surface-sunken); - border: 1px solid var(--border); - border-left-color: var(--border-strong) !important; +[data-bs-theme="dark"] .architecture-points > a { + color: #f4f1fa; + background: rgba(31, 24, 43, 0.88); + border-color: #3b304d; } - .alert-light a { - color: var(--accent); - } +[data-bs-theme="dark"] .architecture-points small { + color: #9e96aa; +} -.alert-dark { - background: var(--surface-sunken); - border: 1px solid var(--border-strong); - border-left-color: var(--text-secondary) !important; +.install-section { + padding: 82px 0 86px; + background: #ffffff; } - .alert-dark a { - color: var(--text-primary); - } +.install-inner { + max-width: 760px; +} -/* Dark mode alert overrides */ -[data-bs-theme="dark"] .alert-primary { - background: rgba(96, 165, 250, 0.06); - border-color: rgba(96, 165, 250, 0.18); - border-left-color: var(--accent) !important; +.landing-page .install-heading { + margin-bottom: 32px; } - [data-bs-theme="dark"] .alert-primary a { - color: var(--accent); - } +.landing-page .install-heading h2 { + font-size: 37px; +} -[data-bs-theme="dark"] .alert-success { - background: rgba(74, 222, 128, 0.06); - border-color: rgba(74, 222, 128, 0.18); - border-left-color: #4ADE80 !important; +.install-command { + display: flex; + align-items: center; + min-height: 58px; + padding: 0 16px 0 22px; + color: #edf4ff; + background: #092a5e; + border: 1px solid #173c72; + border-radius: 8px; + box-shadow: 0 10px 26px rgba(9, 42, 94, 0.12); } - [data-bs-theme="dark"] .alert-success a { - color: #86EFAC; - } +.install-command > code { + min-width: 0; + flex: 1; + padding: 0; + overflow-x: auto; + color: inherit; + background: transparent; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 13px; + white-space: nowrap; +} -[data-bs-theme="dark"] .alert-danger { - background: rgba(248, 113, 113, 0.06); - border-color: rgba(248, 113, 113, 0.18); - border-left-color: #F87171 !important; +.install-command > code span { + color: #ff3cc7; } - [data-bs-theme="dark"] .alert-danger a { - color: #FCA5A5; - } +.install-command > code strong { + color: #5ce1b7; + font-weight: 650; +} -[data-bs-theme="dark"] .alert-warning { - background: rgba(251, 191, 36, 0.06); - border-color: rgba(251, 191, 36, 0.18); - border-left-color: #FBBF24 !important; +.install-copy { + display: inline-flex; + min-width: 78px; + height: 34px; + align-items: center; + justify-content: center; + gap: 7px; + margin-left: 16px; + padding: 0 10px; + color: #b7c7e2; + background: transparent; + border: 0; + border-radius: 6px; + font-size: 12px; } - [data-bs-theme="dark"] .alert-warning a { - color: #FCD34D; - } +.install-copy:hover { + color: #ffffff; + background: rgba(255, 255, 255, 0.08); +} -[data-bs-theme="dark"] .alert-info { - background: rgba(34, 211, 238, 0.06); - border-color: rgba(34, 211, 238, 0.18); - border-left-color: #22D3EE !important; +.install-copy.is-copied { + color: #70edc5; + background: rgba(92, 225, 183, 0.1); } - [data-bs-theme="dark"] .alert-info a { - color: #67E8F9; - } +.install-note { + margin: 14px 0 0; + color: #737988; + font-size: 12px; + text-align: center; +} -/* ========================================================================== - 18. Sidebar Navigation - ========================================================================== */ +.install-note a { + color: #512bd4; + font-weight: 700; +} -.sidebar, -.sidefilter { - background: transparent !important; +[data-bs-theme="dark"] .install-section { + background: #100d16; } -.affix { - position: sticky !important; - top: 80px !important; - height: fit-content !important; - max-height: calc(100vh - 100px) !important; - overflow-y: auto !important; - background: var(--surface) !important; - border: 1px solid var(--border) !important; - border-radius: var(--radius-md) !important; - box-shadow: var(--shadow-xs) !important; - padding: 0.75rem !important; - margin-top: 1rem !important; -} - - .affix > h5, - .affix .border-bottom { - display: block !important; - width: 100% !important; - text-align: left !important; - font-size: 0.6875rem !important; - font-weight: 600 !important; - color: var(--text-tertiary) !important; - margin: 0 0 0.5rem !important; - padding: 0 0.25rem 0.5rem !important; - border: none !important; - border-bottom: 1px solid var(--border) !important; - text-transform: uppercase; - letter-spacing: 0.08em; - } - - .affix ul { - display: flex !important; - flex-direction: column !important; - gap: 1px !important; - padding: 0 !important; - margin: 0 !important; - list-style: none !important; - } +[data-bs-theme="dark"] .install-note { + color: #9e96aa; +} - .affix ul li { - margin: 0 !important; - padding: 0 !important; - } +.resources-section { + padding: 90px 0 96px; + background: linear-gradient(120deg, #f1efff 0%, #eef6ff 100%); +} - .affix ul li a { - display: block !important; - padding: 0.3rem 0.5rem !important; - margin: 0 !important; - border-radius: var(--radius-xs) !important; - font-size: 0.8rem !important; - font-weight: 500 !important; - text-decoration: none !important; - transition: all var(--duration) var(--ease) !important; - background: transparent !important; - border: 1px solid transparent !important; - cursor: pointer !important; - line-height: 1.4 !important; - white-space: nowrap !important; - overflow: hidden !important; - text-overflow: ellipsis !important; - } +.landing-page .resources-heading { + margin-bottom: 50px; +} - .affix .link-body-emphasis { - color: var(--text-primary) !important; - } +.landing-page .resources-heading h2 { + font-size: 36px; +} - .affix .link-secondary { - color: var(--text-secondary) !important; - font-size: 0.75rem !important; - font-weight: 400 !important; - } +.resource-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 22px; +} - .affix ul li a:hover, - .affix ul li.active > a, - .affix ul li a.active { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } +.resource-card { + display: flex; + min-height: 244px; + flex-direction: column; + padding: 28px; + overflow: hidden; + color: #ffffff !important; + border-radius: 8px; + box-shadow: 0 14px 30px rgba(31, 43, 91, 0.12); + text-decoration: none !important; + transition: transform 170ms ease, box-shadow 170ms ease; +} - .affix ul ul { - margin-left: 0.625rem !important; - padding-left: 0 !important; - border-left: none !important; - gap: 0 !important; - } +.resource-card:hover { + color: #ffffff !important; + box-shadow: 0 18px 38px rgba(31, 43, 91, 0.18); + transform: translateY(-4px); +} -.toc { - padding: 0.5rem !important; -} - - .toc .nav > li > a, - .toc .nav > li > .expand-stub + a { - padding: 0.4rem 0.75rem !important; - margin: 1px 0 !important; - border-radius: var(--radius-sm) !important; - color: var(--text-secondary) !important; - font-size: 0.8125rem !important; - font-weight: 500 !important; - transition: all var(--duration) var(--ease) !important; - display: block !important; - text-decoration: none !important; - background: transparent !important; - } - - .toc .nav > li > a:hover, - .toc .nav > li > .expand-stub + a:hover { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } - - .toc .nav > li.active > a, - .toc .nav > li.active > .expand-stub + a, - .toc .nav > li > a.active { - background: var(--accent-soft-mid) !important; - color: var(--accent) !important; - font-weight: 600 !important; - } - - .toc .nav > li.active > a::before, - .toc .nav > li > a.active::before { - content: '' !important; - position: absolute !important; - left: 0 !important; - top: 20% !important; - bottom: 20% !important; - width: 2px !important; - background: var(--accent) !important; - border-radius: 0 2px 2px 0 !important; - } - - .toc .nav > li > .expand-stub { - width: 26px !important; - height: 26px !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: var(--radius-xs) !important; - cursor: pointer !important; - transition: all var(--duration) var(--ease) !important; - margin-right: 2px !important; - flex-shrink: 0 !important; - background: transparent !important; - } - - .toc .nav > li > .expand-stub:hover { - background: var(--accent-soft) !important; - } - - .toc .nav > li > .expand-stub::before { - font-size: 0.65rem !important; - color: var(--text-tertiary) !important; - transition: transform var(--duration) var(--ease) !important; - } - - .toc .nav > li.in > .expand-stub::before, - .toc .nav > li.expanded > .expand-stub::before { - transform: rotate(90deg) !important; - } - - .toc .nav .nav { - padding-left: 0.625rem !important; - margin-left: 0.625rem !important; - border-left: 1px solid var(--border) !important; - } - - .toc .nav .nav > li > a { - font-size: 0.78rem !important; - padding: 0.35rem 0.625rem !important; - color: var(--text-secondary) !important; - border-radius: var(--radius-xs) !important; - } - - .toc .nav .nav > li > a:hover, - .toc .nav .nav > li.active > a { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } - - .toc .nav .nav > li.active > a { - font-weight: 600 !important; - } - -/* Sidebar filter / search */ -.sidefilter .filter-input, -.sidetoc .filter, -.sidebar input.form-control[type="search"], -#toc .filter input.form-control { - background: var(--surface-sunken) !important; - border: 1px solid var(--border) !important; - border-radius: var(--radius-sm) !important; - padding: 0.4rem 0.875rem 0.4rem 2rem !important; - font-size: 0.8rem !important; - color: var(--text-primary) !important; - transition: all var(--duration) var(--ease) !important; - width: 100% !important; - box-shadow: none !important; +.resource-card-docs { + background: linear-gradient(135deg, #512bd4, #7542ed); } - .sidefilter .filter-input:focus, - .sidetoc .filter:focus, - .sidebar input.form-control[type="search"]:focus, - #toc .filter input.form-control:focus { - outline: none !important; - border-color: var(--accent) !important; - box-shadow: 0 0 0 3px var(--accent-ring) !important; - background: var(--surface) !important; - } +.resource-card-nuget { + background: linear-gradient(135deg, #d10ba7, #ec35bd); +} - .sidefilter .filter-input::placeholder, - .sidetoc .filter::placeholder, - .sidebar input.form-control[type="search"]::placeholder, - #toc .filter input.form-control::placeholder { - color: var(--text-tertiary) !important; - } +.resource-card-community { + background: linear-gradient(135deg, #0869b8, #2689dc); +} -#toc .filter { - position: relative !important; - margin-bottom: 0.75rem !important; +.resource-icon { + display: inline-flex; + width: 38px; + height: 38px; + align-items: center; + justify-content: center; + margin-bottom: 26px; + background: rgba(255, 255, 255, 0.16); + border-radius: 8px; + font-size: 18px; } - #toc .filter i { - position: absolute !important; - left: 0.5rem !important; - top: 50% !important; - transform: translateY(-50%) !important; - color: var(--text-tertiary) !important; - font-size: 0.8rem !important; - z-index: 1 !important; - pointer-events: none !important; - } +.resource-title { + display: block; + margin: 0 0 9px; + padding: 0; + color: #ffffff; + font-size: 18px; + font-weight: 800; +} -#toc .flex-fill.overflow-y-auto { - --toc-expand-width: 16px; - padding: 0 0.25rem !important; +.resource-card p { + margin: 0 0 24px; + color: rgba(255, 255, 255, 0.82); + font-size: 13px; + line-height: 1.6; } - #toc .flex-fill.overflow-y-auto ul { - list-style: none !important; - padding: 0 !important; - margin: 0 !important; - } +.resource-card > strong { + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: auto; + font-size: 13px; +} - #toc .flex-fill.overflow-y-auto > ul > li { - margin-top: 1px !important; - padding: 0 !important; - } +[data-bs-theme="dark"] .resources-section { + background: linear-gradient(120deg, #171224 0%, #101b2c 100%); +} - #toc .flex-fill.overflow-y-auto > ul > li:first-child { - margin-top: 0 !important; - } +/* Keep every landing section on one continuous theme canvas. */ +.landing-page { + --landing-canvas: #fbfafc; + --landing-canvas-rgb: 251 250 252; + --landing-footer-title: #172a4d; + --landing-footer-text: #667187; + --landing-footer-link: #60708a; + --landing-footer-icon: #512bd4; + --landing-footer-icon-bg: rgba(81, 43, 212, 0.08); + --landing-footer-rule: rgba(24, 39, 70, 0.1); + background: var(--landing-canvas); +} - #toc .flex-fill.overflow-y-auto li { - margin: 0 !important; - padding: 0 !important; - } +[data-bs-theme="dark"] .landing-page { + --landing-canvas: #100d16; + --landing-canvas-rgb: 16 13 22; + --landing-footer-title: #f4f1fa; + --landing-footer-text: #9f98ab; + --landing-footer-link: #aaa3b8; + --landing-footer-icon: #b9a5ff; + --landing-footer-icon-bg: rgba(155, 123, 255, 0.1); + --landing-footer-rule: rgba(222, 213, 235, 0.1); +} - #toc .flex-fill.overflow-y-auto li.expander { - display: flex !important; - flex-wrap: wrap !important; - align-items: stretch !important; - border-radius: var(--radius-xs) !important; - } - - #toc .flex-fill.overflow-y-auto li.expander > .expand-stub, - #toc .flex-fill.overflow-y-auto li.expander > a { - background: transparent !important; - border: none !important; - } - - #toc .flex-fill.overflow-y-auto li.expander:hover > .expand-stub, - #toc .flex-fill.overflow-y-auto li.expander:hover > a, - #toc .flex-fill.overflow-y-auto li.expander.active > .expand-stub, - #toc .flex-fill.overflow-y-auto li.expander.active > a, - #toc .flex-fill.overflow-y-auto li.expander > a.active { - background: var(--accent-soft) !important; - } - - #toc .flex-fill.overflow-y-auto li.expander:hover > a { - color: var(--accent) !important; - } - - #toc .flex-fill.overflow-y-auto li .expand-stub { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - width: calc(var(--toc-expand-width) + 0.5rem) !important; - height: auto !important; - min-height: 26px !important; - border-radius: var(--radius-xs) 0 0 var(--radius-xs) !important; - transition: all var(--duration) var(--ease) !important; - cursor: pointer !important; - flex-shrink: 0 !important; - padding: 0.4rem 0 0.4rem 0.4rem !important; - } - - #toc .flex-fill.overflow-y-auto li .expand-stub::before { - margin-left: 0 !important; - } - - #toc .flex-fill.overflow-y-auto li.expander > a { - border-radius: 0 var(--radius-xs) var(--radius-xs) 0 !important; - padding: 0.4rem 0.4rem 0.4rem 0.125rem !important; - } - - #toc .flex-fill.overflow-y-auto li.expander > ul { - width: 100% !important; - flex-basis: 100% !important; - margin-top: 2px !important; - margin-bottom: 2px !important; - } - - #toc .flex-fill.overflow-y-auto li > a { - display: inline-block !important; - padding: 0.35rem 0.4rem !important; - margin: 0 !important; - border-radius: var(--radius-xs) !important; - font-size: 0.8rem !important; - font-weight: 500 !important; - text-decoration: none !important; - transition: all var(--duration) var(--ease) !important; - background: transparent !important; - border: 1px solid transparent !important; - cursor: pointer !important; - line-height: 1.3 !important; - color: var(--text-secondary) !important; - flex: 1 !important; - } - - #toc .flex-fill.overflow-y-auto li:not(.expander) > a { - display: block !important; - } - - #toc .flex-fill.overflow-y-auto li:not(.expander) > a:hover { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } - - #toc .flex-fill.overflow-y-auto li.active > a, - #toc .flex-fill.overflow-y-auto li > a.active { - background: var(--accent-soft-mid) !important; - color: var(--accent) !important; - font-weight: 600 !important; - } - - #toc .flex-fill.overflow-y-auto ul ul { - position: relative !important; - margin-left: calc(var(--toc-expand-width) + 12px) !important; - padding: 0 !important; - } +.landing-page .architecture-section { + background-color: var(--landing-canvas); + background-image: + linear-gradient(to bottom, var(--landing-canvas) 0%, rgb(var(--landing-canvas-rgb) / 0) 96px, rgb(var(--landing-canvas-rgb) / 0) calc(100% - 96px), var(--landing-canvas) 100%), + linear-gradient(120deg, rgba(81, 43, 212, 0.1), rgba(196, 22, 185, 0.07)); +} - #toc .flex-fill.overflow-y-auto ul ul li { - margin-top: 1px !important; - } +[data-bs-theme="dark"] .landing-page .architecture-section { + background-image: + linear-gradient(to bottom, var(--landing-canvas) 0%, rgb(var(--landing-canvas-rgb) / 0) 96px, rgb(var(--landing-canvas-rgb) / 0) calc(100% - 96px), var(--landing-canvas) 100%), + linear-gradient(120deg, rgba(155, 123, 255, 0.07), rgba(239, 53, 170, 0.045)); +} - #toc .flex-fill.overflow-y-auto ul ul li:first-child { - margin-top: 0 !important; - } +.landing-page .install-section { + background: var(--landing-canvas); +} - #toc .flex-fill.overflow-y-auto ul ul li > a { - font-size: 0.75rem !important; - padding: 0.25rem 0.375rem !important; - } +.landing-page .resources-section { + background-color: var(--landing-canvas); + background-image: + linear-gradient(to bottom, var(--landing-canvas) 0%, rgb(var(--landing-canvas-rgb) / 0) 88px, rgb(var(--landing-canvas-rgb) / 0) calc(100% - 88px), var(--landing-canvas) 100%), + linear-gradient(120deg, rgba(81, 43, 212, 0.08), rgba(22, 113, 233, 0.07)); +} -/* ========================================================================== - 19. Breadcrumb Navigation - ========================================================================== */ +[data-bs-theme="dark"] .landing-page .resources-section { + background-image: + linear-gradient(to bottom, var(--landing-canvas) 0%, rgb(var(--landing-canvas-rgb) / 0) 88px, rgb(var(--landing-canvas-rgb) / 0) calc(100% - 88px), var(--landing-canvas) 100%), + linear-gradient(120deg, rgba(155, 123, 255, 0.06), rgba(67, 216, 255, 0.04)); +} -#breadcrumb { - margin-bottom: 1.5rem; +.landing-footer { + padding: 78px 0 34px; + color: var(--landing-footer-text); + background-color: var(--landing-canvas); + background-image: linear-gradient(to bottom, var(--landing-canvas) 0%, rgb(var(--landing-canvas-rgb) / 0) 96px), radial-gradient(ellipse at 16% 36%, rgba(81, 43, 212, 0.06), transparent 48%), radial-gradient(ellipse at 82% 72%, rgba(22, 113, 233, 0.05), transparent 44%); } -.breadcrumb { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0.25rem; - padding: 0.4rem 0.75rem; - margin: 0; - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - list-style: none; +.landing-footer-grid { + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(180px, 0.7fr) minmax(180px, 0.7fr); + gap: 72px; + padding-bottom: 58px; } -.breadcrumb-item { +.landing-footer-brand > div { display: flex; align-items: center; - font-size: 0.8rem; - color: var(--text-secondary); + gap: 14px; } - .breadcrumb-item a { - color: var(--text-secondary); - text-decoration: none; - padding: 0.15rem 0.3rem; - border-radius: var(--radius-xs); - transition: all var(--duration) var(--ease); - } - - .breadcrumb-item a:hover { - color: var(--accent); - background: var(--accent-soft); - text-decoration: none; - } - - .breadcrumb-item + .breadcrumb-item::before { - content: ''; - display: inline-block; - width: 5px; - height: 5px; - margin-right: 0.3rem; - border-right: 1.5px solid var(--text-tertiary); - border-top: 1.5px solid var(--text-tertiary); - transform: rotate(45deg); - } +.landing-footer-brand img { + width: 48px; + height: 48px; + border-radius: 8px; +} - .breadcrumb-item:last-child { - color: var(--text-primary); - font-weight: 500; - } +.landing-footer-brand strong { + color: var(--landing-footer-title); + font-size: 27px; +} - .breadcrumb-item:last-child a { - color: var(--text-primary); - pointer-events: none; - } +.landing-footer-brand strong span { + color: #b51ad0; +} -/* ========================================================================== - 20. Hide UI Elements - ========================================================================== */ +.landing-footer-brand p { + max-width: 480px; + margin: 24px 0 28px; + color: var(--landing-footer-text); + font-size: 15px; + line-height: 1.7; +} -article h1 > a:not(.header-action), -article h2 > a:not(.header-action), -article h3 > a:not(.header-action), -article h4 > a:not(.header-action), -article h5 > a:not(.header-action), -article h6 > a:not(.header-action), -a[href^="http"]::after, -a[href^="https"]::after { - display: none !important; +.landing-footer-github { + display: inline-flex; + width: 42px; + height: 42px; + align-items: center; + justify-content: center; + color: var(--landing-footer-icon) !important; + background: var(--landing-footer-icon-bg); + border-radius: 8px; + font-size: 20px; } -/* ========================================================================== - 21. Search Results - ========================================================================== */ +.landing-footer-github:hover { + color: #ffffff !important; + background: rgba(255, 255, 255, 0.11); +} -body[data-search] > .search-results { - max-width: 820px; - margin: 0 auto; - padding: 2rem 1.5rem 3rem; +.landing-footer-column { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 14px; } -#search-results { - line-height: 1.7; +.landing-footer-heading { + display: block; + margin: 0 0 10px; + padding: 0; + color: #7847ff; + font-size: 14px; + font-weight: 800; } - #search-results > .search-list { - font-size: 0.9rem; - color: var(--text-secondary); - margin-bottom: 1.5rem; - padding-bottom: 1rem; - border-bottom: 1px solid var(--border); - } +.landing-footer-column:last-child .landing-footer-heading { + color: #ef35aa; +} - #search-results > .sr-items { - display: flex; - flex-direction: column; - gap: 0.75rem; - } - - #search-results > .sr-items > .sr-item { - background: var(--surface); - border: 1px solid var(--border); - border-radius: var(--radius-md); - padding: 1.25rem 1.5rem; - margin-bottom: 0; - transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease); - } - - #search-results > .sr-items > .sr-item:hover { - border-color: var(--accent); - box-shadow: var(--shadow-sm); - } - - #search-results > .sr-items > .sr-item > .item-title { - font-size: 1.125rem; - font-weight: 600; - color: var(--accent); - text-decoration: none; - display: block; - margin-bottom: 0.35rem; - } - - #search-results > .sr-items > .sr-item > .item-title:hover { - color: var(--accent-hover); - } - - #search-results > .sr-items > .sr-item > .item-href { - font-size: 0.8rem; - color: var(--text-tertiary); - margin-bottom: 0.5rem; - word-break: break-all; - } - - #search-results > .sr-items > .sr-item > .item-brief { - font-size: 0.875rem; - color: var(--text-secondary); - line-height: 1.6; - } +.landing-footer-column a { + color: var(--landing-footer-link); + font-size: 14px; + text-decoration: none; +} -/* ========================================================================== - 22. Responsive Design - ========================================================================== */ +.landing-footer-column a:hover { + color: #ffffff; +} -@media (max-width: 768px) { - .hero { - padding: 2.5rem 1.25rem 3rem; - } +.landing-footer-bottom { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 30px; + border-top: 1px solid var(--landing-footer-rule); + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 12px; +} - .hero h1 { - font-size: 2.25rem; - } +.landing-footer-bottom > span:last-child { + display: flex; + gap: 28px; +} - .hero-tagline { - font-size: 1.1rem; - } +.landing-footer-bottom a { + color: var(--landing-footer-link); + text-decoration: none; +} - .hero-description { - font-size: 0.95rem; - } +.landing-footer-bottom a:hover { + color: #ffffff; +} - .hero-logo { - width: 64px; - height: 64px; +@media (max-width: 991.98px) { + .landing-page .features-section, + .architecture-section, + .resources-section { + padding-top: 78px; + padding-bottom: 82px; } - .tech-item { - padding: 0.4rem 0.75rem; + .landing-page .feature-grid, + .resource-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); } - .workflow { - --step-size: 38px; + .architecture-grid { + grid-template-columns: 1fr; + gap: 46px; } - .step-number { - font-size: 0.9rem; + .architecture-code, + .architecture-copy { + max-width: 760px; + width: 100%; + margin: 0 auto; } - .step-content { - padding: 0.875rem 1rem; + .landing-footer-grid { + grid-template-columns: minmax(0, 1fr) 180px 180px; + gap: 36px; } +} - article h1 { - font-size: 1.625rem; +@media (max-width: 767.98px) { + .landing-page .section-heading, + .landing-page .install-heading, + .landing-page .resources-heading { + margin-bottom: 38px; } - article h2 { - font-size: 1.25rem; - padding-left: 0.75rem; - position: relative; + .landing-page .section-heading h2, + .landing-page .architecture-copy h2, + .landing-page .install-heading h2, + .landing-page .resources-heading h2 { + font-size: 31px; } - article h2::before { - content: ''; - display: block; - position: absolute; - left: 0; - top: 0.15em; - bottom: 0.15em; - width: 3px; - background: var(--gradient-brand); - border-radius: 2px; - } - - article h3 { - font-size: 1.05rem; + .landing-page .feature-grid, + .resource-grid { + grid-template-columns: 1fr; } - article pre { - border-radius: var(--radius-sm); + .landing-page .feature-card { + min-height: 0; } - .status-yes, - .status-no { - padding: 0.1rem 0.4rem; - font-size: 0.7rem; + .architecture-code pre { + min-height: 0; } - .tech-bar { - flex-direction: column; - gap: 0.75rem; - padding: 0.875rem 1.125rem; + .install-section { + padding: 68px 0 72px; } - .tech-bar-group { - flex-direction: column; - gap: 0.25rem; - text-align: center; + .landing-footer-grid { + grid-template-columns: 1fr 1fr; } - .tech-bar-divider { - width: 80%; - height: 1px; + .landing-footer-brand { + grid-column: 1 / -1; } +} - .highlight-grid { - grid-template-columns: 1fr; +@media (max-width: 575.98px) { + .landing-page .features-section, + .architecture-section, + .resources-section { + padding-top: 64px; + padding-bottom: 68px; } - .highlight-item { - padding: 0.875rem; + .landing-page .feature-card, + .resource-card { + padding: 24px; } - .feature-list-item { - flex-direction: column; - gap: 0.25rem; + .architecture-code pre { + padding: 22px 18px; + font-size: 10px; } - .feature-list-title { - min-width: auto; + .architecture-points > a { + padding: 13px 14px; } - .cta-section { - flex-direction: column; - padding: 1.5rem 1.25rem; - text-align: center; + .install-command { + min-height: 54px; + padding-right: 10px; + padding-left: 15px; } - .cta-section::before { - /* Shift gradient line to left side on mobile */ - left: 0; - right: auto; - top: 0; - bottom: 0; - width: 3px; - height: 100%; - } - - .cta-main h3 { - font-size: 1.25rem; + .install-command > code { + font-size: 10px; } - .cta-links { - width: 100%; - align-items: stretch; + .install-copy { + min-width: 38px; + margin-left: 8px; } - .cta-link { - justify-content: center; - width: 100%; - min-width: auto; + .install-copy span { + display: none; } - .breadcrumb { - padding: 0.35rem 0.5rem; + .landing-footer { + padding-top: 62px; } - .breadcrumb-item { - font-size: 0.75rem; + .landing-footer-grid { + grid-template-columns: 1fr; + gap: 42px; + padding-bottom: 44px; } - .alert { - padding: 0.75rem 0.875rem; - font-size: 0.8125rem; + .landing-footer-brand, + .landing-footer-column { + grid-column: auto; } - .actionbar { - display: flex; - align-items: center; - gap: 0.75rem; + .landing-footer-bottom { + align-items: flex-start; + flex-direction: column; + gap: 16px; } - - .actionbar > button { - flex-shrink: 0; - margin-top: 0 !important; - margin-left: 0 !important; - } -} +} \ No newline at end of file diff --git a/documents/templates/public/main.js b/documents/templates/public/main.js index fbf12aad..401e0eb8 100644 --- a/documents/templates/public/main.js +++ b/documents/templates/public/main.js @@ -7,6 +7,62 @@ } ], start: () => { + const logo = document.getElementById('logo'); + if (logo) { + const lightLogo = logo.src; + const darkLogo = lightLogo.replace('Zenith.NET-Logo.svg', 'Zenith.NET-Logo-Dark.svg'); + const updateLogo = () => { + logo.src = document.documentElement.dataset.bsTheme === 'dark' ? darkLogo : lightLogo; + }; + + new MutationObserver(updateLogo).observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-bs-theme'] + }); + updateLogo(); + } + + const navbar = document.getElementById('navbar'); + if (navbar && !navbar.querySelector('.nav-cta')) { + const rootPath = document.querySelector('meta[name="docfx:rel"]')?.content || ''; + const getStarted = document.createElement('a'); + getStarted.className = 'nav-cta'; + getStarted.href = `${rootPath}tutorials/getting-started/prerequisites.html`; + getStarted.innerHTML = 'Get started '; + getStarted.setAttribute('aria-label', 'Get started with Zenith.NET'); + navbar.append(getStarted); + } + + const installCopy = document.querySelector('[data-copy-command]'); + if (installCopy) { + installCopy.addEventListener('click', async () => { + const label = installCopy.querySelector('span'); + const command = installCopy.dataset.copyCommand; + + try { + await navigator.clipboard.writeText(command); + installCopy.classList.add('is-copied'); + if (label) label.textContent = 'Copied'; + + window.setTimeout(() => { + installCopy.classList.remove('is-copied'); + if (label) label.textContent = 'Copy'; + }, 2000); + } catch { + installCopy.classList.remove('is-copied'); + if (label) label.textContent = 'Copy failed'; + } + }); + } + + const currentPath = window.location.pathname.replace(/\/index\.html$/, '/'); + for (const link of document.querySelectorAll('#navbar .nav-link')) { + const linkPath = new URL(link.href, window.location.href).pathname.replace(/\/index\.html$/, '/'); + const isHome = linkPath === '/' && currentPath === '/'; + const isSection = linkPath !== '/' && currentPath.startsWith(linkPath); + link.classList.toggle('active', isHome || isSection); + } + // Prevent short table cells from wrapping (e.g. "DirectX 12", "Vulkan 1.4") for (const td of document.querySelectorAll('article td')) { if (td.textContent.trim().length <= 20) { diff --git a/documents/toc.yml b/documents/toc.yml index b1d527b5..69aaa9c0 100644 --- a/documents/toc.yml +++ b/documents/toc.yml @@ -1,4 +1,7 @@ -- name: Docs +- name: Overview + href: index.md + +- name: Docs href: docs/ homepage: docs/index.md diff --git a/handoffs/Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip b/handoffs/Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip new file mode 100644 index 0000000000000000000000000000000000000000..03877d9a3f846a41eba6dc44a3e692a8de6da294 GIT binary patch literal 1341220 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

b zp`sh!Ze}10SCc=)yKoaG)7~*Z8Qv@t4=Ag+dr8DOwBF6Lxu4P_{*Y_=Ks2%S;2j|S z$-*Qf7Dk6$;Fqtp#gr9N?M&NsQ`1IzjHZD#xGH(qSMKkHe+{xBo(uuM$h$tIy_~s< zyy;2eH4TC;l@x&(j_b3&1VUXUm>+0skMTjbbN5A6yQ^U@@UqEAAYOYjW;JHa*aWn! zP|eGkpY^%8P9o1NE<(M=;w5bL?+RoGrpCa9tTyb0n(&>0lQGiy$tr-kbgYY-H8pmt z7pQHyhDe%%&W#~4)1*OMcJNCj!M$@7lcIb}l6EaIyUT7ueSknJ?Xp|Igu^TE^OUS- zH1{QgVRzH>g6~1!Q0UB-q%8PQ_CG-_3DvK7CEO)HK?^Uy|NY|HW> zpK@VcuXkr2zn$kSy8Hdz*&0_J4Q9U&b)FC9`AdfKKwupzRvR3)}A3ndG-n&sXZ;yI7$r<(Sz9pM72vC^-7gmdH%6Om4Ql_$yPv|5bAzC zU9-MkCVZ-@H76-s*en^~Umz(rt+%%=9r^shdPhZ>Z@>Z!My$oQ5&HRXoQ@X-Ub+jZ4` z-3#2Og)br(B`V=t-P?DE4_VQ9x66R2HLnN9MoFV>8C`39d6Cv^1!qryXgW;9;>k9$ zX~=U|9Xq6u5r41&;K;RmsaE90WF;*r5956y0u$)xQObSlh}uXUoxnqF5pg`+M;h?% zx>}xaIQpgksu^RhYCbVmNmM)Bra{ecsk~G}MMjiyCoUkine4RzYvVovc#qW*O;*3b z4HRTtY5v6ZQ6+)#N674uP03q%TeJQl7%J7Fv^c7^^uRuB-dPq83TrM0)0jP>A@wJz zjD`u5OA2J8kUK04L>b3kPAfZT=DZW9sLbLGEaZaGQ6odySw|p=6l{m`oSA6fJs|!Q zH^=?5HTB2*X|i3)F$;NmlX3HzB!?F}uM56=_o$XZpr{kNcHAP!3cssgivFelPG9f=0@U zAw8ClY_LWM-g;wIv{Px|WaVJ)=nr{y{0%lTHnGqO{NKwPFS7%$Ogqq5-hH^STHM-Q zh&i7U+Y~@}LlRP{r}BtZe|C9Oi=H5wJYSMWo^AAx?AR=H0JNzz^c(`FbNC@(i21Yr zeWe#L(vTbn*>JZ@J1Gb6iXFvH%p}lL6hE?wy{QW>q4r=8bg8T;mf$-jOk@@l8}ifU z#8YdxJ=0?H5&0Vo`b$YiHkL=>>F<5xLVA^1ni$w{VkD-Rnl=XP8azrdmAl|f$vBto92Ht*Kc^uOQ&|>lEaHzLmy&YrK?*P%OUX{A}T|f z%)po+)G$>m+Db`kAc0X_N393EI02vxh_hUviG|eu1!B7ZiWgOT`{uUrlnq;Ri(wbUO_?Wj~?VG5$Yd1jc- z2P@+rL7HIktQST=_K0I_KeZny@1y3w%CX5RA9{SvXoXuaN7yP04fI!lh;$_d2TzpwV7w2ozX*InN*IJ1pL?uD}iem)+b z?lx!cPR@j@Xt&ZZ#WO5iy)2ab2$Pb~w-b+O{d$30w~kl00pYU&z(8B;(1QY><90M}zx1FeD^U zWqW1*GT>Kj_K4K)WdCq<%<=L$zaafiDs6bsn8)^hRQIVsdw2S5o-;1|1O|udbb2@n zq&~(U46~1zv8>Ukru;^h@|R_@4hV)HBMjQPL*yoZDlHXX&Q4hT5YI+m{W zC};D3{Kq<4OFYYz)BMjVTJUx%juG0W^Iff1pkskA>uM``$noK`bs(CPu>nh$1X^LH zqJ=v<8iJJNN@kDbQROH*1Ek|b4>QVk$QG41R#CFlaq22Dnz88l)p zOxhk&q)6BO0;xr&-G&4fOO#Z)G_2wz5J2=i8ukh&yDUWY6W!5fM)a4YtQ5!|`)mat4n@&q1-n2jY-{)E8Vl~lu|*=MM&;UHi zLD6=nJ`q{{VmZr9CZN<@m@Oj$nGt~=jA_bAxhJ84j45%Jfv#E_yc(dadiChiYO!M) zMsp?^P++uipdo!bQeH%2J(CsvDk|y8EiGg-M4kvc9pRxIzB=n^apf141KxfF=@GDlLG3dr1Yh+DxE5nYyaN;Da&O54}W(Ekn` z!klrcpFbpkUlU7%tjMP$8Fdv5%TS?etaE$`$~CKZhspjvFcQMSSmfgiE}XR?ehhP^ z%oH{st%PAsbiSTo5Imi%v|_Nwl+UhNX3?wjRJ33Exj<~wuHG~jnaq{OvtFDj1S=D1 zID)?=K2!}@D!1ezmugn;QB#&35oP~O=DaPVt3%aK)rg7-lJTcBo`9_Kgl(-HOM54J z9w-sfA;ep1T8l!mI25HUs;dt7q5h%u{PY%WxV7Z)#^dom_oO7@`q85x1QQTkRJ%JF zk9BzYqeOF$O;pr<<4`LIgag)#M(g>V8oE;r<5ar6d7Eu z_+#Q_KgAaGxvmZx2Vd_S9A71EcX+%g0i-H#cFUM28C+T!o-XAa5nuv>`b zc8YY(?=HUU7^w0Toj%-9Dfi>RfRm)Gn>U+z(E0$}iBN=iI(QVZs(;+JP^zP@{)ut@ z=sKv_?ufMY-!!+I>x$0DMe@!+u zfBJC8RmqrY3aarNf@9dk-esvE4fKQTdFo}ZDbY!65>ro};0yw#Apa(7SJqs%H3bVd zc^A9*vGU*Un~n@JyY-xLrFkNHRM65x#ef1AdNy-o^Dm%*fk;Le9M(`^A*g}5XcG$5< zYzMyxG**B3xvST98YrM;$chCvd5UrDP238oXt`NfqazyiBBPcf_5gxNex_bqpBYFV~irJgDx7C&F8c#Ml|$nHHBz-_V4hshK>orDN`&tSjrnKjsSGX?3E9gxzfm z@-YdpvDDC_*#-t=c7E8mb_IJ;3o<$nxYtEJ6b`-WE4pz+yGXEM264tEs0z7AfgPEp zmK57ggQB6uO`&fcqop&y+gtRi4|X+w!D^s&ML{eH5jauVXYY%3Wt8rwusgq9wG#h6 zv7>Raxo{X&yt3|lg+oz4jxwy^Xoh34$SYN|o&{eOC-+>myDhsRnsWWXP|Lp_e5S>+ zvc9x?6pw6iRUWQ|&^Rpt9(7olIqV%SbmHOm{ z3G$8R24S&uky*07HFvN;AX!&U1{(B>{1j3MZMi%`H7?VXe(q6YxPZg-2gT9R)xcab zYJs!@q$pNHyL)>g#u%;eEz{3`^r(k%Q;xM3%?D@6vWA3)LZ4>Vhs1$$j~n~9(4L%l zxS{1_ZK$!oz&I1umAN?;MZD1pgEXV+adi?u6vP2l&^q1n|@8 zR)V+=VltqPWEWZ1HT+ZVtC>?eY7EI~6(5-TA#Rnl4V0l`BmM z>}F5mPvk0zI*&UX$`6z3-)uMrVby#Sn(}%eXLLcY^3zOyeq9I$7@zMTEDWZ*X)}i$ z$D%j4A44zbc%G`*)W@P>DmnD2xY0$b36@_`H{U+UJm#VFun(vb%r2M|(mAqx?$fPbv?dCQ`|1#suQn6a zGqXX(F`naM{k`>iHeedKf}BO6$W`1jJ_1~qrK$~opi-oBgPzdBx%T8^!()3I-2y+^ zzuhg+;OuFMaDqZL>iF7$(D4j9W+^6y?|xP`LFHv^oN|iob0DJUioE^{U3pE0r0D(0DsOmJRjg0tW^*0~400_POBYP2W>~!(OW~?YaSH@=2;7 z(T-?h-8%*iuE)P3$Jbv$kRIj@{8jncG?>JCNFYyP+C8(BJQi<$*Dp(7usrt}g&DKD z^ejsr#{oe4@zPoBS=?G=OKp+A%C76*6+>HQJZa;~xW!(xq^pP!81DCH- z+DSW#l~gs1oYP9h94+t#U>NxKC;V#1&g2+^W>PDugZe_NwLyGOf!<`ZOJ^2T-d^l) zewv!w7eEm+3{8Cep8-dU51teQ^%8jSs2foawUquvN{*@l(-W`pzWp$7);cgwC2@?m z_?fd19xLm4QwAEG3Is3<{t!G43OyB^y*;wv?G zk+=raIZV+Dk7#mc*+AZE{RN0bm1q&`4s+9Ed}dy-41z&>COb?P?gBtfBz2J#zOlVd zUWYmDA30T0xf~9Bgc;dUW?hU&S&alqDW; zyb@0*28IFsjSd{Rm5|emXueRtjcoayFxgq|h^H4|*O4bWwoO$EsLnIkhb4_A+j_EYV;QmfbM!&W3|a<()OiaV6xI$qn?5@A)Fuw=Q0V<)rWt1RjHw_g_v;7t2S|eo zgtqn{z_(m++#zo`Lv6QP3Naew>wCZ3-w5=%iou-TMen3`4mDbuj=s~lK}?bn`Kdaz z7o0`yeuCibvwv7c*G?j1rX37 zJiHHEchRUu zH7cl8_V13U7PVIDDEf{vCZ^B3fsR|s?nKN!o=Hf>*59;=(d7_FY^1Za!X9dBw{?v1 z=rWLOnQ$njLXrl3yaj+C@M25DCYV8{z>%it40qh`h00G^j2BVuGNmV|(6ZusOFz`w zFxuB|63u;j`iQW#5?iS9BFOBuR;x{{-(nysNFsQUmT#40VK>-PM=&jxsVzv>vb@Q% z+)S)nQNS-KJm#ZW#zce!%~>u0U&3lgei&UMYh?3G5>&EWi=<=1Oe2=Fxtz%CNu& z$`UAw54EU)&9T-SjUJUjPw__bn6tPC%GZ}-I<>HQQjFb2T)on0lbsSi?ewpk-E0yf zCKBf5z{MLPDsJk(zFzHA5=Z5~>veIKHnNb+6^}{kPIk;S4#6AR$_-uLS6*|O84o`E z66Qp9rI%L8W?r!%zD3X9-WhyU$Q9M>5P zyz6`~=5P1Syk1FpIzF+uJL!Zn=!o*=fsyyULeIm?##LJZmL^_r5$UU=s{E31xAKa( zq@9BspQ?XG9O7C(grAptnT0%jz4}541*#(0eDrLk$IpugyB_e z=)<9-C&T#u^Gjr($*3X^Br@d^swmQthT+^*7_8$hdM40ZyX&5)?t;+HYp4-{0b5O& zj~YQr#)sbUp4x316Vzyxwi<(Us|XZ{E`Mp!);=T;4iMiLV0bX>8K;1biO1l9${yb9bLwjV7qezXX4gzpu3iC>ku~@+?{Z ztb0BI|G4d?cEmVRz`D{46Z#iVm0V@(Sbx*X?YME0>ElsfIJpNsvbkRO^Rc8eKWndI zjA^2rJ)Hjz8k6~}jpa$6b7)-t4Pf8SlgWSjk08&tj^!(|1wUFf-zw^is#{)96!GEp zDA2yqxzAg$(RO$H(VjoJ)f4f7m}pBC#{;G+_?L2+pK|S*@ArN-x+jsJZ)bB7HBvbV z2+>~~DiQ(V%*-P$2FGnLvarwXQ17DVR4VOuDM_Iu+pyL~s4?b{Atpchlv(l=@L%ls zIv{sLVDMZAxf3uU3!mt^7hOyDoB>K8uPCXr;zX(6Yh+&XCI!&>t4_7%FLvFhfa0D* zSr4mGF2`>iM*W9@u7}_t&G9^}unnat^9TjVh_<%7ku_YJ5vE_GWs(lV zqE2a3PgLY68tNce5b(iMqD0GzF)U_Fv{t2;BNnqHBcB_^78)U`IVB!2K*`g>69-De zX`BW2wdcjwm4hT1X+zXS7WRf3bQaiAG5LAf8P%%GcTx;HgBJ$3KIc=bbkcpqh5+`x zo%bOm3BVAT8_~Yq7hRI-ZZ{y3I{sC24_OA#vDl7JT`FYc~d+wc;&hIts zR^Wy?uTqjX?(7$@M3URs`ENbXLnA}eiVTXIdKsN{&y(dl_gk@-7k+)u)I_1!%z=$x z=kgauOF}Q#`^e+@U~tywdcQbu^WH%j{zxJZR6^I^U4PGdx1I9a@uvLryL`X?drEHi zc0QT2{-9*};}d{e6mm0}|LvkF_g;P>toM7B*<`>Jxq(=m>dbhtTqHwdvtjfLPyiJef`1_x?@S zAi$^QT`Z6Fi=#=?M^?V=j*xAfYVpHjj8|@ctsT+vf(ZW-zU6YI#ddAVa+1Nl%BE3u zF^~r>I`z*D>+urs-Bee?1$=iD$?H(-B^=R2&a2W5kf8Ewuh|sDL-V6t;j~?5*hjrx zVQ7(9`lV;AI>L}M+Ii@eBnmEO7klidU@Az|kY9C4eA^YA6{tPgk~3~Y=@tE9pz<=F zMnvMn;CVxiPAPAqP=m!@j|r*i7B$ul1sm-4yEBuKZxn_`|3d|B8_U}Nsju<+oy-WM zwR1i%Xg^tSUvc0jS_aK^glQH7$bM-{SzZJcp*b?=62&6a@uP3u1K@IC7*CAdjMbtu zjXYI6hF{5yihzPZlk~5>CS)+QS_iC0L{>9twz%$ABJGM^{0n&zvBrf`us!NBx{4PU znL-~br+t(_C5#KKIb5H;e0Fm)W8`CJvb}ZTPeIFn|I~G;Qr*gv${@T|UygKQDzwnC`%+JP)UFz%_N=+q#8*1E)Mk#!(|lAD&l;|6a&& z9UA50T_!_^z84Hrx=@eYA`|Luq}@DTCtfE}uc4(KmiNB}@{SF@i$iNgD=jjKe~Q%_ z;vL<%X!HIqNQbbZI)z51?(oomP-J^m4bcgkL(zaGoZC1+$8Z(exBzUqsqJtV!`GZ3 zWF}*4cMxu{EG|-DgFZJMvE(WYYnpySHWaHHx}-Xol1R|m7&titRT)A(s@x1ZJpp9| znL@aKzFy9%B2c0><+0J!L{hQCQmS=3#a}xPw+*XBt2}Rz{Gi&w-9HP4i0FLTt!`Y3b;Ta% zr)&wrG_${{vfIj|NK*OTJ9#}6{Tyzic%&Qjao;HsZIwbUNyYD|spt591ISg$PdL-L zVIK$H$HTLCA2{IT&5W}Du?jGL-0kii(!x0rg#y2sFGr8}qtWeW52c%~d1ng+VsSf1 z_v2CRsmbmfjc|84jdcekKYT|gt+#I1RI9e5B;p_Yp%*6_ zG`bFxHNvRPiGbHS6ZJg+g09L zOk6qmcORcC#dV_Vt&@0X-}xyU?O&b^-<6gcn-Hygf*Mf7@*yAX|4O-edahEcc}JF9 zUn(Od6gQv0`PoZBSFl}x~ z8iyXL_a(4-cQdoErH_$lSZvPs%A%-f4lYWEIQndhFZ{6gp=P@Kdr2~4rWR|e2vBdb zptAE{%agd&YVhjX`=f@L5IYVHfW>8b2hB%_2WjhbsOy(K<0<^#ta|0r!s~LSK%?7- zk`N%e;EHjl^P6N>YMxbgDtz#N1YqyMqTuo)Act^TaJcM6z*_pX`dVw`UN@BLh#z&P zG}L$yzYpVIMM~1yWTmu~Wq1=H3_9IZ}@l4nhr(I=FroubN_-nUNq|v`9KT5T+{~7 z1A^6LeZz&1BnM(PZRnT<#>=@rioWrn>6iXZLtOVyDKa}$nbh3t3~VmU#x5tYkl>Ji z#?%oevG8ESc2+#7mrh|13pFRs4IUFbzSOgv8t83 z|8KF`gKMm7KbU+e9W0AkG_)2D1y4F91CTk>;;l`jOI=p8Br7xui?VTmV%MuQ;(r(s zcHJ=&r5}nvmW&9$DHCZuPe+sls#lojv_KN1V;yx~PzSe-7VKaIAjbi(MG0sJ zNRhwv7nR#v#I!Q1aIKU4uYP5b8>6^EXQ*`OFu=>)RFs)2jsM9K;K_jT67}mzX22NNh>QLdBZ`IQL10qmh%A4@(fzP1r zm+ehRo0mfN`%ChWZka4p4@(2D2b(K#rF2ZBA!PMa5v6+H>HAQ#t`DEj<`58UZl0D0 zYqs<|?QtE4NRPeYQM_#!I^C~Y#FyKl5bRs^Ged6v$#09C_D>w6bl-5#vxDGXZ7m;) zJ`JAhqhEjOs!NTod_ryvT8499N|| z^NXYzit0x2!(Qh>PwO*mwPI`u)V_~nuje~dA3i~K;RX4%kC)cFTyLf8_w&Q~ znYuL(CP=3FJpNPd4BsBd8i#R3;>7xo>0iWe4kB_9fnZXxSPM@J8W6F5)(RNxCaeiM z?tI+t+Hus)EvtXwpE}b8X0eFV z`kv=k2w7s;2Hr+gX_AlJc9+;^tD7xO2367}(=MlY&ehM#1--M~e>y%LK0?2XI}C$O z8u;j84WOP&1FJpkUWTz9Z^|GT3%TE@0u{^VYG7!9r+<56 zsN1Ci@H598v$VfTsQL((iA#Ri%i%{90>lfKon1!jTeO8hgYsT16pzx9K9XY@;9|~Qyp99j`82Zwo5J{!# z7k65+BnQYk)?jY6)3K~b`F+#|YS_p#FF`T7>6x)46r};>tzn`&Kf)E$9H#+&`UqWJ zql9E@8*)$iAp!S1KQh$PSrMW$%!*|iJ_D2#qQXH?OhxRe#*}x#P_m$un6EM_xLo!> z%PNW_>Th!j<=EBnahH`8hR~b2UecKh1EmjMH30KQ8U|@zgm9>Isf#3)l>bG@f^zn$ zR`ud!GZ5i1K)6yEX{-+vigH``zy^myVvebc&DZ}Bx77!iQB7)CJ&-ior*s2_(2*XB zbSRlDz$PQxQxel^nSuSkKnf>0pB@w8XoEL57EX{-pfv)Q4YJxjDLZeBs9U8Y&)Ceb z%>?sb#I2}us7otLHy1jTgFq%`?iDqTl}U0ur8-63rPQ*%giQP&E4*8`uft{?3*A3BFEro&(Y~C(8=8P^WNljJ_Bgj?03Tvgdyd{d4`Sf@;>X`nY-xPLz+Qy zem(oyo1y!TA{@?|$Eepz-W>Tn1ZuJWVNp9w{Q_&^YsOKqNL5nTt9o}{=a-a$M9$T$7a*F>`foPpW@DFbG)*7 zotygvC)Zr~>HT=@7i&q}+`NgtR>M0}5dsYD$REWxByWPspi0Hue+Q=AHa-m5-Aw>F z_jetV)lhcpT|5mw-c`x9xwPc$hXX4TQb!#At-LxX@jtw_Je-W|rN39E`UV2J|8(KK z8qD}Oo0sDz(yC(PJU{(&V~hFSOv8+eEDqZq+qL_TodU61|GJ1pF)Er_5nGce1pOh? z#FcKSJ$B=jYRpFmYx$5&{{VV(C2j3Zt@t@c)@z_x*O8}xt68Pydjk{4CBDgW@oni> zM$2V&y$cg{%5?D`hD(I@&Kmf-frONTs?DIqoSl6_2U-C8VRP;+&#fwKjp;qg$USg7 zC>Q?F%1|4TSAObO#=-)Jx9NC5oE9jBpDI>`e^=Gvm_mD{BJJD+#?rcz90XD+!sww)Jx%PRg@hH|L zbF4!S?`}{^0nk2Z7mN}2Qsv|OdDovqa5O3)-!m_S3okEcR2TQ|EKbS+iH7C80oI6< z^w`e{Rz_HD>?Q1K=~Po_K;qYy1{AZEk&BP|T6c}e-JqZ8&>->s3u^O6Tzv3eDemhq zn8uX>qehonR8yk=olk>xjlnFD!anT?9CAt+jK)bG*3zhie{l z_H_Yd0{~312{k6lJisn;C_W=}aA99R%P7H6&f1GQP@D&FX{SFgMqpHE^jx!75!p<) zLU9Mp!nI+$P}r&JL8!(?fWsNmF&FY8kEj;MgcG>i8gvZkRDElUx&n(fOGV#TN#dIF zy^U+SX(t8U!5oPCXvWCeLpK^~C&8nnmvwY4UE0I7v1%lR2j$!=L);EMZZuf8rGzQ= zBj0TeUZEA7P+WN79P+?lAf06A>+F#^BTn?p1t?c%5Hl(vPw|FFt%sfQ<%#pavx!1I z<+q=Zn{|cwVpGHy%cBl~Nk~}+6pVFf0u)sDzGQT2EEVY?3{*mL31rI*CCu#ENK827ti@8@1A z*B-~9m3-uJPc(OnKA&QzL&?hxAlYeBmu5;%B--q)A!L|HeK>#YvYyQo=i%n-DYdSr z#7%uWbjwHk9V!f%Ik>hJGE`njRP;NpNxH^j+|W#auXW#-b9XFvcVfu?Y(r;g_cXfg z^Xc`iNY>72SNhm>d}(oNM~dAoxic;#7LUH!YuoE8t=TWTAIv-IZ{S$Q6*waiZ{X80 z-RS9j)2SkZxY#AC6mWV1K86;Lw_3t1Kf?=Zg~n#A+`)P*`dhl=f{M||$;a#}yC z^2!y+)JwWqS@WOq+%x{0WY=IU{$Ma$Qxm1bP~@j4y-?6wzseCCYjQ!DaY}S~x%%%w z&1yar+zNq_Z+om{YvRR1HQk0Xd{0i>1O5_wP19aaVCQHHQ@fctdpijicETp?YbBtW zg?vlp&+0}3Xo~zR4QZUSL^No&$1%OvMGlF%n;#L+NwloH-GLh-(H9@0T$08ft`)3H zwdZ2Mtl!LCiwC<`)O};9Sg}JTP2>**t3@VD97Pv{GF15l>^>;|I~cNm$kXE0B47i_ z02qEJbt8TYl<+BaP3Kw z{asL7Ws~%BokW9_skwST#P^ShoXTI6`-}^Ijod#2hiX`D3<1K*3JcZ7&KxwQm}cK8 zgv=VdQz|k&RY9?m6Fa#-!-`o@tq)%@z~;bFP62}Y8jYcAvDP;8LLDG>LZz~y6QHZT za+sm|JV2urQ@%#`00=pzo`6n7{f(YOEGYbn&U~Se_F7%027Yf%(UWNHK*=*J9Pkwc z(Yu#Zs-BVs$bgKck0>08%%q2g@%0@S*MKap=e1>7x3ywLy>z6^0$%MmWmIXn!0hm8 z1ohkNS34I4W-dL|-WX*#>>lZ|t95XSbhhAYY`&cKdV073w{u>z0vzse0Pms50m!Nj zL^#b*{JA@<=7gIL9f4#H87}*AlS4ib^U!^2+6vdON29c>X$hcqd)-h>DMnZi0Y^;2 z-lkb|_la~%RZ-n!1x6(SXGiMl`*XyFt$%m}b*9h@yB`wl=_0_^0Sf~%oNB1-t`1ek zy#E`Z*9#mh;|IOTWuY{4;Ud9RHeY25iErxin~A%D_D|gg_JafaUcBcX3mSEG2qSKxx z`^)$Bq3tG(dh^h1cNF0S;r>R&-I0dZ=?kc8{h4A@)ST+M3UhKAF9G7&HP-a>hu7WE z-Xa?L0=Hm095B={p-&~JujB5n9jBAsjVLFn>hzZ@uUSN#X^3irOM|q`a1gEZai{E&wqO66)0Y$+HJ@X#;v|h z62rdbIhl4`RSN6b*5gi=7L-GK=4^r!MDhS3&OP{Y)t-Z zfXdgQ#Qg}XYaPz%8HAy3f+^}gt$;77`d=nLoM05CA9Rh6UUpA;#=-LF^Bh|2>kH|j5yOy{)6Rq;C z@tI|4-xlk3xONq=+xfB|=+QqGgdQ!1Ra1X9NksnI-?x*#z5K5rwD9jRQ&;2bNT)$J zd>m^J{J!(aU_Q1x=3;WwuC%HHF@{?k2?=;WrkVMV+)2WdZph6{ZEfU_GRZbkm-rZ0 z_A%3&Kc}hVhLP%iI^ADBI&9iaHNf3QnE5sPs`?~8T%0X~{MV)C#qaYQ#EW4G8vT)@ zSnM+FU0Em>&!#7cNncT04WS5S%lpP6-A#H*gIp^uc623ZQ6HKF5Y-V-c~&Q~ByZKO zxD4IwrE=j2=gOvG2bwzMX|vRRx>%h!8d02r5!GnGdVji>&MRWS%_J|1QL9suuB-bcW^riJ*OBWjr4bi}_+uZ3M{|X##?Y-$<88SOlur z-Sb-~YnB1;f5RbH2K~>C5>FPWn58$H$3gw+qf3isj4j)u#pdblv3m#@^gGpsJq`th zYE0rQrYP=oZnIuA8;`1UQWQp)k!^a{t_bCE)B!krbZ99Q7xaKBB#OHr!5x23-h5!x z%$l#cgR{EhI!U1p?aS6siPz;zZ!zDZc$3}ag%lJ<&7?4Zy< z1&Bv8Jxaiy^Dt{yFB_VJnhjB!DBZf{_UqBaVk=$XPA@{OpeRgesTJ&j$AIw4Ube&h zqh(-6kQ-O08i$nVpz9dmFFofYOg*_l)DMtx1J+2Lv0CG-ZX5A29aT-yQA53tFSXk@ zoJ;z!YMKOjyfJvdYDJT6DM{;pBfwW+4G%T%rpJ#!OoMZ3>A&7uCNL>1kEGUpuDy9N zbH7`^nI&^yX7sLeKaElvGQE8>areXRKI{7o8D7RZB4qg9@3^_xyom~nLdrXdguJOd zXW)91_~m28FnLy^>CS?-#g7Duq%I#k?-r?_Jrf3&eYY4Ys*+H5bMkwkuKSGVcnXv8 zQ|FY%4@lUKh-P|^XAk#=u|U*G+vK;nSb{0vl=hz}I^3KOE;(APM@9^cPGY#Ja3u0e zpLtXh&5SQR|1cFsqblQVxzk(*&6A>Hfymwcruz*)vgW^S)Ej>icW%^}XqXalI8h-+ zho(sO#mfeuP+HxnK#7Gu=#a9{1R7P1_i_Cpx_y^ZeY(6tpHy!?pFtC~R0}UGXp(R- zEJ;g-95wZ)E5}dnRg2?cV?~V*#D8%kSbX0)ABK;>S9B&Qj;VJmj^MILQ7}=6GLW-` zbUIjw9;!T4m!c+vgrF`Xj$Kgw)C|3X?4xp_h>q%#0-&a12qY-??^`BlGvk&=tCb7m zV;CEcaZo6MEp5*8v>N8yy(@h8c5`&J*j=wTTW%YCPX+uZrF@OQ(OJ_`VxI42k32GO z3Qu+8J}{YM$Wxfe$`_U`RRNPgjm>gSD?*S;!=X0f_G^o2C)i@#g=Lc4heisLPed?w zx9L%(4yh>CdacXSi9~-Tor{=*XLIRfx247A0%W?By#$Y<%Qpq4rkBC`pvv}6lrhs(9Eog^0P7RKoi82m^di$+;N`{8O!uyfy{@{?Z z?=7Pk4U-g1L?MEzAIlx2BK9;M^r3itTVOzZ z8XC~F-f3RythX5ZxYztu;Dh(>W_#{2>t0h=@qC>$$aqCJ@u~6g06HK{q_3CW0Y%Wu z;EMrVlXHp#PPOSl`&kUT9800GQqM5+94~q@)wrMRUOAs1z)8GH$&yf-+d8V?s)!g z5xeD7RfJ>kQTcT)4h>+Fzoo;Fbsl?U1z$WzqbxxbFi@dlrh_vaEtH~G@Bh|SiVOO% ztRC}E0<-(~g;E=Dr|ZN@`eaNb7%r-w7EY-bup5|t1fj*_c4=a6;u0em3R{x?mJJDg zT82SC58kgGE%$@k%>Q(CP#R(5ZFawIp3c+zbzeWXL|))GL{gwO1I>DPxI2|9_GoO- z2tI)8TS$O+_2IJ zP?Hsuk!Brv>M6*6J#AKb(L~oiutp^n5qxT{78JLCVSC7EZHRx6!xe&ADQlVStga%$ z=OS{}gtsdgZEKcax9H8azv8-|w*|MoK!bNQo319l2PVuxbHm0C+crqe4??;{AGh=>YmDyj&- zH6eOX?XpX@HY_*Ko2A^zY^MnKo`^Sk}3G{d7nhuB=_m4Ace*adFaYi0}%-=fWni-M8{8{)Q_)t(uZt(q=@jsw*wya1?> zB5~ez@!}fv!m;{0?f zFu%&VZd=J@ls+U;z?7=rW1F)y+hOABtSimd4)>!h^TNwdv#eQkb+v09>g^ff;Tfq* z%>$Lc)Z$kPsN-+2mJ^QM=zK`0&7OG}Jn7$m$rB5D_SS_M?#h5IgI0 z|8N>s&$lTMDE2a_s~G4fa`fFL#I2yergP{b355uI|-;Q(vZFF~v8}CU#4}Y`3eQ!C=Xe@{uCgt`iQED9Hz{%(ytkEge+0L2x6-f2Q?eq7fSEsf{Es zG%RLdmbfX0${d*`0sMDH>SaRj|2_EaE5eQ~rd6MD2fkN{9T6M8?MfYT>%Ot59b=_7 zfU5PSDS|vnYso-wsr+C;&2Z^qsPDKM89)J)L12)yM3rl<1i-m8r-id4CaMqTiNpsK zrJA!B0*#Cpgp?_zhN?KZR89@vI$|0{CNIYdfaJEcGX~b-Ynk>{YzOc9g|< zLHx4-unuu0yw5y}wnFO90R9O}fw4Sk6H+ofJ*#JK{rc_navuaVencJ1F-~mjdA~HN zr5I~F9tHi!)iH4ljQ7TT9&QOpML%BQu*~?ScD~L~;Q3zux2c7Sll@y$j5FlOJF3MD zGk2r3xA5zZ`s6`f-r~^f_9AHSz`=&dABXAta!!3X>iZ3uF#Iksk#^6ni;drLWtFuQ z@gf_0%!X*PbXM_xr+V{}X0MHX;kp!fYZs0?5<%QQgt%R{ohoeZ4=q@vIY)zO2#ure z4(c=l40A&Uh8yyx2ub2c3sFDL*b&zodGY5W#OOnXS+Zc1`iK0bXcO%43-tIsd~a`M zfZs8lIg`RCWZt|#32D#SFz25NQDHT%NY5ia3;fGfLSOIHhi&`e4R9ARfZr_6azP-p zw(-?^iN2xpn)J+dFXrl)-qGUg1QM8mv<7~rS#-7XpW{i z9NJBIc2XfeH`L;LKwWWRs-7Q%lQ1F1t{Tr6=R?M8UC`?cMz|8|e5uNN`{a4ys_9D5 zE{f{V=^E2sn8K3vHsH)RpffJA5Ed+zWpMDqUKG$P+SXc?K?#uL!EMr|?!5OtdQ+T$ zJKN_4H84PKB93M9G0R-~jZl_3@gHUI%ws_ZqNPZ&g);E_1gupq??T~4t1qXBhT`h|0 zfN1Uw7?72x1VENPRt&gZJPw>0RWc+|0o5ul2YTBsS2-Q=7Ci!eH^+U|^!Bf2tE@7} z7UtoAu!&yM@V9i*f6PJ6#(j?D2^(nx z6!ppCOg~lcU&_^q3v{y=1AYGk&=heIKGjLfU@#44s1Bp}2Zj_rN9hzTYHGq34(i$M z*tws8t9$#;Xp|6Q;sXT&7U-T2e1)JwEXWEz5MpugArbhPMZ?Rdy{q@4)+glI*5lW4C+{MeZe zbPqw`6WB}ASm(Ew&tR}duN1;BQ1v8Ycel4OIv7>bh(_E==+-n-!!YI7#pze0BbYOT zTJLK5A#0qKGkb;5-{IEju+rc)BIIXqgJcZz$tA#E=h$10#|q{cchwAiuZzEmWxC{i zI83plnm}Z841Dnce#-e)Gzn9R=m?8KGprc1C#?NxW84O2yH2yt6@g}<=oGEKr(`%< zBjuMtb#;CPt;^I&@L6#_@^JrZ^>!yN2NR~>NLDvk`aX3-rAwriq@KWDCBsoo%Z0g? z2UDexWQmekvAhq+kA0~Tl>MSP*l3mFdR+VQPmiBxOm|w6q5a-0{YCCb$XLG7dh}iD z7^Smz{hT2QI!_Ad+#akc)9Ov@G5T8izI=U}0rm-BM-(kkv;VI2NW`V`I;}oD^sx+L zToDeH{xEh~cS7~|>Zs*;Z59uN_z4z|J?L*_P-D$ix(~%4rezgfojVr))}?J<`iasP zrL4fr^U4-plt(n+cyIuIsic{m_rp?wdJfd!otX-Fum~`T@sVaaJuKB=_bXn1nC1^u zUc&9MlqGDfb1hUZJza#@WiqFIs};=fUWLl`+Bx-=Q6u6a+vs4U>wb-qAj~#Abc+s| zjG2gJPDCp?@tGl4?s&-cZ%E60BKpZS}n?>4L4QQd|&*?awu+%h+K-Y}kG zBMY_;{k3Nj10%&SNQpIekz^@2m^~iob+R2q5(Zs_MzTaxxg5wu2?mcxWx&-67_*_|GzDgXiRyad9z)t9x6FTnEgy_WYa3yGJ`-S~ZM5;UI#_L~ zN-Hql^HmW{mRS-`Vw_G4ydE5#oJ4xrTIn4oD(3q1@_M?Sj_s(wBNMCp?$e|(RidZf z?t}JT$EC4+19_{*1sFc>9Ngc4iHVTI(b1cS{=ux_e*>P5kaRU(Zaj8yRQ<{WW<`Zm>=Maq{4+g5X^RgCmqF%wv^`bxMS`|pT-8$I zTIfC4zfQeeZBH&W?S9s$kSfCTW4DrdT7^-KPYcVC8I!4&dN96?TH|}qw*E-KsrmIM zwTf7t_Cb^!Cj(79zB;YOwdc1G-j;xB4rlAbV_8UB*x<3qb2Ia>z;Xo zG-Wkd<;~@Fs&A(<5ls}Lctl1;9eQqp+H~tN1gJ~=4hV;#7g!sFG!+FPkQ+Dw(^?u}qJUm^AtSe!B-xn&k$QU0OVL7A~Sq zCBw1q#tN&nVtW*WpmVK16Brt{Yc(uUILV79%Qe1X2G&oKm9ayZvNGeqjn;>MY(kny z-@b?=$3F7E2}B!I!Wy$-MA}pos_js(EAl4>1Xkl~5Tm&CqOF+wZ=-2MAm*!jfuQAF zAgaz{n%6qyLG;z(xr1qr{R>CEAfBRwM{wp9PJw4aslqz11g3?Snh+&`ix;MN3j@z= zdlM+^K9fk&={xGeTt;s!*5SzZesz`ENK(D`QBtz+xF`-{c;G4}S&gFKio+QEtm7Fg zcLNw~(e<=Pwf|zw)9$`K{GUNIt^4D+F9~nAYZ}(c^Y=~6Usl$&7 z|8>7leZ76xJzrfbqOX3x^vn_Ee$thyl%ADeRm0YABS`v>kTb)Sj_}u&w{zo2nC(rF zArBt^gA0W1>t0k%&~;H)Tb7>LRBfyFn=qr;rTh-Lc53&!YRSz2xq5Hlg_YDVuaHyC z;Z9rTI-VF$K*p;&>HF59(&KCJefQq|Fb`RzMLDrFer%wcD2D&j{(QU~sV)#2qpB?4 z4%gxPv~yzk^ml5HG7f(%FPr~r|3fbm_pD;WLI`W4#epS>FF`wbP3DkQlgr9=Wy00wP&&2=VTNrTNi?6SPB&8b*9L)? z@~aOCh%+-=-Y6*_m+08sgs+HU54{V3Pw8u#v}!o2sJ5{TJ8m;zT@5PH*)Z0?kZgO;?cf< z@yTVx4as`%KFoG~pE0bI#(-b#dyWMCf7~oYBU5=dFa} z&?h91{Td))xMwq6;NW`%E{Z)-j(@dXFRVm%K%4!MEoXL4wJ-M5I zW7*Cp<`O>Ysw>r2g#A<3Aq}vB;u3w&Jo87LUw@Nmym?zlTcb-jlAtE%O`{5eKSF2T zhU=9)Bc|q+e`$ucDA9`F%D+V134K5W`oL@Q3faW%xHY0(dbtLsSO-zysD4Gh{*cV# zj#MZMnM{IjW~LH=!SIR*c7QJsl@#@!skKQX-8Bxxmy+GV5$91^5bFzu)%`n(!I({} z^TlVv^1bu+c6ooUuex-0e-a-$g)}8Z6A94?@Y3FG@nb!ZE&djbBPRuj!q&6?mgU=i;ZMz> znQS}(l8OgXiss!I-H3=@e6J)lZO4A!HWAtk1eRH+J?g)@M;^s)UVuS|1^h?a-U5@O4hKaG>*k7rvwZ_f=kYwDiXcGf7}S&xxgI^M{U=mngzGU zSjPE_f-OH2sL{Z@#E%uBWY0wV%k>2pRQM&E5pG~ZL09;_81rxlH}0AnGV6fI1}S2F z*^SSI&_lR-mMaYyen?a1zl6I_ZSsCJel`rpvip5f->Q`r z2fud5?ctlYevRLwno4t_{e4&~jW|B7<6&Dne(NIWyqt+p9leaqyQ{Y8CZ<3C=sZ!3#t_+*WZJnhkaqjFW_1?wl#UW`_$i zBi#(tySw@T^7Kteazr9RZT`mm5=q~ea4p}yQKT4pI^Hh7YH6u(eKK<14M=i|V-{&; z`TE1UO%W%;JdJEQgyZLki(K2bf&L8w!{D6&6^6X?;e)1ahPm0DD8p^9v^=w3B*8V>SB*{@P+nZ%nET1!1 z@e|QW`UKs>l3j(FpkO%iv6bb#<)2pWA;?Y#S%hd(VTyH&8>4jcl%PS0B|J8SHtJF+ zgL)Hk3bV8yfJ5+`qU-vIrmiN1;Ea?-MmX260m}9;%~ZMA)G2^E^CTbCYcnD;9B&L~ z*b1jHUZ3?aR?b;Nn3EyLf|cygQJ=|QR+=?DvmZxH8ElM>w;n|@}wac zLg+c}p=|x3EoP$MV5yJ5TC?p> zv4>d+umAt$N0-0Z2SjoaknP4P2^WJc%Mzr)La-4a_PT9I)z_-8QHSezJW#YI#0o91 z%-PpQHaBx{k)o9srBqIVzz7^EF3$vhZT!|_mxa({ZG~435_DzPlEK2SX#ecW{rdw3MH|8}bRA;Wq zgAd8%bBaS)3Bti5Q48MTT{*y{`%cLw{<{m+x(C_`jt$mpPmTn!;?|k84yX{xm%u`j zWo`a~P52b^YLAEpVIYu~Co0r!XV}nJG+|d(Z1}f_bF-UVB2p>MB~aCt$&z6Xl$r!p zJ$nEH2zfy^q_9n;gh-`4mn7>X*vywGWZstXY83X6^VLg|3nYv|Gb@3JO@GLoG7;_=Ll2j7exe7d11Kc z92?@Sjx}gaVYx(=bM&$%=&Mxm0am?r2`xv&>{OiT?z@aX;;;CZ8r|ahFa&O&Ev<-<^ z)ghhYqGlEkPvEoc={AcqF~}x@mCX5jgxf2|X?09m)7Z!gw!{%g_owtn85S_x zuOew7O+t9neXeg4@}!D!C2g}e*;$x4JDPpmJPhofW+c>&GF^SDGa?eM1 zKFmB!T-Aw)Bu)2Trnx37$J%)OtxhP5AiZ5jsOoO~Lt!q{&=OXRNa3x0ggJ)L`#tw; z{-DH=rT`vll#ZczXB0bwG8yxYer3r zg+P^3R^K*nnF>7)D+b@E-kE}~e%3$oafn2@glj$qjYpa<^VXPqfq9P*FqbPXHN&r4 zh3NKd9GDFmOlv>NJtvv4mN$N$dbBYRe6%cK1{aHSe(qA0(w@ua)5wa7$ogcOw$Jpr`o_q~z)3+u znQ!se$jQM~!D(I4jaS1Z3WA3=xhQY*>#{HREPuce(K}-6ds?U1~N`DsN8L~rS(Nu~tem*Ff zre?CQ#+om2s2FBHPBxn6#!RX>%nVCau^kugR^@V8zFto57bD2J?vx%L)9N&tv1jqC zPxD*Zu^SdNAuZI-u4zA3Ha^+Hxvm`*6w@_r-Zxn{J&i6^HeoDsM0GXW_$g~xzrsy= z=-xLe49fRgvEiBVr;OX-W%@5wW*)Bb5wThJ45W|colG=49o8*erj@1TW59idsR42j z9KEfjQ&|USR#W>~Au?|!3nkwvs+1|x8WY~GLP^&vhaBUQ2Ug6YtvoFpuTL6NYA+*h z%T6;k!-f%~1)XwMQl{vtC;kTF2tdYqB~7IvZZ{W-yao*a>7Km6;Xx+E3L52p%@PA` z_bi~m>ge#Q`1*TUmvXk>YKqpSL}5#}k)3aX@cIS5T)T!$$Nf1U_e&7Yit~qWu+eA~ zIY3lmG@}y(z!O&J=GM2F*4MN9ZJwvg&&-Mahp&0}64Up=_VYO3@P)Cx zE?e@IgfXI3%cC*L5$I-HS>Tagu6Hyw;LQSy@kqm%FgMAy?FVO)dVxy-{g9Y!P<)gV z?EC4_xqEsVWZ2;jVyxsQl3VYp34ye8XRyy8d?H7~xBipO<){2owcNA~t$d&0vEzq1jPcAGm$S06cJ3eG+b(KT3a?7WN^&yx@D$QtwAHN)4Sah?yJBpq6k{)0sZJL ze!y{BUs{z}oG7b~OTAW%E5#g^Z%k$Cv$CMyDf{GOyiLqf@R7pPz`0G(G@s z%z5ei`>0pzjNffZ)DrnEvJ4DjC5k`&hpGxpryBA044N*MF2;%R7ATFY846y~Q~th) zq#HH8DbrXTMC4R?X?C!qJ+e5v`x_6J!)dk2>sGE&M!a!!LpP0S`*h>;-D`;4$b{uC z+V`kcRIYIvXxgJ`(HC7pt2_Km)_qYa-R1w{tlSScqx?am);d;iYtevD(@IM#uQd-9 zS}a%v$8lv`S3-X3yr9jN>HTM6`9~qgdNW$tU@e%NN0zPKUzrEWwK$T`UjAh5(Zk8U zot?so=}+$ZV~~@>1YD^B{^pH_7dO*-g5)-|B?gH&oK*n|apUv72UfH{tYJtompFO6 zoDJ_cxqN?;SUZM`4cpz=t(bl@{Qhm?{M+5p+Q`$}@n~meU|^-7i#*G;)ln?8IE=dy ze8LOl3GRc55P}OF1FldRY0F(iC*krzEUsX8cNY9llE}RAewbNtF(nd` zJW(!wRk8H^P)EM(V)faKPKM|$c9z0v(HxQ)VPdN-EQ}Uogm0K$7hPg>$KafMnc1ZH z`(H97jb0ypI>v0^bYi=ENv>A^lZdv3Tn)8sASG2x8hnYNVm`8xZ?!z^^*e;TUfEM@ zT<7byJ?_IyTQ_ce9Onx@G;eZxT&HZ>MYo#3yQrVw2f-{;%9Oa43r)^8Va|7)77I@Y z{!_slgU%vCT<|#k#qrD@!24#f_Vz}K-?>EtA&O;VV^!1UGfy{Fmvt#~ct@UC`M#iF zhbf=zN0QbqVOT}UzH)1@5T`DJ1T;M#hEPvu*6*oVvki`?IdF-(qf465oTzfWZ zXPS2%IgdZ7_>gm^W)5-FWRwGc(*29wGk#o8=^h3M1xeN<0E+behku^B5+uA63RP_^ z4nk{XV092GKC+y8ajJ%?a2Q*f9asy*2d`840CKo8VrAuKTcP^xwBrVQ*tgLoAgNJru3@u{L?W^6Bg!hV zFh5OsM7RL6+!VrrIVD)flAp_L9FcxKEea1+*M1<+4vS_$f$0%RsIC5(eP-TRtpoypl zsg(kTLRn2^W=KXuuuY!)m;EK=A*wI<2kKFaM+k~ruRWlZ3y_mCNl#$|YwuEq7L@`@G1GgTy=TD)em;EFBeF-{51kGVGAtCKI4ho;B#kKB%9g)n6@c@{z34qr}_DZ zJoIj`9Tx@(MgT@L6oe1w?vK`miIdk^Q20I!Qb?-cC>e1HecaD~5)=-LOFh^CZ`452 zem89pQY(i97sPXVH9lcOX(aoRqnn(!RIPkhsknc6SN8Ucc1vLHYyXI{{t#&g67d)k zb@=mB2=gz25uKM*;7Dc(J8FSDqH<`)^{vjCJT5%dnSrJla_~)B;)H{@4!t#PqAad> z{6C`JDLfK}YXY3uo{4SSHYc`i+fK*EgcIAgZQIVowzl8@+h_NpZyHbEbRSgJsY(%; z$DT}cN#)qbJU5K~s}OA~6dhprTVNS4sE#b4&iiHmk6BgIv8il))RdORz+y%)`hfWQ z4|JT{NGVPodB&kXI@Kr>%ZUq1L1;L87LRd@bxnx1gUh9Z&AEf5X@m4c5fLa6F#&_( zUyE7Yz71XMQ%}=x*otA76m9({MxBP9O)Vlg|pwWWf`TPxfr`3 z)M{Z=kPWbG1A~Zmi8mK~bHTkKVZ;gOg}^6%2n9ej3qvgjfzcdP1VXw6!e78*uEJuh zg7&H$_dVnNOoJv{!jLTI6E8oEq4m5RC)pwO`Po~5-_LIy^m&f<_PkRy?^*F-uRnk^ zN~|jWAOEJ-u>+`Ev3+c!@_Rt#iFe*0XrsI*a`{vx1(T9%6=P!y+#19`L zznWs}fbA%Yc_=p|Xl(X%JO_pkI|~|{n36Np#ubnT^^Qp%k|%D#DRwYm_R8WZb*rXQ zsb0y|W7NWVu{|4RmmJ z(Cw5kZM}ocfc6gk+>UJ~_e&yoUy>{0v!=cc*U$|>q7dI|bx;$H(4`jfWjTuQ)DPij z5K}134l7p0S4L^b+H*wx6;wTp!1LzD0>RYYb~njt?kvTrH60^tvf;Ve1utG*fxp!w z^JM2P&gWX{!KHz-k1RphFFO5K9P3Z(k9>W_7P09SLX%4jxBRgSggZz+Va^v2 zi1I(4_w;;!EsApH^EaF|34O3mm7jD(W0{4iW?>ptn@F~1hhaXZ8R$4o&nHxwu877g2Hfs(m@@|W2PO`zg|}!V6oacNQ^)fStxl9HWtZB z5RE+HdVRJwCSf*%L=Fohlru1lCs`Uf7?HpM`;P_?){CE*ZSWmSkw8v~zuQ!Vgc&g& zg_)r7T1`?~I%4)Aku!W*o9bU9fD`z_qaM$Of?i#h_ovnK@Vs0;2eb&#`+WUTo!f!i z>Fl{ZrUBs8pm*~qzvRFvD)l91e_XTF42qfccvU#u5+ar%ZrKw5Q)adI!V|~5V)-NZ| zA#uRK{(M765kFJr6fK|udGu9B!yo-jt3R_Xr4f-J~+x0Y}fRlT)SuBEhe7gKYPP6m~v{X ztpJb7obsa{&G4Z%6neH;0b=TB+?>@U5RUN7q85X@gKDC1gEsn{n%HPhB!?9fEnLR9$OjmkYF;GUT18GM{t!3p~ytN zU5{>{1ahCS7?bixB+<$Xtn}G_Z|bAc;(82&OdT;mAUpUh$Nj6;@$(swDX_`oYL7Ks$cm z6EQvAIv-al>%V8t?X!T^;q4Bu!ndCU%XT`nI$z^ncJ<@BKZZCBUDZwF?Tqik+W!J3 z{OPj3Yir-Aq19@(b@hFFJRW2Ay-%)nJ>5TRTHq@D+n8&= zcSjGSd5b1?mFi3r@}?Z}<3T(`6M+Qznw#wlZ@*qroR;a&TQ>aOi5YiwwS_>o+gV&N zuh+GZ)_2+$xLcDauHP=3t6YFSJu3Phg*xs4Fi6S+(P$1zx zeR!~vm-nH=?#wI=t+ACPWKEPZA(T|qG7vwSngQQe|AZjR#=C3?AI7}Lm@A?ioogGG zI)C(b4D;q=v03gt=~Y&?{L}IIWj34y!l;~a3WFNWK!1irgtu7{sd&wk~n+xcQX z+wwoQNHrx@+d>*8s+wO2KcJrV&os!8u(dvGeSIP;gF3U!YsSlz^l$1-Hcc9Pr&ld8 zX%tzeU?(r^v-y{KvH4YSs)Kj_;=m`qEh(>Dn}F8Gc@g5eLT|D1{AdFt83G(YZ2_fG z;JhSPxA=M;nc+Nw!^XZY@D?$gw2M+nj_gmOsaXp$vq9luf3fKANX+j#V)5XBTZh4@*nY7j<95AT4?-g`)VzF8 zeQWSYVDd(-|0a}T{`T=4rTBx*Q+%}ysJ1XFmrAW2?~D7!1@6;EJpGzF(BWm(Nz z%D>jLg^Rgk153Ln#vm3WZ^<@z5koU1M>}OwJWJ#8UubfP2RXN1IPySf)8Of*2vKKa zE?nuD(5zK3v5g&81DTRGd6u-4$y?Og=yUmGj+!RBr}Jw`+w@z0@8UIvz~7!vy#&Dj zeE*lmK&92U&SB5SqFB-@KHyHH0t42(=ST41;|4&+KLDT|<`u&$J9M84XYYrL{kwa^ zV=wV;`1xcFm^kShaDmK{8bYd;SSc_>ukq`0 zJg+!}MCzm!Kd#NlS$TpBlbgGxc#h+t2azOC-WKc7}c*aDbc!QGwUsANX za^2;cs#aos3GoR!*12MG@{ur--rdy) zc;3#6&EJKypaS%+Kg1*^mdEBI#%>b^1`0@mf6UV8ESE z2#oRWpJGYWR1zUlH{N5KGq1lIME8)TbZ4%uq`b}Ec-8q(YyIv`0VZtmW?_~?@$tV& zpk(ZHa;PU&|JM*)I9u}lsfTT55aOp&g40T1vM|9R=*j7yMF|TRly}fjiU{zk5w0(! z!pv{-)9(bz!%;}XlU*%wfWaN;v;SKH}EA^VRQy{gc5Ss#Sj-( zPjp_Z>5^=4TDrBY^FLQh;@8biSbr(W=Ej_OqeAq%Bj59tg2{tBC5M6Ow^iG6Ypq}s z5H{)&1HD-BLl%4Q`8Wu1+Y|~?EKff%~mJa!?2JlM-nlyDB^D{9|L_Mh#=h>iA+ zk#GdMv|s+oKYx8_-JXrG!Ql;##PL)4-D?V?EJcbAO1d=a1VQma0^7lTkFevCx?NDt z^`-T>KQG$&(2^XGHzBdLKDb|Dr|-ljyMkqL^1YGT+gOPt(1CK_Jc1jN_p2Oj=J40} zc5wxYHTusg05U5pS9-b~jukI+0&pDPdYtZNpX1hZ;*#|B#l!QbU@wDy_sL2?;|3;9 zM_i;5iH7eev1M$6vu`EO$AKkp^XkPyL(vr0=%Rnkt1`L-RcJLmcScqWb<19b*I}(% zkZ%q~8?eD!t3=^g@PT-(&`nJ5niWl+yDzm*-2u!Kwr1 zdc~L-Q7uUrN7(b47qn;45(+UQ=4qa{;t3i1A5ly}k#)dHOfP=C4mxBi9dX$btyH~d zhf{t^CfjOke6vS5DQm1VsAP3AR8|V*+Vl^5{UPVuAWKAvVf_WpG=eUPaz$LJOQtJ% za7Mm+KmGO%tK{RBTqr+Ema(+t;&ym4CaIE%{WD9^>-*R2s>rd%*RKzv{<_o#Erv zj5~k3`{U%WzJBcCIA#sfnqHs5`{Vjn&hpNu(I;$pfnvjuef5_2bqSE937}CF+4~hQ zr~fTs_&EM1@U7E&zr8MB0hpm57QU!*kNSQob=X51JDNY?lJk_u^ZW|I=J7gPEK9Nz zm(4mf^RIETw5eS;4IHrbTN9=Au}+KNx$6ZuS^U-D&KfJ8`uMnl|2fXZo;;EM?@cAy z4&E=s&1gqExsd#LhNLTSX`ew3M8V-)?mV6=V))Li5B3{UAxRbHR@j>vndesvf&YCY zTX(WV*q`GQcdedrI{FniVm}LO*&2*JnV!Tpfar|4WQ9ZNI`tfzoH8e`t3vVo-f6t- zU*QBrWK$N3xgg%`v1K!1o5Vr~z0l)fxpbI~L*hn-S|3olqu+}KL%*s2O4J$=E)mR< z@Q;+si^3Jf04B_pF1u3Q)Pw-qhh!HOx&kRt67S5X)aA#_* zK7~Ifx~^x8o7@*c&F;CJy%rr45uIQAIbJVho)c76`@0kX=eoUlnxW_JnBH17Q@9~< zV*>|&F6ECGt-U+z^PR2c0N-~6tkad6PRNhDfd(=p^Fb5r#`ylT5}b-0^c_wx!6lhg zcE2~b&+{H}TuQG4q4jBbvw8}X7Ze&*X-Wd3zl2r3SSKpBl5vM3;6KV$;^(WFPf~+r z%NsW2(|zt;)J4#YmxIP;Oh2#>RX&@7+_mh!s;KLtfkM0yfCmmuAoydbY(v+1>aO9i?QWi8DC?^=; zXC{<(&T;_V&0q6L4#*|_Y_@dM6FwH)G&PCk&B^IW6J@{IGYm`16o^I3VL>rrk#DPG z76H^P@e-gr#_Qr~meH6WWSfFS`fwM}2hamNtpMl{b>@!k`3y3si!Fwz7tZOG8!Pvkb%-Tw%O@orsxR{x!bgdiz zYhPsDF}SNqs5@GSv@(HgsG3dS8j0WI^E<`ke%~n{Uo@|uiDW&H?~`CDZ`6>Pv8ejj zC|;8Vu*T7i40wZS<|Ky3F~T|?yOh}Bq`2ECk8YWw7cKG~vN+T}*G#t#BdvV4lUGwB z>*|ko4lUMIQEBvG@XpI`C8A2*l5d6q%rBzVgWDblPjY&G@`g1mYSF3dn}2F>2m^;X zI1!iY+3hlA3Thf!Mo&Ht=lRkhi|gg8n$mDs7ZZ8dLuAf^1C2!M@l4k^}%Zx zTk`cB24^?TI}`^Fxw%mtO*FED?TONwo;Z&*!}ELFOz80Uq~UDVkaiM=J&Q6N*Oq#kZ^?~3A<@(r#>p3^_Jd(f3BRP<# zou=bw0Ny4~tQ|PN+qkyYcjkXh^@?YWVWn3wgg{!`2xVu$D}#V)!VGUzq;6G?trUS) zOUg_w>q_QjP16one|yZu+dgO4}cmL~K&L(|#nR)h41&9z_+ zCD~(d!9w~FnAzGS2irRE>oRR5`|3Nf*244sqHKi_q(L^pp5zFCk0t*VVCVOfdzn2SDfeqj_%_@$Z{@pcP5xaqG~ z2QU=rsX=Xn@_;YB+*@hbTJ`ojCenjM&gMSlCbFrn3v|1oY|aATL0eH&cSP(r6t2`2 zjA)rdbZ$3Fw`A=&RUJ-kB}}t?j&}Q$u)UpC@}eQTVfg(Il43kS>&g2l21r6Ani-=k zqsH-4NwBQwke^6^c9f1R)C8`+v^17cchDg5dN9llauXLtq|}u+-bMKn;Q&~_ZMeIf z@KATi#B#gGqvnPXfq&g2)>;)gA}J&vuA5RYx*iw4uRP84PoeoGR3?Z!4g_!~a8+UK zD@$=S#ZLZz+;b{V9i3WeU8i@j$vBkor@mx`LT1guob2$1fQUs6Ds?wKJ(FHbN!Y*P zk$h>FOlH~Upuvqw$ejm9g+6CQ{WeD)-M22g%W0P+e|&)hMnYM)!{_mO49ALH*B5XZ zp6P3Q$b!gYZ}TCCJF+Js`IvF1=f%iYYT^*#`2)U1WpR&GS2aGhZ_m>ETT)?jbanjSH)8Dd zb^V{CLVeCmqs{gD zEh~0C%B_#n!zC*~^R)595g zb2&_p&N3JR!Sb9<%1z&ro8h7TC5EU}`kJjb+I+jc`Ah&oP1RPHR;z>c=M+Jtx!%|7 zGyu2-toXf=9O89-%HD2e{?{)yyCFPlYWz^z=-~va(XTq|fh)ssc={*^wqH4Kf+7x5 zZ(>iQlZT6sm-N*HJues8q6@X_?&wWl3PpulK*AUNPi%`U@1!RoW}ar#S+_nNX!*ch z%qMFPU&6ifRl&p@m8bEnF`2~#T4{4z z-eTyPDPspS428jsI(mx(-ojHYI9kYGwU&xzFfe6vV^6pTul@YR!nJG~DW+J>SgQDu z4qiu5b?Ycz3DROYXLF9VK?7#Sr6v%VCp??ZO@fO@3dur{lCatZy|>_hP_5^vrX!Jz zmN}k)UdNwuXy*~NYHF}cx>20@bLk`4v`^)49 zS!l24)(T9}LyS06{h^;1_e2goCB|Pxg$bJrppl2!7HT{SUd6xB=(5PPNPJI^VwaG1 zcQ$m-h(djjPB)KmRb!cE-mvLs&BP_BG7An5;9NvNHdG)Le*t$aVC@fVjtMT?^|NnC zu*i5%k6{nfQf~k$SqICExdU0Y;*XXGJz@!rJ_lF4$XSdg5eMZ^!0ixk=bMtqkGL}D zUoXY)S{3^yQH_HvIPz8HDbLr|uE6Enpiif>(Ps(_4`VY*gObFCmAbF%i+;^=mHvZT zGw229n@_e~(p7{|VP|Ep7zByvj(Z5xpEn*Llm#2?(wj!-BWt^N>bqYVJjkpA%nL>wvgDC${CB)BUUJYxnp{owz58%lTvq8jF;kHp%jF(^r> zZv?nHJXWg0iZiadryfv!SePU!Jj4&R5n94dQ0anO2@2pn7G8sg9Ai!i!?p1No+I08 zQ(#u!oTWPc2hy=Gg6s)907`S&jmFg_nd6A^e2FD{KYGj9aL8GL|4z>EGxdb=^$3hO zzm4V*uB!@;kPJ!`Em)^^T zjtHm0?WMrZd(J&l@3F`H8qc98+`Bte!^qLa*v~nr55@Wxj0Z-_98cr1S5KP1WAvjw zR%;qWQ=dx6W-Psg0k81j38AA0oQ;cxsmg1=vAdOnNg!(-mqi^j)%X_#TZZnmV5hjH z4ElYE{cs2coE+y0KY!jNGx}^6rW!9^K26!~kHFY2EvVXe~!Nc3#^>27`z%lsib&IK%BQtWr6atXeZ3I^zuMR~~g;vb^s24I0V@X#R!m%$1&2VCWCg9Jr zBasehOiY{g75^WzGZ|F4=}xep+_*RDSQLXZEg9*pD&U$cE`3mmuOi8iv?D^Jui1&TS}vVVQa0;0*pfs%kuo) zCh652=g@0=>M@Rz^}wncu`HOwZJoJhW#?7Lw#ZI6x*RM1+bOp2#Z` z*}!{vBzE?E-Ey&A5X5b<$v*b|Ni*m)LY0#zK4GugI=mb|{uzug#0$T@!@+QlcJG!n z{?6;sx$?9Ti1a@VX5=}yEG+lHV{gUlFl{I$E=CSsI5&=3*?8!+1bt9d>`Ge;rp)vh zP$nL)=RGl6*vVE zI8mqw@UohKiXHSXkK$yuw)vkjn<^Sf?ZC^0(K$4l{v}y1sm#c8qmff2qc*;AP0g+B z%vS5`Pv_6O^YfOSHI1$&w}Z?2BpN3byx_sXvybN%VLJZ zB9bYvo#N7v;KuZ!W7f*2xLKBS-bW0gFkjL3fhQ}V431PIst;|W$`4=nQklg{c;1Il$67-{+Ad_cUBE+!^U5NT~!Fc^` z6-8ko=R%XwZt?T9f*>tmV`Y7j_t{?maVh_9+#voODqHb%$LZME42I-OdWe(c7g-w* zrK64huUCb~*w}_e5Alj%AD-lmJw>xL-U^Gjsfwt+ z_mt|lqMUp zMg0nbiSp#h8rl}0t~l1_^Zx)^s(4EY9HJV5atIFpqBqKhkH5*2{INXgtSiZ{k?BWi2$>^Na?zF- zeEiRmw_1+bRhkwLYkJDGU39lzpOku|x#Q-S;u*v5xGU}PY=_1*7UtK*Gy$)Vo6R!d zp~ojNRo^B(;igoyX}46`L5#JC!2TaGce^MSj#omO;^7LC=yOZPAK((N2w9iqWo{ zAVk;~j#AS>S3|4RiV8~Ph4S$dxQs7M5rs8a%z@_bN|?gY|NggvVam{`xN{DGILD*l zn`l7nw+@Tr@_-~;A%IqT{~xpc3u))g;v6IrJ&q0WU^}AiOvxj?>*% zG-h~}L~E^`gnIoJvR0R&fGp$K&#YkTc74gl1Id$pmy}Ls65k!`9@^@79s=;wFvTJ$T6O5^3*e!P;0ij)E82n@5 z0N`LXzhx@HBKuv|YBocgP1V3AHbUMoT>2Gx*O=t0{0G%k-3{2NV7$fUtyed&O8Fu- zPu*E_YvdqTrqKZfA39qO7>>YL1Rn{;NlihXjX+%cfL$-%{}wc^RrahJ2sJepc@~6O zWdx}hox!$(_;aNz;nK)r)-eOAt}t?xU+??3Ri;N)1Bz&#ZR=amK_`F0D?*4J3MbU0 z#t2E(Cr0PM$X;9TwCQtV{HVr;Qg+5$_9re2_vkh%O!guvb*MZq;HntKISoLJlGh~H zk^@kZy~gvfy*cMxmY{}7wV*z$rA z&EuYTb4;NqR@q;~z)`&u>)Vnif!^84K`#zaud39E_~8+M;Q6=|qkb)ZiUoTjKvSs~ zv2VCZ2cOZYVz-bkRs^6Qv>8oZ@)jd-S*=Qou@7f^k`hdB0A5z zX)m6gF7C(kp3QY#1&UY&GQi80u0_n1ZT-?!qo<#)>D99g<+uOwD>Yn&T}}=6;QA-M z-v(`$SF%R_`n|3kk7D^A>M^%4s#8O9Jz3S3hxcbpPZiMCpd1VRXi@KbpNv^E-JxbaA@tP!5x1Bf)_k4&L+qBP^!BH(goJTkAgO)D6PcF!5j zR0+%WiPOq&%hvz18IKD^H@u?XCd`YYt+VLFAJr(9v)E%^UvOkWK17BJqhrG`FSC8Qe|F$ive~_O%ZnG8=^Fma^{#*{e9jz zHZt-VnpL}zo8LyKm68;e&c2f~dA^ZXK z)p}f!EE~_IjuXI+Ee3_gb*YFPku&zf3J-0QV_-m`TVL6=_~{CvhUUO;1VWuC!|h6K zszR{gmcB8y<2~gq_E^9QE3KHYf|mYisyNl*=dU}N{f&CFZ{MtnhW@6Wj!CGU$S!<$ zRe5R4$usw-nh}DMBDErY?50jEeY_hwX7Crp-pHlGmh=CwBQ|?WkC;m@%L_(>5~t8# z-)@gkBD$iKm7n;7NPxY;!td!|_PodEz0QGI@^remyZG>OIJ-SZiVzz7pqNHeI-nq7 zHjIdPi6=YXgE%#`b-t=|UtdDEfrpKc&&SBk#mdL-XJKaLb$4^~us1fB{`p=md80IC zgeXwLBI9{}J2LP~&DCu2P;3T4>(*-(BChYCme+dD5bT!3l}S={AnI#yXC1%9-fG3@ zL3AHdq`k$O!6(;lf&Z(u>DP4B!}47-NLp|6xZHQsBdtTl<=);4k9Dmd2Ag^GWw^E! zfjyu0nVkpuVB8lyuIPc}!5q6ocCikU*SloH9*97bw9UAqoZJUcb1U;hm-M@u z0Yu$Cy1JJU`|O|CVf0Q#@CASf@2DA#?AhMv^NE$58<~ zvF%rIGtX|k7pD+$Lbvz1`%qA2DS7M&Wj_C)%xbKVG)_>sn{KMtDrffT|BCoyX3G0C z*oVdCw)eSbLfw z^@*DztN1J75vvt!0cW%svCg;S9{(D zawI89Nf#9%n1u?kIWRiL9J!~GBINgM+-A>~q(5I^-xd$D!nfInz(gBPNJaz`1Pd>q z70iB0hK0UQmS)uuItxAkp3h-O5}~b!MN_41Kr*e|#yb>(8S=zR_@0(!J7ryD%bbu0 zd4B)vzb{*xGaq&hoh&}m+cw5V>LQfKl&ot?&UhACYQ*(j?l5Jyp;hrl0iJBe2jxrM ziyvoTr1iL88SOn)d4XX1fSN`$zl7Pt`F|-#Syc5Gohus7hr(mc9a#zV2h(Ee!;se! z-w7`^YHBnFO?KMXtDcoSFcQTckQ~dbI&2bN4SYGb!PgSgUnhr2hj2Y?dF^=R>n?P? zX)5m7T)pTh62O3Nafph{{t*l^lxOS%TOSix$^Spx_3c!pw3;`F>loS?0zxS*PBfRa zpA?z=rp&x{=YRCiV*>)(KQi#9SQwRtfUCzAi+c{c&(PeNsm-U9*6Cro9cRXpo1bJF zETUY2S}hvHLI80c=Y6b=fz<-3WeJg?T~WB2nfs zrx_&hU#yIc1$3czAoB{tB*4**Q(vsN^m6+GPIae4qv|jmG6_>f zGI%UIBv&(t)G;gDm;#yba&3*`>p(WFq1C18%i0C}Xe83)FM8(?B@hefjp&ubX+ryk zFQB^P=T90-vVn$D3?nt#D0tx{bte3GzWFE(Wl@Dw2zQN#>&qHw+W-6ip1jx&kaz)I z_`0|x>;Nn-eHd14!p4tPVSOm&sGH44kbDFes|$-9lS2H+i4;pG0NaXM($?_O11l!*MoNe%^J1gEGMy4r;WG?gmJSfy!RQD$zSbc81#i0phi| z7n2a3h5e%i$t1^Cv84CE`sYm(EhVfpy=yVlzEwWHsvhIM-L8YLqwKre9QT&`CBA99 z-v{0V1mdXSmi_?6+3Bew?~6dWEAGaO0mM$nXW#Idx#QL@CF#?4MZ9C-Ph!{#wuh{r z^uTLLN3(-X0sroMMH}T9?dtLm=i}+&Fd(?GSvF5FNH5H9tocp0%q*{^l*+_x*$c6>ttk;8lD1q6K#PD;>R^=tm@e~;r5(Y52J|VXL>yB z-ydB_q5z5?Jl-PEWUd%mw3MLpws0dG!u7`-A43o{;M12e+Skpan7 z@nE{aqY%t#$@dqzAgBnAUDlrk@}x_yHJWOTEYgh%v54^}R;a&JLb2&3xwKjl)q}9M zmH$t%Gc{kKpo>ai97ZAyO29P39=-uiV%vJ)bOmSQ&G-1n~y#suNXf9I_>O^28**)d8B{%Anv%tcfxW%cR^6Wc5-rv}RKYdL> z>zi=!ce;5f&Rsto#X%3f;RB*Wy|VvVI-yQ^2_!9u+q{V_9-PqqQH<;0f5lMI%?Z@RLK8b<0u9XJ44-eTc^yZ*x-E?dK+170I#)hIg;)-$d6T z+e6e7;co&e=ACP8<))6AdKZh+-mndixWf%81ji9r?~0U~565qL3bz;#n)#=hfvXIz zP38bFx09>SEtg56`R=C?EA1y8sZqaaBo#F8Ga5p39;X88|1Cz9J#84uPUI;_Vq##F zVnsfKFb26```-O*db4@7LcAI!ezicu6LsBzWwVAen-jbXIfi8uBB%moE(A}sC^Q9> z3t0nNd&V?X2Q#G6vg4-SrEz0bH_ozfG?HK9&^co!D~R&c@j1-8%lttG-l~es4`dWX?&bJKZpiJ zP)mUr#wcD|N}3$S5F00z@(Pm&#!{+lgKRGAu`PC8q2)F3%n-;^l57ZaLt{XmL!n~9 zA7wPn(qC47hJ&mlZvZo$_*!*q;S0lb1C&Y$X;s1tReqw3fs>CTo!v$zh~&K-Z^c~m z)A>A0@6D$URoH9H$fo;Pri*HgIq$~4yq5rYHgX>qASUYfg(hC;o@C5aX)*Nm7?^T!nXxdwKq<*x0+agQwZM zE3o$nolTC%-SYWMkB2$DIQB9@sB>3uC<(Cvm+Wlj*FcF1!0Ce0(Vb@5Jy<-`E31hk zJGPBc(fP6y`H{9V`A*EWFkMYF1O0Ird5)LU{>x~;5O3S$bC<(n&yGpOUF&seWeZ*$ z|2o{GV0!wr^k3cjK#u}3bdmkADgeEd}E0p(>6 zz|L^Yv4#BlLwAfeD@#Wco8R@{&EmeIjl8^k``|0^hrZhq%b!3$`87b?W6$|a%18f4 z9#?iIt|prEL__oiH+uBUI%_og!ceQx|uX*@$ONN>=UI~lKdr`*p>B?6eSH39Q z%d6&lkYOrN1&XWkrWIN@?MasXOX1fuweIyok;+qMZ#q0;9;XtU>g|~*f8O!qhx9hK zHPJyykjkNO`BRHQUoD*vk|jLXJr;You^kB|E(NH5ctCSpD7i;`J6OoFL;@wQ66^&@Uo1(DpdoRECI33? zs3GqhZ{hTw9?b6K?C$K4<5S4S>_l^SF4)eG#7Kq z+(dBKlCXJAGE*^nxy9IaeD~%Xzf98^v)YeB9AH66gnW=4^%IfQDZ~iW2+ShaD`QoZ zrnqLHvI+eb`Lm3TTcl}Np@@NX1zpRQFAOR+9z5}IJ)T>|Vf5{SL^#5L`o`vJA=`{{ zZt*#)&z_E*-JQCYFkVODkdch&yF9FL5fVnOPGRw0Wp<8EYzriF1qm1~2dt<#ayu@y zh~~|?*)i~8|J38028)?32Rjwr1K0~MfE4|IR8OxiOk)L? z$^n-w7i^1F8lNSN4o!T++y@qOcdV8_1sUdIEqG@*L&!z*647s{R4bviNYhOPb%t`N zzXI1Ez4Uz#H3dr*(~HPY=4u#FQ{@3o)Vb*Sw*X)g-GzH? zm1_QRuxZ9dEO9uEH1a`)P(3VxVSx@=`0lz~e{Py~k2x@(3d%mkmKWb+QHV1j5*sl! z9L|cwGKp}PIiCB*EBwpXZ>MZ7SiT~u!CSlpv8auYr#t_0j$*jIaZmAUqCQM{Q!F}Q zm)_rRI$L`^PK?pr;AxlYl;8+0e1 zIm{9F*lD9ZrH2tPI}~6(j6@*dv;Xhkg$koEV*7i0(L(TsA{a?o<=ksY1j33$_7yH* zutS5R#4Pm*Z|nuFj#gN->O2UY$ur_eyFlH{#ES^nl) z&YhTN3y)~8q_9pj`Bk`?_yT!eu{hrEkG_xnTPVpSn$l}HUTaF4?18%zBkHKm^bQZ>|69Zg19 z!v`$f4tj)40rz7?OgD ztm@f+;Wmm8A3-BWV}o|4a6~Lr8j}{xg}Q51A9K<{k3R3}waFq?+|6L_V10DdH>n`A zWQtZ2hJCqFeNA!&f&4*-snl&U>dy2h(HYO9kOPEntn(R1a!E_cF57O+ZL`$#Af(WXt~m=NQ3tms)8rdUbrU5D{35OQD>gA6Rm&F_wfy zlFS-V9MHZ%Kb>m>?Frv0Fl8T!b=c1Q@qt`eZ%OWX@#^bwL7bq765#N)ZJYvR*&qd) z`C&8O*+4VlW}pvyX&J}+R)oPwlS#m^7=^h_*kYj07bTp5_Pjl@E6X}frv`N~Fs_8~ zXpu9_aK^?%tBYt7UuaV#C02X0WdNsgH9v>eV7LrT< z$a{G=Efm41QImi}k^7&OPXQDw0qshEAp8eV094oOh+Lx3vvi9m8$wjj_Zid$ZjJ2b_rpAq-}|4z@cg01fW2 z6&z9K{5vlzQ;*N|A3i72|F7?t^Q&Ncr}{Pe&UfL6DR ztO5FudeEo+S6K^bA{lA?ZY}rZ?W?xhKqEf8zv~=MgG=wMb*i5(UV#D_4mE)&XBqz& zvAvNV5zdX2m3(O62?E*6_&ElL+PB)YiSKRKV@vn;%hyJ$h-w$t`@s68fT(@-&X>>U z6Nk0CzZOsy)W1Q1@$G?-e{hC!tqY!cIdaASbvU0CMiHJw(3hT4)Yb87p>g+$fjf)# z$)2UD@q6ky=jL)u#qUYV_rufN&rUdd6px4oYUIyJ_NysW)8r&Bmyf~sY+ic$!W`1K zfVcfN*^pxql7EWVVR6f2L>wHXILa~Re64SdmfWBVtq(7De|2~ z9qDWkolOi~pozCQVf+jILG{@&gD>D&)QnEkTTQj$rL(}Lm1U%eT zsgunt@8ko<8_)bGATfUctzDFG4Vt>r)Sw! z1$;b8m+9E(m8$F5cg;J=9L#)L)kwIrSJdQ$kJpGq`z7evXuDl{D*kH0pAbQp%W+Tg zJ~@4=GWGEqeGKIl67I8rx>@le(xpp4GlAA&$TBCj@u2Zl-*Y8B zK99EzR^xY-H7`+@$#1;W{cTg~JEOGyz2cTcIb~D4+(dajxmrItO|~$M`3D?kM#?+^ zBt&Tj(+)Xz5mHhnsu0hBYaj+BbGLEJNzSwtA&!T=j;ECNP}~Tac}CnQ{s-vz55i+o zn8IDP(*&(-X;PBiSbpC2zTXGYy`$=((7v`RmI27?pd*Y(S$m>}%t{-|Fs8Wv>I5|B zSW<$gwWVHWxgmc5YSy`AL;?SLe+XfMYAIXS?h)4#$Dnfm>r=LLr)_bt=ebUo3B(*} z*G@Iq&+Av*M|J>N-cEo6ao+?1h!j=@#3NCvD5|URlu4E=GRD@wt!Bk(YYv#IH)~{A zUQC8~<*H&*(fB4%?;qeXD#Tm=z(eN*!q%X%h1n`z1QL9{ldXB^to9>(=(81Ojjqo( z6eQG=XtzPgBGI+alT_g0WZN)?bhAn%!Ran)2MLP_(lZC81#v`NxR5XeJO`#FbF0=R zhNw7{t)TJT2$BK6;zhLuZ3R>E!Er9cl_jwG;!f+%8xGt{Fn=yH<4fwtj)UxT@W)bu zB}0%ZELs>r=2kfvBg=u84$#Yx3^{gPbh!i1;oL%?Vd24-k#{uD0!lVPe^UK!%k76r zE^oLrP^6}cU0dVlQGV8;7tKWs|FO*n8y<*Dc?^cE(3|YG$poY9+Pq~TO*X{Dvtj=C zI(yqBnss^vSGufI7a8>bVd)yfDto+c?$+d*Y}>YNYqD+EWZO-+8BLJ;>9Nd^5FI|B-^gLv1YmA)e#sB9%d18>x$^JCJ3 z+^Qsb2LMPU1;~tWUpWa8&+6p2{O$?wQ;p>LUCJS1d;b@fqo7^B}`SV`9H&!K$^O#8Yw)oSHrCV`g=EW|+SQhNQa zSD!d1W*f2l5WckL&#R-f%lD<44V}*C!4qU5uRqi_*)vapgmp>-%BEbr}Na*)Jpm5t#38YF`KV;<=n1& zvLO3KMNotqt5Sw_E=oenFkO4&zsXrW4DK=!a(guu$Bzp5KC$Jbq`2t|DbC~w{Z1Ig zLI$QilSL#{`q!@^pC|d28SsCo&$`ZDGtP3hSbY}sub6m?OezdhD5Ke@k#WzFV@cFyBy5?KnG2m@KS$pAOwD91$)Idj)7urJSbhUyNV8Q76TL{uQi{ zF2(#6oAPY=>IL_=qF<*-TVppG#3Zo>@`ncE z89rpG9Y2fV8_iY_i)*flK*y856EFV)sz(z^3HjU2Sd#4*ANvz&@T7{KV3dNf(Slv8 zyG4t@S9z!#V`|g)C8egwp6*hYC2+hI*DJxe$jy=`-gDb+Q!ei7OjGxgFc-`RhKhm{ z+CTxCMxmtjilwscDRg(z9n1>kPNQ>9*OI27{@o+Q(A?BBsyC%uD_Wc6H)fsCXC`L5g1CCKi4(O#fobLFE)_BuiaFt zs_{$0y_WRqY#5ekVJK#b&Zpw@n889PfJUwJdjG`p`>y~es(!Y#nt=9~p&HHZ&w%Gw zPrr-VMm`YAc`?(9wrLV|>9u~y#CqbiXD&2d%)VUr-s@T??7wCtXD%+)R13b(paVY?Pf z1p+I^y=7E-Zq$S{Geo+BY?PY=O@urh*z^RzF$YRLWP&x;W@TkP&Q6fMGAYHInc!45m{HiR(4pM!3{V#Vib+kaJ(mcy&peDMm}`YgHDSg}*iN(M%xePY za@8-y_A6O*xO?Jr^_JPZ128eCA+mA=QuzoF`1EEE*%dM2^~`i$h&&J75!whFZo>|8 zmTkw3TyPs$Q7{px%6E1@eJAg^dNRHp4FVG)+#O; zOV)dA^sf>uTpnbmxpyu$a^^qJbHl=B?S`@eSskumPyZb6SxhP%29aT&q8NyrJ4BSB(n{i9CL5UrtSzcrHJDA<} zSqp(vkS=rrp!Upb8+M0*k(7gBLgU&NpnCd{D-^3Ai(Z}8=Z7@|NSC*$ zZP#&i|3v9XLP~Iy^`IIKkA~wq^?kiskPsvP5b7iA< zSs(A4yxUz_VKDS|F#;D=gdt*rY6rl^A@BjcIJoMp)r%|fY#O-#P@K*YIe_a}l$>4I zI1)^x08&;Lk@ySZfV>!$`j5%zhe;5M0|I<5;v5<2r?t_AnQsOL^n;X{|D^UQN}W~r zYm%dtJ<^{G_-O-wj1cifJP;WyD^hx1inWsD8va$2R$KP@W#>w9sfZISTMGWiWdK7Q zAZ-pYB@_()2#wEvuVH3wg@W4=n*_HR^iqba-Oq>eBf<$!)uOtNm^~R{2LRCp#dQU1 z6;U$qC-&b9eK0668wpB6%+UfI^Tp*`P@^!40ytMXAc&@*fS}^MB9t{9vfz;)XY>h) z$Pr*ZmUCoo2&?O*KLxQjcMy4dt&!*xY)|3zhk(ne)?a~AJi~;PW}ffj*P6ym9qU)9j-x(M3ke#Ayr6d~5UW4x3;pnt4(TFfwjb$S!YWLl~_Ojx)eh!a;P z+U|%HVgriU_cbN0x$8kZIm58hAi($e;xNrIpwaJfV?TmOtES_OWZ@LZ-{v)Q$R+sZ zwcm+MxAkHE{P+Ast2?2ExadfU+19*w&anFJ+%paK@)=>s_ItVCM;EM!t>Oyq`U$Y2 zJ;iva2j1R~|5qwQN>e(b>#bT$6kt!{K#{Lhau z-8ShER*=rN{Fy2~Cga~)eGWBA@#@p8>NkIne%G(iZ`XKlNS|!=d$ieKg*|4O+u6x8 zbtq8I%=?ke?{T$#r@uMw;(1gz$LmpRUQZ0QgE;^S#e=hfUeVLXG}$bxtpV5dX*P)jCW4r<;fCL)B)Z?{{tu z?w_uV&E9LzB=!inphY)rd8<}5N}Tf7LVgZPPEYHa-WPw)-abOspnt$q)$8>e&v?J2 zTD`$6*@%e&cAsK;Hz9qw2pEX?$tIcXg@WN>!|1<$nPLWo$QnE1#=u`E%@CxW^OIK8ih7br%#R-VtCh&Gb1;{^&Zj0rHv}r)DQM**<#V$y<>l% zm(PCcjGa?!vW?*=pXUDO@Nx}o?K-bm9G6aF&j*NZtOC-_1m%Y5glq_g%WTD_o}=I0|3 z@}-_SgEc_v&&yjE`Rpo&gb;n(!>Vc}z#>DP#r6b9pLQ$3!$#c5Tp-)(#K@PCHdw;T zeNH3~qw6hKeCA4LO5OUr;lpXV(^~3b>&YdkHeMN~+A3(YN@!02h_keLHKN=Eu<@Z? z+RHjvGID0&dM-T-k9T3OI@-H-Zc9<-p=NEk&~z(5JU;D6l}N9wyx^TyS3$>>U|DsuvpB^~I+oIcxF|RnNA~I;S%d=8m64X8sB11>!DB9yqHn)wwg)9jB zyHH>4u-REpC_QsCUX>QmkgiL}h;b`;mEWj95CjtB1XCLYxce5B)H$-|8_~D4lm_Fl zk`|w$(t~)c<++UK?UvG~Z@k#euy#xFn+8`&HQKo-^QvXYRE-(5J2kKy+jqepg6U6J zq&=eYmUxpRe*%)wW?gMqDM*MB!UN@~MT8qAXZgTjN~U(=Qk95j^7fKXP&hl(ijTFY zXPt`Ez*c>>pi7aY@xudk6aZ*LCZ$p~-n2=vI~XK!O|NKJ%Tky9#CIC{{9m#>Pyjf@ zmyF5O_|^XbOT1d~RMzoGaoar)3u0i869l+#EL91VZ&|W5?7U4N`)wAkF8F@Y0u6G( z+&s6nI6TKL5j~=?sq)>f_P_5VySL-5et%&bPcJrK>2WJaB*k2VkxsUxB|*dYyO?+G}(n+ ztKumdAL_U#(>t{TJcmVoi5w#s8fe-pk>r6e;V=jYY?}lg*+DU`e`l6&_9u#SliKtz z;}@vdiH7JShsp6B_3sOwrroikRZH!8V6IkI#n0GodJJC?Xw9i)zLMBwxt91sLK>J2 z=Gz{J(=FdB@- zxeEVNWGXIKb?l+3M7GD;pVKC>81@7u*Q6urlG2?pi%;zM=F>rSfJak8)=W`3m9wfa zG!$ZfL|2w0Iyg86ChAWy{B8AIdtg0?r9;e@LOaLjgQ5H-;b;7+4F0zV`%IZS&7-eD z*q-hY#CG3R?%#bEvNQgJPC$D)=pH{cJ8y1InhocprzFNpoplQ>$^B0? z_mh>Ts`T6Ry`PKHggSNK_ZCLqFKIuWDcCx{A7XIlRCIkVoM7bzHQcN&jM{#`hlpvF zb=22??;l(6yfjDqW4M3Lm&Vl<$rd{o;%=ERdA?xhqt!azcNP!n<)L)(G_LqKo>pKx zwk3-xlu&3lJZ{%eW~q^`6kg_eDb&RN#S`;x<=vO-Fj|2{+-$6(97zSwN1M-%%BQwq z#gE9ctveG)Gx}Wqrs974_@|7wor&NjanzK z`c4aJzq4(#T$C`9)mheXhSJfJH^8yCif7kA2_MG zR^Jeg7IagsoMXG@xBE1g%N1}NyUgt?;^F6c7r(~K^x!rsdDT`t8-I>Bowfd|l|0Nv z)LAqj3_&7vpvJHt!NhvsI>4RQ;`>u{O00vJ7jr3p)ch5;;<|GFGB0QH$vb5PtCRbU z=-~IH4_SSfqB}`Ov1PS?D;(5=mglS}|gaPugMTve9Ld2Vc z;fF$5WWyW9nXEoahcA)6mjhW!xDb#l?XQv0zmpV7l;OxazCJCQTx` zN1AkSyE^L>OnbYj2Y6lz5_l9(1g{`S(jq4r>LAQ@6&&R+1TsZ1>v`lUvfaDo1qo@n z%3?}#?P)Fu6wYNNc4w6F7}}xzU_3yRBODnx2>b|yCj^^8W(-j%GC0<>@F7$)qP5Q~ z={~|Cr9ABAQPOfT@XP{S6cKFYS*R}NC}(+AYYIFWq6!QF%o2mkxnboOuT{kxC@D5% z_A+tEa1t1CR>;(R5@tyN8FazW^~`}JE=9uN%=+saTPov|n&chY{gdcUqzibDI*+h= zL*o~SoIvK`F5<%!ii?FffV>btaay^jb*4wP zdzzY>1)Z#dJnh%;o#f++K$}{wjd;N#5>&xs;+N>aWC%z}ic(P&8P)TY$VT5HH5q4p zgQd+ek;j*BeXJo>;G;gPxQ6RWGT}ix`f89Q10g8IE%gMkoggS;95%UpUL+8?>HRvd zoc}c<(1{-Gko{_;exB8u8fTzR`_!)80(8Rh$%i`1cE|#4s3B^2;#(2~G#=uL@Ibo3 zTvJMM5!_n{Pu8`vr2nwgHTXEGs-(p@6>?Wj9G$#+%R&qcQUVeIj*@bM1ib&DUKb?l z$fc~U<(wESs`LFR*cOJN1rv9LY)Qe@Mn{<>=(RwJ&{)@5$Fa{6jV>}P&6Uf+eXh5V?w*?t28&%A)L1_4?C%y`$jMpNvOa zGQ}9VHwl(wA;FfemxJnkSR!u(hS6lb0I&9&=Y`RJxdewz!}oq$x}~j}^?*UF&34~~ zjJ5WMvi*o=wv@SJ6^15eUx%B!52G&b6uu^Z2j|Q9!NCx4v+l zJn5K@vGQrzNWj=$nUF6@yI#98yj3Wg;uyg}#BdV_r3gdCLWsoPcRQmYF*h7b22=Ls zZ#>AG&m$pAP;BGdWDbVD7#Sl)_K<9c8=*MS#}y74RZ0FMgAfvRl51%Tp|tp5yZnP` zQfI0CXv7p*iL<6&4#iDnXrtP(xt$7gfAvigwuDwpBJN!jMPnh0H2}=+2q^n?Nbz5Z zNp=d{f<=Nx(H_#>_t4)cP&uGt8~6VZcC64}5&GDV0#SuVZOeM%(@&Y@d} zfme1cunEreR-&Ey004AvAgVD~lq`WtWDwb;uBe990N9pcjZCrySZqnz=5X3@e%YR} zIRXWuyR;10&A3RvzlYQIHP|RiqCWg~i)rRcrU0DB@GCHOUaz z{#H`kJs3zOW3kxjG3$Mm*Jq9EJO`Oxliu#wNa20-8<4*@9y z1|Ub0Mq@UDh^ShSgk$q+FGB~o&(A7=+`^_L!Oh%8nwO_cf4-=b#LhR~3I9-0+2q|`PtGz(fpKYi+hen_{qUK8McI^&BRLG8uW?f9CdLpG(b?gK|rN8Ro6du0Sq2>i#q{(oIO%IlzTN5lGuZpf`9n`Rvd1xNZ6=$j5?hMP=s_&QqTn8$6Eh`trQszl89#zKRkPe0*3BBS zLs5Ie_?fO*+4?U9sg)*6d$9W{E{5kqhmSHZ)ke8Wsfr#a0MFM)hP<$0F&CSM>3D0L zy$lG6$-U;x;ZCH-$ovG~j)KJv#En;8{7MJ^p!iTOR1#4vG@+NJHJa82HZCj~$547M z-$|yYfJC!sRET*9gTcTAj%&pI^7%>8ahQxiPj<@D@JMj^*y~iTNj?#p?PQjcEXqiv z?MO>JNcLV{2m!S$`~H4Dg~$P#F;R4{-|+VS1l|Zx@H`+aGmQjn6I;sGq?e(NP+uu7 zn>BN>HsqN0pgaQZrc z(qaf>NQ81^LMreFcWu8;aiT>$!0BH84suF@3JKMGmJMq(sUcCebJwaML4YR*lh8Au$7rpNO3*U&t1fR(X2KEH zIz*TqfEppKmV9r#;-6DynzVc0WEC;?LtKg6mesi3qgj8AJv93mf6?_lbQU}|7z|~f zJ~2m`n57?zZj^hN?3g<3W_V6T$@hguF@H0wnq#chn9EZ;xms4Xcs&$`=b#o`6Bi25 zk0E`Go&%T4s!Wm`aw2~}twOt>AaSv}mi|4Kvo|vR4aKeo?n{?(w`b#K_Cqj2EnE~L z82e_~kuAA%IG|P?kJKqYl9G&uW#I_B*+|iG?Uyf-B=!>&9|H5O4D&K+KDTHOH;ham zNrrOSQI+s{zSe()OLEC~+Jgx)qM;9oP6;i-FBSj+ucriQ(K=rc8VOawyN2WgGUxxR zg%KY9{6eKxCWji80gp5V{OPgEca}z|)8|dMU_dMW_Fn)8Rg7LW-MX_zpoZ2RT zdrW}D+~ptV)(CFMB7u{UiO%`?>48wOV1IG;vqWgkG^dSF%1o;3&s{mwFr;WU{y_Cb zvL$>C3shzZoaF}#cKsSkjo_^wY_~icr>c;fyV`dCgyij!*Y;-eJfBznY820rYyVq0 zQ;!!X>C@9$E{|?}MBj}#l3y=M*O0{yJ>&1sfGe^6-o_Kd?nvuTezNUzThxT@2#^a! z|NZfhH&MOw;o$~(iEinF|K*7dS>4~y_4(ub@MYv}+eg5)R$V`XmNvB%(C=>;7HjsI zJZHwr<(k({XoBw~J}tIR6OJRZ-8lbbYT8rtgo;4evWy?;qvNN?jyPk=o6=bFgdNjp zpPgm~<(HM!zc#y3=seRdrj#kxkzOXVnKVR3-4};ciaB1t)c=60IpkBekI$fV=!)O2 zD>tgQCT>$BJjZk@2p#Tq9Orz$Mme^Vd{nZ&@s*-+wGYl26_^E0q{YaE`EhE&1l&D! zzwbN)^SF~dakWgo_s?N(P_+ zA@Q6LLM4&3hq2)xO9%2tAMMD@(2b?8C)B^(Tq*u7HT|kb%YumyRX}CyjthQa2N0Dj zt|cjyZTlBQD&h|FW}6E}>1+8Ks7my;I8upj8bz5rEDS-Fle+3>#{3+>JO|PU_)g&r zcGf!l#@gD?-+Uc)#`J-GwZ7X;2m5KBo24 zf0xa0ys1&!zQN*Ix!kk6Jiw#*<|<8!>#|zEn;^w>i8Qa!TQlFJ|=`SWLnf2{g_N;4f%!1YmcGtv7+2uKI|?si?Y5YhdRx>oZo&a~=eB(>L>suLZ+ytKPeto1Y<7V0e;z7Suu9nzFJ!@|$7O($(eHZHIEvMwK~ihU?<3 zyTzfr1!-6kK>=FZUQC@i*mNWL7955Ge<%X`D{J40y^TQPFRGGgo%Jtg6 zIll3~Y*eOA8GZxRQKRR!b)oN)s8Lk`PnjcQtw-+HDTwEw+>Dm@Zj7#Q4`qeYlsJn+ z)D@&5J|hxn!AjG8m4A_-`~3L>3N=gvA?n zQk+2(bcL8WRal0I9$|b4G!(d&ehlr=#Ks)>ppbt~;jXrwzlArN<}ugYcOBrNimt*b z{iW$E0#`RsZwoZkN1z#~M>KJE$ms!GLgk;%SwO_@o3ch0q~AZG0(Ni_W?#jTq~pL- zIFZ~}&~%N57s5ZWPbnWImb>k0H|Skfz^r7hc>(`UZ^U5BMzJgjn_Ld{` z^|C7W)m{J4C@u5S1RE4^TuL-6}U58$;GJI)rGlq>2)`WT)DOReBCRR zCTFP0=#QRp`}8`PVY}O;Guki3&Gpkok^f6Z9M?cEvPw(Pc1&SVdjTH2HX^PBP9m zFo7~+4<{3JS?90F=Cn2M!=rRS&Ck<`1-?s)t-;x@N|#rQdo`g%tqa3nn@=f1pt*hC z8}KiWhOsCu%K(LW@7)zHlPS$=iy9(|EHCB-oDI)|-}_ZLV|fWYV)BB!u-a~(0R{XS z1U(ljOr2}QPgXbP4I2K6PtzwQX?PttaFPh5fOLFr=}e|OT(Ec<08~n-%04v`Ea5VB zo^W}tI`hI$-j3>p2CZUN(Fjnb9ZS`d4xolXWu~4dcf*7ZF1GRZm9+rb1xB_r`Nt^@ z07)ZU1V3oXaRwB|)Ib?33ie6s2}v7Y+Jab#ymjAW`DNYrj44A-%qc7X2ecp+Vj1sV zreAT5jLE{@lijsT$vDtvuUPpaOh=u{oSQ-Dm(uh}QO$SSgm{j`QC#GU`SBhNjL~t! z$34To^kH#awLy+Y^(wu&&rum{y3^z^L1D-+h1v2YrJwe5pbuo7|85MvYDGq{^ z9Dq_qye;Vp1}RfJL_JX27_7(JPVM=GvDR$w%UyDSJVl6>;`<=)xY`#?Aa>wuBnc#k z-s+(h=f)<@2svNn_YNck^8BU)^x3D#?MV2(4EFCErbQ1%Tm;H4?vTd9~zqB0~{&s}NmqRl(G7J6rz2A(C zko)&{8@U#~b|KI7oLtQ8u`gv0ggmv+e`=%DdxiAdbbN5mzgk?<{5GA!=|op_6tbP3 zxl(twIh{|Dv#2&!!}G!5l#8tmcXi8bSTt7C9>MIiTub*?<3q+rYJxpjKPIccl^c#% z67mkZt8(C6FRpJ5dzQbI@ZAv`al2comO?hT5|83|=&wE;wMA3fo)V5K&?O^~Ck%|- z$Hx%`FB6m?09Y7pa;h+$rI3Qd>QJll(t4j_}!{!f=1Phzg!UL2s(1gXY zAu3wH#%X?Hn2xKm{7Y1%LZa9;eDms$w7MKkhe8Q%prTyrasDM5EmlTPPIi9GbXz86vm^hEyHO=`Th|9y&^4(tL{SrK!gJiG8n-%Kcq=Mw$l? z{-lFam7nn+?ouce1Onm#5n?f==(v2OQ0LX<8O|nkG81_s6X0M?C34bopsr;S7Z(Zf z51AhS0!|7N^++Qu`0T=Zk~88X#s6h`$u13|;c0L})1Tk8AtJ?40KIX!)OJTsXH`7JLDo6YKN^8s-lz zbfgFjAoMFB$s|Y|cSM~}?hhaF0QJLegt=IPG1BT9#5c$0FK zCmWcPv&Itq#dSGpX^9lwY1QQx;3JqFRyY6}u}aTk=ELi#H_&VC5UWc6RahlU9Y{Y>E!m|?is*NIM~p9Q ziRTb%5)4h?Vf16d#_P~~4tyevJ%|mS`%Cp2PZ-;P*-=bsdkBwI4UYdf{YzT)+mssJhXFt()oyV*yJxic{_jKmGYrykNV~7 zKCyD%!ln~6xdeF6GC)=oxiFIbMEJabwc?Invo(6N{nIH&INQ8oQ!GgJdz0!OsW3}K0?c1 zD5!Y0I0*E75gZr+jAYrlUFzb5``d&)P&?`^yT3QHQvt~$L}be?f8n@qfhdaAfwoa? zSaLjP&@4iz^WDNQrf=+vthPyr{$H8&Ur1l7D!?hls#{ILd`iK0Z1v?xBOf72MIZo( z1PYK9>U<98@tb@Kp1|x>fHkEsHtCLVdz#r~c)nvzwABUq=E)*Hh{RG<)Oork8!VSj}7?h+bTzQ<2#@-0B zjLH6IbC`O&c@m&Uw+?{khm#2h6GN$5Az-8eMp00)p(&70=#f`VeL%t?6l~GLI5Q@U zFz%roFdED$j5<-823-~c)jF>5*QS^M^9686y?KGp5R_r|zei@$V*D0xcQFRi!akq2 zi!wN;vR~rcKw^0Y>zHSarcleR5SHUpAt|uy92oGtI!bi;IW?Fduri!s$oSo}Uy8frFja)v0a?B3Z>Z+(SQGp{~VZDt85km*%c)(y}8`kH7|=;35;DiLG)_RC(V_P zZ#JN`{sQuaI%8*t&#S|}CrIkMZydEHTBQBa@mA|{Y_or{A=Ta0bhDic@`5YYpW1G& z2N$;9c^G5#U+CSy{wSjo;5b9oh(gDqMe$ za}n`(ROAT!_V?5d@VoIc-V+5IQSmUoz8i|;Pz?VbK{~vFparIbCx3p(5Ey_rI~neswZ^QM2V9%pJcIkr;q!WQ$?w-zK?w}ZV$0s? z{TAb86<`s`KZEggjq33-_G!Y!#errNUSUsp7~&{hpNswl#_@$VXqpeC`=F; zHe4SBO@C0C>z_8Lt1qS#3^Q~BzDMf=k? zXSVGh)+HtGJuTBj8d(ssEME|c<*h5RzqLcP-iux}E}iWUuwv3X+Ymg)x~?a3#S0&O%(w5yOQiD+6;*rGNC2t(sP=#f>_{ zC=t2ZBf!d@<(^yt$zvOh|4c}+%_f(qh)z_82RauCWRO88Q^QCfQ6pDq;KLMIQ^lt^pTKA>ShVC(3eKeA8ul?W&VHWWT~+}kr>;Yf_ngwcpa<5ITehATnm%TA-H!KAQ;5Q?*aPg;9(?P zs;@UROp0Nf=Qt2wsi8$J@X_ z0t_Gf*^NnBILzF3TcqZu#jN<{#Nb{N3=OwN*0mAGZQ3P-GhW!EgE!G%IZ1G81?g+2_F*qFh0g3cWY+Pg#7DH-0&`Zw zd7XL_2c~?MRlr)Gq z)!S*-w-jmhS#-|6aVfobkKHP!F?J$|*ZHcq{O&-158KvDLn+6L@g=z#jqP)`cHk9n z>oXZV+t%*VP5_}T1sLFCyFZkj=J<)GwkXlT#j>lw)a|*KUGp}aHiR81gU;urOy_wa z@bY2*d63C{U8IGc>YkVTP;)5&t~n1ArTwj0cto{x)Y|Tsr7PPePwHk|gS*Nk!yjs_ zu0#fH!`TrgHkZwGdlp?cUY-H8De-{!HtzF|-z=wH^cd0J6_)M1KRpIYHn8mL8k9%p z)i0u6<0kl)lIi{@R3v|fvIE4pz({xSAh~;ad%D&6R;~gnLobI- z#2~RCD*V4v;S%TLi}b+Txou-$jK|Oa*Pb%yDDh+DVH!%Z4h682>(e(U$QKKaPyv8z zgfe9@SyKZEaP_LdIB)Il1zr2`Z~UsZziFuSC8CL?q4xs4sE9>2EsanhqlMwaJQC~J zU*J{vY;tUfV~c78hx%$?e0+xbaIL(cK8eMoZG-8Jg4_eCF<#sxQ5P`h3J}@Lif9BD ztZ;9)XQVE_Ars28V(6lRN%>piBzIBf8Q#T8h(n7fgAbsuFa0KM*zisHclS>f&gXt( zB%Y-H8PwBtN0){RK=dDxt;`*ghq4Pi%y2|_f-*TDa|Eg2Y`L$Qr9~*Vo*WA8IHh$u zt83Kf=E22hNkP}6z$io+|0 zzDapUvqSof%Pd+R-CwOakphrK%2m|O=g?!sU=mpsp@U^v%Ka?ob1`r-?k@NwlX93XT^Q*^@I$SQ`;^gf~Hmw`7${Gm)H1t=n=Uelou>To(JSsw7F zQ3Iu{q-y;B(%I;L>fOKB_=m-x0yVl{{6O~Bh<4HbYcnk<)vNz;JNn+OvGv5SR-YIg zOEgWuA`}3cU$y-aZw97}pM!B7w)HdM!RYxlg6=Y)4wSXsuiVmf{LJ|L==?P3de!8Y z+i58oJ$$6i=(;zIrW`DaaTIFvX39f6hK%xh=5((AWqYyi>=*_?R`>+WCQyDe`A=SqU-J=$`ZMzf9_BZ7=kG`&y z4}XaEka_s`wy(+eoj z*S?Zm&55hNBLA9=h7cysCk!d-7v~1m!i;NFDK;F}<5mj?H5hv2#9pS0A1*||siY16 z6NLvtB(kHxoEmnt{Ty;ngV7i)4Yy*yLY#HOg+1K+ z#r>)WmrioXHWFyebi{g+=FNH%$P?F4J7=2Wf1o!HW@-umrNw zz0AGpdc~+*K6v4=g+?AzP2qJ3Ihj)Fo=y3<6ZrmR8&_*EOY^~59UcyoDyQ45G7d_ zly$*V)DtCV*%UZP05Xpf6!zelp=!>{F8I93>*&yA>aKq!%wbnEu^3Jow0F7uTlB@Z zUM+zp&G>c*apq|Y1@RNHai-hY(_pL>aWkf&AOqc6%#&zVIg7M`jiV6NOZ$;mg6jUy zdvUhl_io#2yn*W8)*AiVhJs1{F`|z3!2h2>ZJuJixUNYKsMA1UMn@AS7ZY}35T?%pUF*TE*@$ri_&CUCrXIEHJpM&*U zIJ3}rr@W}rLEND=x4n2&-dHY&ne@?>EbjIG8rZDWyGb*-k}$I$s7t9w(=>r;hc3ed zFOa-Y^Qx!XD5Th7YxhL_c`o@I6?r!5NZrsJ8P4M6f5}V9?hb6J!85=ir~#?TimC<6 z(hN!z$tq3x7O2M9tke*b;OXPwXz-A!mcIpRh% zvUPV!!|k2Vi*c4Ni04V&@I%v`gaLEHe~;MrnjJAip-O&05}egk%<~^O%t(=JbQ<Ko6H86LX3}iy66Yr& zRV`AZbW~$Fgo+Fvsj*UYWg&lp;tMSEnyqif_aV)i4x5u5ZclD_N|WNT#NFy4TeMsH zes`l-?k(p&{77tcj{e^|uYJ&DD~%mhJ_7cKuoV7OgHjSW2DUw0Odp`)DW_swsq_6BQ=w>=rwy}lXxMOAh$;8X4CmbS91POi?lr9x@QdBC@7&7|lL9iYyigHaCf zvvGS}+O^r+`^UH6A0eMir>v(Rz>vX`^QI2(f}^t458aN#wl`58wa*e%=oi0T4uYDS z&#%>6u>q@Ao;R1@a9)-c3_H4J?iMv)JtY!8U!%}lflr_ANfJxii|+50c7GhSfHmK@ zGwz8bF;#bu)3qAmE1X+PTse&0?LD%`+dK=ePrf@62oL(Loc!j%sgM;^`n7J#EJ1X9 zwD2;%&GImZNpb&SaM|Vld`9gP8$OXA??^LRKO@zQWp2z_Dfdl~(A{MQbL>GM16;d3 zIc~4S{zeyr!;8C(!`s~Qe6d_X$&M#$IhjHCn;?8rch|nvXGsZ3{#zjfX8q8w=RZfS zFYG&G^%RDl)wRk^_sDmKwl)Gc6oFKQ;gVB|`?veYXYz`Ya>5L_wuSpP#gP_=z8y7! zly61VqTD%+mP^q0(3Lbt?oTi=hInzRiPUGKpjfOoo6C%5%uJBxaF3I3aIRo_+6 zVdGM$_>=OE_*YHGu3LSldN-!_$bVF=sKS=~s1Mj%4Bu>9eTwC+y?Bn&t!&1^^ogCg z&&qe-xceL&FaN~tvC^93(2H1xi@EYdo$KknS26WCF=x+i6+Iw z)ndZCX9LZ?PsL(OE7E%g;VXLi;;_~ZCDoq_@xXwra0MK)SWx%BSGW&8q9Z`&5nN_h0EkH}&!~zMBUdZRJ|I%@XCJ86Ix-at%MZ z_HL#)P~AJ&Y&;%1>_5F;{b!i1-eyVKIB*<4ZRr}Aoc#}FOhzE%NRsil)dYVnAe&`9 zb*_Qgd`k^T|38+#F}kjj=DZZyt$H$nr6&Zc$J+FndQ4lbsqUld?G(J5rG7b)I?D`rUzMF>OA9*} zn}mVbhX2I6k{?C3X)8Vo{pi(ip6jdiv3Zwc8VR^yD>+`^>8@_`k-I4J5Ha)5%xpZ$ zcXtaIVz9hGklNbe*gV`My7*9KqHjra)&ZSZd~&<625cNtYk$5imUI0svnaJ{M8#Aw z6E@bR)GOoN5T2c1@jD~^7yD{HcUvYmt~!u!W;lxCHV;fJUHpg+wRLLrpAK-_%*mni zTK!k3yK)qLccZzM!UT?m26}4g^0N`<^i0spx1@?Gbd&EOD9hZR^i@o@q`YX|j^_gu zvhIDhG=r}_b-4FEwm9mbet=k`Ftq_E53^A)fNzWXKGKeM!&`2px++djUUCbxlW*Uf z$aR{7H<7E=W#||5ZU$B#**YA5=1^&}H z=nS$ML7E+90DaYBoSx{29PjApPBY`nyh{{ux%lEtu@Qo*b> z!~E)6gQvgtKZPtBeBSt9q}!?BS|Kfv)I4rfTgqQF0q%@2AjGnuAU_@*no#G>e78&DLya0?=+Era5ia*g=nlnnfGcobLC zGL`86_9<1e=1J5U^09{etQ3(EmI-O?vC$g+87zEPtI!sp1NUn z+uvKG6kK#N(6MRe!PsxBPO1E?CHj4pIZ#8o`z0}?q6=ba8?OHl8m5zl)^Ff!XL6Kh z&J`T0N|+&ku#kJPycmL}ozAh_X@Q*JACp`XCgdTOpZ|!gW%9Sftk~~M`yFk=-RDfQ zg<0NxT_qV3N|$m#1SmSdQi}57*h6mX{=H3tr0p3BCMx zqDs-&Ro)CPa>q}78@A4mfF5I%p{Qa+fiKE$QxL;U(B>JaRN+s}keKB|5t5-1I&YIC z%*i{?j$Jl|EI+n#EDnwSD42S41noU~u`(j(hzG4A!+Z^atp5hX&jqyw1rZo zzjm}2Yeno;-cPkk_@alk2Y`O^gA-qkd=WIidHm_S3inS>tQ!EpxR2)gi)g{L zCQFwNj*ZA(T>Un8le_p9q`bzy+QiBl$b$`0SOSCEJa-q3QK1(#qr9P?%>d?0NnPL| z6NIC~WXxCO{Dw|ifS{$PRsThRm9R3gmA_rf?^`|i7fh1^pI2K}4mYOO4SM397nRN~NG>irvnh=y6I>^V zBsO&2H#X|M9R@Fdr@^{YgAOk~zbfFsc<4(H-@SVmhveSz;T`a8eR}iuM1l#&_{NqjMtz=Xu9NGSLRU% za1no49IIORXmcjC(i1GlF>$aDmYCKabt%EVAaFiXG8@0gjcOxPjQ(pxBO8Alp-nM{G+NULAAWqb*oK^Hq_N!=PsSAoJK9A;i-1LhM!^S zyhhc`0bIOOsx>a(X{%_g{yv0Jh#Wf9?~3~633A0S8)58wbLj{wbvko!-lV%+l@*u0 z*~AW3Q8h+5jLI4ve8r7h9%qQK2=GEWs-J+$FK4x2X2D)92i##|+vD1c;cKJ7a4)SU zod|zFfJh6|2^9<1tHwwtc2B+Dsq}rj@YEe?R0-&Nt_Ew2>$W1K6nA<#;El$ zF;gRo$BF(?mxqZ=-7B!?sB4!qLyP`DSz0_=9n0=}_w7q{W|8iMxT$MqDkz_Mx93z# zueUcXv(*JwXNte`+52JovqmBA6;PeNTi^X)^)PY~+4-}zf8suDPxoUkZQ*M#Qto=M zmHqA5)?3>m2+*;Whrz4EKPf8;E|g-m%WgLkI3$Fa)gEO8N9IkXr#hKnVaflOFYD7L z-PnHs0rjc@pxsF{A$_mwKT~PN=ynn6b*IsCPJ!AvR<>0_(6TIc$aZo{KlvR>lVa;f zM_*4R6?ENLnJq|GC+)|7O9wJ3DSYli4%dG=m~UwG#We|jD69}%d>;h>x4j%k$Nz-8 zUuSLUpcOxcJ4ncpHwL9Cq3Haov75*6plO!$qHR=LVjZ#|oh z)yzghZCS3ylM6VmhL5cB4&9-7hTKWB{NNo_ZuV8cG z`|Ydj{Tym`ZYC4j4Ysd;tTUf=_U@Ff{&>f0-fB{N!TJ z{DKa{skrbZ5Jlws-xLhI|7NF0?Z`xS zC+%CKgGAqq_O$^2@(i=YCeL$pvoFGQ|HNEy9Ax1?j+LpRsT6lXYH)ua;n*dFlOj$^ z*l~|O!1QkMKtTqWclm$MSw4kYm%Z)*M096yj|sm`=vjBQfOtG=?Ks-q)Q3ocPi zmo2Sqi+O9^3Yw+@myIj!AF3;Bo+E1&ZJJ0`@3xx8JrJ4K_!R&g{U|Qo_aqAwf3Sg9vxA1g zi~rHAgL>V^^6v%@ef#G{ST=`cRSc6`FjT9NGZoeH@i%2GHZ+g8Px~jM?LZ&Pk=^qu)B8LXRKaGEo$E zq@%mPz6$xr|A?QLFl8hNG6iNETUJ-&o@71Ypv*?#_!03XCJLJJ zkhUPcSeX0P__7B0F$wr5kf;U9${psKmHfh72_XV1<5TMnsC>!1#9gsT=x5a)>f9lU*#B+fcQEo?x3WYY51u|d@D5ebyv|Xm^ z$|u6Bw|!lyCquL3WmdE2&=5@Fzjvpo+<6=@wefF~8M5*z7`f{8zfwmFNd^swk5k7I zv;4g^+Y1|p4qd}!8HF{Twb$&m16Ao=wAHV5JEy|muKZ?h9s4?!+OsnI@#eh1#Y!hB zpm>GV@G@TYJ4iz6ZKsaA{|?XIlT-9x*rs0Y*^RB;3a%xTAX$1_wSUIO$b$fb2AX=irYS55AFq*Mer1 zozGdL8>L_<91(Q|3?>^w1PVI3GHk!H0jPJdaEU@?QYDj0#`cPviYugaLllw`k>sIQ zTsRaN(oPE@Q9;wvsI0C0`vhyFTmCfPq?+r+@jL6m)GpWMXp51DZirCXbmzN#_k~36 zi#9Q6!eR-)%rILSVT~994Ub`}NneG6!!bPZw;{KTayf}~p%vK7)780>p%v*%E&_oA z3nM~0)~~$h43o$uyJ%Qo6Z;ae+lW%yWDK{etux5BLdY6{O{NGh>?je)6%i&+#HZLf z+-jdoG|uSywB2=~;tRlgzxR7j^99a;2ftPQSjd^3UeQlpR)|CqA)=UXM2&m~eySVLe zmXA9|9jy;ocM{O5eDa=MCA>bmjg4!!`&eg#C(~T5v~yoN|GuWq+__s%IH!eeJK4cx zZi1}3+WX%06fX3(pK0-_`hJn0?lmiD;Il53)BX|cvv)MYLR|SiXx=*eQcqUb@-M<; zNJo#hF3H>BDp|;DwpZXlyjY?Q&&6c6Z|{8_qiaJu>m;w{%6t^=GOz_y@?Niq{~bO1o&F0+Q8dz+F(LX)SK0-5}KUpfe8xpAd; z5rV$A@~28SJ%ifk-J=OqHlRuiAVLTssfsaaF|BcEBT_jtH}WQE|2%{I+&^tUJ{-C} z1D1e7hSvd}d;yX?H)TqqD@~td$K8m3O||ahT|+FwPy%A<{qbTNVzmCj}5T z$S+huV+kVS`8d0r5e3T5LFkaQ4UlAnzX}-fP}zetvlVT)&?gIAVTvBfC{l6QVK9bh z95Gi3Hn8D}+wa>n8)>K%f&gXA$eLJ6Sj|;bUJ6lr^_Wl7lg7JQy z;RD>$(&-pT;J_w`~TwR!%mI$9$j=W}w~pe%0Y^Pd?9NC|l^-{5&a?Dw$= z=`N00ZkM3P$@|1^o#dzjlWtG1=Vv#-d9@Q|IVFhdi~Z!KT!RmuSw0Vu0l92P%Kn)S z5=~#6{H|tBma|;1t1WN_Ej2!%X7u{R;b*x+d&&MHcTsWralP-??YA1-lT}Qjt%v@; zU4!-qCD)Iwt;^Dh@YSNp{BPG*AO5ceVh);Wo;@XK^UEL)w!bGY8~mc12H#=}ip%i< zNIacmPTXELwNYD*s9N%~l{QcIh&;;sBp!?Pjm) zuz7}h!wUu#$c_&85&I~kOU!=NR^p6FX(zM|xZX5N=%QG+D-KYw*BsvU!bwfDMBEJ+ zDmyKNo%42MFJbz--`(dlSvN<@RG-s-u~Trk8M~vU3lP^Kdn>VPTn3Kg+=^+NELQW0 z_2!Hzi@fl5&0cDcuGDUK&N70|OfFTNzw*by6ycJF!}N>h#!@$a<0cR68clpe&1 zc~m}ac3bu4M8AyPIp5FiKeyFSE6E+a;}5$;s*HG#q?y@sP!o6n$V!#pTE)D0(-x-< zJXBGc#i}!!lbu^OQH&l!sXa;|30^K)6;-I!7bK}#=T{ssn2^1Na7YNuDko=O?;l~1WJrh(F}u(o;8s6 zJV}ydlqWnXGm!o#TuE<%8GfAOQrv90vq^fK0S7Atd4b{@n#kUl4g^XBS{uEII+V!! zZ=?KRP$rWK$zSB$ZdDzjh6hfZ0SV#f7X&Hjf*8^e?IdB*TrhkT^BviLel*c`kj48) za=`iVuNCM4b;*U$5WlT4o-Em%u`0CnzoOJS76-zeHqqhNf?V?+2>Hs<90>1Zjv+U= zQirU3z)i9Qe<^GQ_Kcw28OucTG-UP+zpV{()+&(nL-__T4yg4(>^#$KmcM9gEDZWW z^kjqXFKXe26wH|MB@qm|njjeh7FoqXApF?5BB3;uEGOpGB>gfv8at-n*7>*Ckh^Nq z9Yt3&r3=SGpIA69&2~|LGO1H*dLC#@9Vk)4CUh)nN80bP5>-ZD%{g9q9%TqoPS%Cm z<}3JadPXQsgBkvOLtqUJ42glNe1|~41gSQXl@v~Je657@&bT?n(q^>d-!mbO*T$cB z;b})MT*0Otof97vhQ!f?#J^GN=X82$$_%4i=0Rg8_6yFY;UN~7jg4b1Ta9qcha+#n z#1CU5LnkUwq=mp#hZV#SzqKV&uPXeCX%1|*a0|`!vTS#YK~RUmN0&xS81jme5-!5W z$U)2Vw}f#|DK2d~NC2B9=(=Ckw2t5nDl~nAyeWhYhQkaQ*;8~uW}>Y8!LY%XJrX76 z-Zl#a(|ds7&_g^4Nm{}o2!X8f!&2JAwIe)%zkA3yptL|omh=p$U~ge&tJN7o;}lLF z2=vZL_|q@kauGqKgw%*n(1e5p9$m$vz_EuiIuFO6;3&~_yG1`y*jNhb%4C<#UU`L2Hb_rye#)fwyP0ae}^)=UO>iu@{>5hF7ktttB zy35%FIAi57okmi3zO09y`)as9K;zP!P7JwMdFSF$$E4pwG~Lb3eW#LS`*d+xS~gjA z`W)LnQQ~gF$?YPLB}lUCw5*M8argei$Nb}bx&uByh9|A)a94b^Pev2I7;TmVuT8bL zPrKtbuPZX^W5|axojVlzo5RP})kNykd235_dRg(^NfDMsu?kJs_HQQs_HM@qS=YPf ziBzA5&HDRQ?aj-zwu1|6xBcaRawH2^ha0_(PEAszu!#E$1q=H;uUQTR#b=B2H>Aaf z+_<|vjh+v{x1;R&OG&n3#ox#_9}A5kL-cg!QG)y`X3XdEv#U3hXxw!tZeqPkS1;pF>6`!$embi%Ld{tRr`8W{IFlEzl)*5QEO&9G` z?u&FPuj{J(bt~bs6wPU`Z%rKXFqwYNq~Tj@7TxhrQ60163#%n61;yF)mVFi_Nh%&yY>cQ9rEfjFh0&C{`RN2E-j zh4t1ldJ$A~B^vfH96AMbf2LBuvrG*O^4|<|upvXTX@^A-inmq>?4K0qUj>v(9JsX8 z7!8;hMeQl#W3qM%W+_IjWr85kqT8%^c3oy#H&}s{_$CY|;e-ms=qDWGj9(>_HB!jd z8_g}9r4_~}+NG*un2~tyvcW%?IN`a1j4#_~#Tw3ILimUNk0NLSPdF+N0#g);Cj{Lx z7{6FBo#L2T^R=NYi$zT0(r?AkV=uB;n4nG=-x1hm3=>k1jE+k*Lt7Ypft~xUUBHF& zzm0FT)!H0PHihtA7%L9KX$gB`6$(aICI5>7YRGl+%=Zmc{?f(+R-`tCxi`i2`bFs5 zb*my$?}DwA(VY+I%*9n$iH%?Im<%=~cwJBw1%myBMApoW!L-w=7$i$`R>yk@7e%wS zXZp}YWYi7R!djPOo8+*fTF7LrYl`RaT*Q~odl(~pKmJ$N^lTm^X!G)v-^z(oPZ^B^ zh9blzAG~P@6TP;bVVO{&QOM$vh-zT@(eYt85_*k;bqLF{CP#Gy4+l$ zlbM9!N}0Gq=ksd9?8VY;PU_YWR3`v;z=OH#9XtH2L5I?4w?eKazkl0^7edubMQkQ8 zN~df_oO?I6ZmUA~rOf$gXKQB4VJw9HKla{W{vDk5e6`7s1uX_fggG@Rj z6T^VE+D%Ptcvr6nqOO(1R-f-llYB>|I*WG(xprK6cZj{8Uf{C&+U5k(cfF8bII?{8 zKDslJ-cIP^^!f*w$SfZB^ptU>ml_1RE^+~!MHOq$u|O^w%tqi*q&yxa4dGRzv*2avJLe>?H6u9>*{7T=$C?I;H9NLlWpq`6dX ziCdN{4>+rA*6WjsDyw+h}H7ne7h+Q=mT7%$^<4(N7%>#JYt)x?VS5u}E6 zxsRIe@tk+*xUxYib?(Zua|XKavt7SOmr^lqZ`_6J2pWh&=MB-e$TeDzk)TNf{Hd;N z?Jv;Xwa+2U%wO$KNGix@IDtX>(jUvC*;3;NwQ~Z!*2;16RIu1rw_l}6B<3-Smi2po zcr;v%=LE@L#F>%e}dAqely2a^;^b5nEiqbZiU@S z-@3{EB3@gT0A|_zuNfQi^7D;ibUjv>DEExvI6AxS-(1Tk&AOP|b-~}`;Sco}Lt?uv zej?PCJZc0Xf08qc={s`kRGL&%$Z+C3hr>AOT*Si5!j+ z{WK8DxBy~{9;O1hltm4l-d-mm{4cH4Ug$Z-zr`nM!`|oFYvSBLC#!h&erIG2&)8C2 zpiuT}iS&SsY~E~Q^w?tZN^U#LITE2(&)b?mRTq_RYK=oMiq+juv!`A0#z8|r&h=hS z-8ndQ%oZZTLusrQqZfAn~QeoLty*`{ZO3*(i+7RbK4d_KPgs(S7J{Brk zY`;Q^dJWsJrQBwtK~A0 zu77*Zu5Yhzsk30S!flej<#j*HKX7t;b7XtK7b(QlC2(m=LiY9eIp=HVecNw&0YhYIiW%X8HECQ69-M=f=m8>nzJvGg`oS$eDM)O=Gr{zim#o7+v- zAYgNKj6#G2P6&^Af>oQS-K5b*#?_(4psT*dlzWVv0fSP5YFbn8Z)A-MuE)klJ@vXz zIy`+*x9CzKKE+t1GkqX0v6F)3~J@8 zcP!m=Z#9cQ>=1V}sOEE?q5x&Iqff`Km9H$648%88wb2j&Rw?a*rR z-LgEW4_7}!YxORRLO2RXoR3Zn@V&%3KQGz|96wW0$8K7&jsTN%V|Q@rL&7`t4F`)z-bSHITy1KVCB zn`%jdNa8g}ZKTu`er)lSUvw-nR9Uz!{*fDdtRPCqui3Gtad{NJrm_y_?DCXM`-No! zzD3n`%q%;!7Eex3OFP$$egR>{6=hJ5*^#uau0}+k>nR*-;rKb2?)`Y62&OX3(Gg~K z{b?i$c}x^sOR59GujL>QSO70wAO?g+Em{8JX!|XZc?iD*exdk(-D(Wl_6fu!l)TV9 z^kpUbg6vGC`f_(rx#}JH1Hw#2xSx3IBl9riGL*;@f8J}pJX5#|3t2KD6S)y`$ijZC ze_-G(4%81;MWe6a2+pygNnZ`mUH!lW>rga;lzXk{Z7n$b6MPH+Z+nsxsS6o&Y2eU= z8U#pW4DLF^5+(|@n<^GOYx=fk5yXN4sR-J#`Xx=4i6I;^g80QI2x=WOMODUg`^Ysl z|MB_3^7VRNVOJ8+m!1FenqJvQWodeJa25Bq9zyG3f4ns~8{jt8p6(#=B=l<{@Z#qv z(x)Jo$946~q(_mCNA-)p$BbITOZA;q&n1% z(DM|O1us_62>!^MvIb16?SO1tAKL&v%LBUWgbMN6nWO?7O?-X{jR7aex5ts(EA^fh zS9Scm06$P~lR!gU-CKOcj~Oz9mSH-7TSsmZV5*FD;lwbl-4^mwcsCOz50t^9?#Nkw zk_YF*;MbO&{GPdW+a% zidqiUx+kFrr&rFunpdOv<pf5>XQZ?CvEc!nUW29!8!XTrA6^V3x#KS7IqO1L8vPvkGFB zheYjgO;B_ejvZ8lPZ%fD*;FAg!`En<<$AbZ@#2rUvT~d}u&eB(bF-JO|$v90*C$yGmOr)|@D3Z4a4G0vQcF zty^CVzdb+(eSwT@g)AifGMpOhJNM46FJYN3Tc)Z$wbzDd`3Csm+|J~UY9OC&}(&m5Ik1qAxcPkVsxmbcDMS$>On2Se{R^FC??zV^quM-!{#Kc5DfXx@nBKDXzOy1Bva z^Y*sesMY&Jh?hr@|4yxHJ;(WmQcqeu`J*-WidulPOk;r_{iwae{+7bLD`Uxb(CX=? z!q#<8x53L;rOFg3XnuH>W|sNwUc~iI*7Ysy&U`lCN!{jo`Yd<)%KGSH*zmVE`;g^m zXKZx~Z;RFBd1PyR{;BcF>*I6FM516|7bL)$yG5SpI+fDPr~e{DUa}1jt+w&V^th(# zDC_Ilhr$(f3wBeC82tN<e?MRV)9$*X*tql1T?oOmPa9cji**%_FwXwY`YO zV)uB;?&m+GAOH9qD1@l*oZ~rx^?keQTy=NA*#P1fy^HSxP10#D$?h+nGk$Mx^cr9n z=Ob>f`k@0iJ@rTBOFzv^%`R?6jLc2viVP%bq|jQT$Mib~Mm6E!IhsPOIW~o^Gni*h z#-Q=AR~;JlYnE%x=AYppce~MS$=$6((y!A@%Vdz=-YA1_wpD&44jw!{bn>W7?b(5F zQ}&)?sUv|{)sW;_(9B{mEpt4`oGDAF?QbKKmaKz6y6;tI1{O?;C44bxiJVIr`+?## zIf!Jow0hPtm^}A>{8GXVV8_%=HD})LkyyvMLv^zj&vPz&i!WZ+pu~zy>{?j(t&W_I0C~^ z$>V;bA~BH>yO6qkOL=6E^{N`@n96v%VNV;WxF(L3{6)BPgY@r|s3 z8MH7lnLa%NW|x5}hnXDLq4kLO~Izh8Xi*%kS+5zyA+D#+%H6Q zr#Jx%IL8r!22pK7%Sb=4V?{T~fvnjuas?@S#7kVU&d`Qx9AdPMD8#}F8HqEd;!g1E zuvLZu4Mc`abq$-I_!F2pds2n7t%N3VRYFp0;(-L%bsJkttvr2nO^Uxfr6Kkb4=?ZB zG>iKY6`j8uFR$m`@buz>NSx-V-gUo-k6oR^+wr_#Nfo)&Lyv)42kkmebU+i3`8qlH zTYBQv^oTc_7d&wFTkffIyQ%Tnu8-Jn;@SL*4HJVUkPu@n$ zRS!Ckb-ApQfI8d@?n@GO(IkqGc-UGZiW@dsZ>sUBs-NbP)&c+YEBzPn@UDWq) z1Q3nHB%3b~%Zg#oly$QkSdL93LVpUo$mJx{1}11jBI@}mN08sb=a`xjAWN_U6C7n- z#F+^|)`Rg-#Kt4OFbN8*WFZ)2HKoE*Reut!2H;bz9xVSi@fqk<6dP0`!x63u9BdSf z=<2XogJSR{MT$hhp;h_c;gi8yGyBrk!iqdESzES@Lu|8RZ`|@VL~F*cnAREA3z95I zJP;%ZscS@_xf?b>=GE*ejBsg~A;(#fz@k|kZ7A%W;UKG&@Xmlda~ToR?{xQPETAJwM5RRh(U^g3iQ)ZyXp%0KE}&RKA7TC zJxI3cEyKwYhP(@6?5Nx43r@n9B{u8D4YXu{MC*VU3rl0I3n5NX#`j2jxf*r&Z$P}* z%BMbnp9CXj2wmO_M-G)l56yu+*0}p6)7n%EKHBrYRX_|b=WnUkr5g% z7fsX2eH$I$MjO>SWWbeHPv@KKAdMB{cYVf`TK5O8P-&f*Og3sH-ouE_iQ&OQ+2N_5 ze9SIC>(RXnr(!ossJO=uY(*t`4uc{p(G#!l6$T*?!pIWlSQ!XLqEOlgs3*~hf=Haa zt7?7DPNkrY_7Y@-s1eYZ;9)4fz=TY6%nV{q@vN}wOfF_nA^kYuW~Gyg31x)%(vguV z4Ndh^@L%Ly4)2Y7X){>1rWA(w?KMYSMojWFxwpt3LFa-M+4PjX|2H~hkr)cLgnWGl zB|Q^{XF`fKKOsV^4?$&^R|R9-j>Y~$$;cU2xWrbW2Htpo4(wdw9uflN4GJv99u)e7 z(w?vC1t=4aYFP@cmESvmB%XBvn8u@4ZFWY!yL5$YKVha4%wP(ekOMaHvk4Mk2uv_K zW1kBBCszh3#o6=+S(tQp?=1KST+A#)d;y!P6kSNGyEq;f3=9#XsTBGiVN(B>pTQ6o zgjK38hHKa{J0Oi!PenqcNKwIlTp3|aDTvVwXi_38D55IiChXh?Cm*me^#5Tf^XoGE zI!5@Ndu)4wiqnCrtT5UXSk-Z$->3D-GjLaw|9{z!%dcl31T9lKR#^<;f&}5v6v#5o ze(VFDA?N|MAqiINSqvGoVI#@5r@CD~)6B*ZjNwq~7z}S%@iAYrh9>mHghi3cL1_yw zJ}gms2J=A98eFKCLI^HoCNk<{H$)i2sSsB1S!cvhqr~);&)?eVyk`RRnFD&+MYPDA z%-uuX=HNR8H#t?s0NaH|)B6?az&s`vW8QHQmIz zQloddE*K1WELELYf2zJ*t{p7lF|Qu?G$W~u7N6DQ`9J&ahxgHRH${@kw{QheiYd8o;m7&Tzj&#dZx@^?L0qzbV3*byqqPEV@I0TV+`zHZg% zwr4MOQIkk5xG$XSH0WK|6c3ydT|Dq*a}loz^0rxQA-t929A9Sfd?s&aBxF6Bw_Rj?2bzv3@5={Rf4&4Dc;=dO$zgw$gN19|A>zhS^C9+J(WJ2As|`ryYtcjtC8EN8a3c9#Wt zg~S+-VZ#Z!qY5p+IZmO>LAhMPqbr-R{~tMGkF$Npoj#IH;uXRsA9An~Dn|x^)l{62 zR#fEe{{Vr)gTft}ktB2|4^w`lkbV-Bhe_Q+PA>c|-+(KgW3hsq{x-J4_;<1pDb)J{ zR>Fby_wU@`eKZWU4Vv=aNTSJV?dr-^eV?g3r(5PJwx&^4`5VPC8f8;}{RQ*PPQ(ue z{~LM8f+&X+Z|^96Sr+l17^D2;yT&*pv;_9rkf;8;??+baR8xfsETUt9sA7-^;mA^= z_zj9$*uRcIjAI-nmB$kemeuqKji)`8p&$ zFf}+G;LbDc=G0haXh%Ft(<*=jsN-I}oib>@!A6fPM@!4@Xd9(jvXrOk0%WZ<7v~T$ zy#rRCo1bVL;+gs`WAR*)>yExXr3-P}2yyil{?wSb;1qPi@vDDY;q4*1-nN`{cHIx4g?K}uUIn{o&%ukcClsPGJhE=#eMIeyI+$G+L{a7 z0ZhMHD+;I4%I(hslGLxUFh}&Fj&=G5{9T!RjmsuW%t1rbnWf8rCUKVB+uBGByEX9v z!?)xLgvr&?@HH;AjutM_8JB_F9MO7O0|l)csjJ_G5mtoRfPAssL9CR)XF+)ap_C~A zJfZU2*0eZ2&W3DZf?;1fh{d7Op$c?baYoi4ksNeO%d!8cLwoWibfe>j`A(=UZmUVO ze}$F)1fsY~S&qfTL}#&U{@;m|2x^a8k9f`hwR$5RJ>`CbL1g+wATZRqbRzbEc9HTb z22Pyd|4=A)SR?=Xk5=FhIq{#-3p~NrtVsol!{_lXMrgw4lFPK8SHmIFYIo!*=MYSgZ8xQ|$}sB{rI2;nb6EL#U8ju7Ae^-ScsnN+ZoQaha5_O?!_e zx4eT0PnPB>!@om?SRGF9a<}Dan>6*1!QI%blHp>zkMh8WcL;QSb!~*oRYCZ-_2p5* z#iYZT?nCr;Sy?NMCac2c2(Ze(6mot`Pn{K7 zR}V&`kku5O(kcG`c$SHF`R`YBX-@tKe4E8e9MYvM?~d-x?m=fWxs1&e2vjlb`LqS0 zqJbfTQ1pA`2x6t!ri57{h@v;HsBdwl8RogjXMl&ydA)b8O9m=B3t^*Gtx1%wzG-XE1kjN9(e(lNpA#cKL)HJW(2YhpB z*yo&<|7p-8^f}G#@-)Lk&>EH?TU4j?@(ga6n&)41n9(})5?h%|m3aNU_Xmz%h^{qS z?d3l;Gk*~JotALZYwXy3=odi%o`LTjAdO?-mP^tyH;dYbyPI^!;C1)o#_2PUcrk-U zZN8`7b(P1nj?Lg!n6ZS=Wu-c=O!qFwFVSu8xZ~KtE6#14upcBv$rfOIzgiEDJOL;}M;!cXB^vIG})&xc5ixQ%sYWu}W#5JN2ct|%eCtZ$%VKg7V0w6_yd z20{qNh~sX7Et7&t66430YCvv3y3JX;5UoZa!c-7~7y^_K0&R(uB1xIGkI#`3c(QO< zf4>gj^vs};n6qZ0^B@-|wu_U$?#GH7rYeloQ0zv%1Sc#d4LL$F7eyBatH9O;W0kL0 zS$35G6w60kS@!!!SD2=P=zDsPjF1x79S>m*RR&E*cL1G^X-o-usi+`hGR(NFfS^q8 zi%J#_g+7RgJ|`I{6X|P1MHSZ8j;5B|8JcXsvIWvu%&9q-{JejEn{qu`dO5v6Njo!@npQhx1EnwwK)BM;!i&r`YTywyLte;7u}|IvLiTCApb z5whudZ0kBKJ*F=2hn~x`NoBn~C-Z1L@HeUJGioN^DcvCQH$NW^$!GqLI_&M2^VRi9 z7mwZgV)<0G`k(1SGBC{-UqRs(S?Pbba&6{YO~zjjHdEZc#4OnRk2b@5%rg_1e9Dqbq95@H;z?$7MJ+0Q;R5VC~<- zG8&DmjSpSBLm%y}%CvazhxV@h&Mss7nVdegY-Ib%O&!L+F5$_$HdEW;KpqTB*)k1~ z#17SX9%IheAI;P3ZPH>ury}K^E`sK|l!Gi%$uVW(f38jx*K|amMm4>26Ims zr2@BTl9j-yX8Z70YdFichV3mN<%_RDCp9(+r!;<{#aih`NAxGxJdv@Tqn#$(6BCuu%e%1-4B+Wa^j%W@mXz=O(6;Q`u4&WCH+#Lo zRich=I$~O{X!d-@tr)*m6+lmFSHkt%l@H!A%T@*P`@*=lFX-uXCKrsz-?8sf%aHf1 zc7D@rH3t=@^0t~VJ)j8a4*9aI*q~a}WK}%c96!H>5`pX3w@IEKE{&A0t}{N>H2QyL zk{`CaA(sShX!PFZRu}aIcXGz{X4Raz39?6PyOhVS*WD{r+e|1gek~XJeT3p=<-e=A zd~F8Rr_nq{t>*Ek&%x2O`S=%^674H>)3Dra9oWn9$lnd7b`&ii>5W}~&ddykpYy(+ zi{oz`jJ2%+d_F=Gxl2HOb({#L(9XWY`k9mQwsadw(;E3UXP-f9vXl%GNZ)k)dyS8JYyw9(=Gyn;K+K6d=`$D>>Zrz`JtM>Y~a4IwGwR5@AE$I#i)6^pd` zTjzdj8dfe+P>>|qSGu2;`ZS^_uqwj9A~+e=GFV}8!07T2Fqn$PBrN-)kNSY+vtSg8 zC5;=Kn5uDNeoUq&^LHFVLeNTJSb{VxCkp%@4!zC)A?luueq4Ku9i#E?(Ec%WLjok* z=YpGA27Hd`b~~n|k6%FuZEG`^`;bJok`xZBSFNAGji!#rU#}*ZaFA}Ql@ZFq#Hp}= z9O$WcMXiY~>Qv*>M$~$qWz4S&48Z2K&f-2=MWYI~K+RYAzMs&ru~3i?Y!tr`vDr=0 zE^91j9sf6F)ORv$|M@+Hj0zokuof)YuoTv$9I5@AaDZFiPT=cScR9^Ie^cdgUKc&9 zm)~{gf8?-QT%1m?QFd%0|5HdWzwgH1_0Ia}?g9+0R=xRE-M&1WmS4C$)r(sIe+q5K zn13q2{8e}TAO&AO<}(lHUjmr?y=t6~?g%wjGDGtPD}3+8T;C5v>p#BcKIcyiccoUB z)8~^!n*-HsNb2-G{GZ>mBX@~>x|b_h);o{Qb(srXBI5(NJ+2Lw8MVWY)R}kOUmevF z*1e{lgk-OgNcHvFJg;uY{Y)=oTj59EmM<D`A ziyjsqpja}J*prf0n$Aa|!m;6@1H%`S!ww}K;`vg@*?P-$-dDk$zF$sf1~ulG7BA=p0AAiNLnbJJ*>S0+eb{R8*w8RYUF*jU=it2WD&`~yw^&M;kKOFg6*?)c)YDX)`WgIew@b-q0LOc z`F%tej~}Ap9bErPYV$hOZ#Diguxr$$P5bdw zM`)Bp$pCsUrd*Hida9VLAw6^8SUI44&otc@e9J%W@y>fOdF^QXp?WlJ!-X^2mikzr z9CW4I`xJY?)wN$}Hp8t|#E~VmmYCbI9H*_-^qYMKEH+pr)#vwj|M-|5+KjNzs|0`J zX1s&$xE(82{Cu#PYX0%v&2_&TYyK!7Jbe)77Sefp zojO?Vx=VL(U;nZZK>l(xn*UqytZCXsEn)X4eN;ZR3mE-C6Y$h^+> z%!`o@_I^uJwn6prv8$?GUia$ALRIHe9K;FFk~+k9C6;5r46OctRJ~J>WL?_@SVou8 zW!tuG+qP}nw(Tz4w%ujhR##2_?>BQX5j!F;Gf(Epv)8kiG#GJ&f*DnG;0P8>r+ior zirqd+RpdSw1Ct=u4HAX}_Yoarj7v#@Q|?QPjaLg{S&Jpi|C7l8ONkV-@a^$H2|*DY z#&Kh57bLU_XbZ(UEIwtK0#dPyVL?-#IJdGqr@e3-{H<{8=V(6v2^)j?oQ#Ca(!DY4 zYVcR!ok$<%8l0iO1h@mf-QZPxVJU*d{tqgk5bEojct!<6@@e%H@L(|0!^|j4$lpR) zqzozZqPHAOQzK6bjwb0yS4e_xzKz=5^}~%Pr9P#kdNQA-ClZd8uwcP`OUSq{{x0XE zQYc6-y6Zhg19>oQ=BODU9)HCApV#ZGkJ#UMWs&r6J?LrSw=$jYER4Fv8U^d#=)5`M zSEZPSfnW-zz>bk45DohY++{|QHA5C{T)KHd$yeaHNQyR2Rc`Xk0~{+7L;@_alL?3k zYz=^(E&B`60-+bLE>c=%}$^s8e;sPmrAXmBf{0~FzWl)y|X6sq*0 zjpG(!Zo+8P5RRd5QCGMkdj@IirN((YbFH=B_qK88^i)0}Wz7BWf?B6I22!AKI&7R! zKZ*opL1`37=+#8Ie6tt04x*3gWIQIuu>JnE0InGekV5braMTbDZHvNRoLB@XLppN5 z%dShq0hoMWh|~m%(uBZ3t(}VBEI_DMh+SdQ0S9c^{?DlNiMX$kn_P-l;m+69NN7HP z1RsD(eSJI{EXpHE$)T8#OeP_r{rb8&5JyNF_DH{VJ{~<-ejllJ-;U{ZW#{^qb@}3U zf4&E3@3i*ErB!(oxe|veQ5NLabh8$x0rb6#6O`A~*jUezhfJ+40IzFze;F?Z=$q5i z>t)5w=10frS@p(9-Xw1OO8fls`hSgV0A*0LK0V>9#F0q5*Wu)EYxXNzTU8Qyww{-d zod@oHWN@K~d@TZ#_qR7G%{0avodn|!pPc00K|#8e)9oL83e?y1(bCq<$l-442pR97PC8URxZE|$Y2|iOdO(yBNjklg&~4=gr=Z3 zp?{eM0ldkwh*X0q4eqR^01+}wEF2P$Hti0Px|0uS#nP}#Vjbo?Ura| z8hf$pj9I+XndKcyD_sMwl(^#v0=O^~Vo<}RL6L9Ol1tjbyHgBsHhV|cZbs%Zd?AAb z3I*L@EGSMuAOk)cF`uAD^<)(^l4_53^N%`G_Vh-_VroC5>(J_~>i1_kpI-X~Q=3lK z9lE7HPcqLK{AP&bPG7OLWJl`L_10 zZbsKn|H{#|n}}&lx9$$Ebo@VJu4hXYU+ewt&(!!njrM)hf2p{ z&d0*gp(?DFCLcD(UCED4=(+CS-_K`Vb2wc~*SA#WeJ)m(yS?b!-M3jtKWTkkS$e+v z0L&TLcJSk|kn3K2TeirJt=(2TZFz(4ZntYSFWBljbs>DoR;z_+Mmv{W%6R+xwnc~z zVQV&dS;|Jg#WDI-2YEYy%mI`|{W30Pl-$(vsw2JUKWm)CIA+g<_g1u^s=|=B-Q!zS z=X3XZo;X{)eY5c-n!1)QMW;(w;;v`*P`%02&SS&?al}0_g@HICxp=}|{2OBLUC#e! zj-y|%dC}B$JI1w-Z@)G&@n&Ry`GVn6WR6C>3F}rs2Gd2 z!E{bvPJXKdC}z|;-1)M4^#Jkc&(h-Mr+r!H-JF*hwQ{qy_lsi=!L2KDCs)Ai48ECE zEz%d!)?mN9ow$0Xs7J~L{N!gWVyEgaU>{y4yD?qm+xyx>NR6dCcx}5CR_xbAI;9xt z&ZLljp_tZvqg_rcY~^)lmod6J4QW<;9gAXjYN~a;S@-Tt;mRrHVyF8buGF&er_IxP zb#ymCTHD&WRIaA3wzBi3Co{2YW%u7yyYijBa1WiZuhh{WdK9y$t=pqWzK!aZa0@yW zw@PIl_crr(%-XznUyn9%B)TUTay4wFAEd01IkT$^qv7yc=(MG%1T;6(&?Q&-R(mC1 zq)xi=*iBVQa&jehdt}qPkGO9_^r~jrj$}BB8q7L6$_&77w&kHTFH7oUC)+srubDf) ztMxXFVvxVtHp)c5uFGUZ8pzrt`gSC zwY?ngYRPKR4XYN%#`E^9UHr@2tji99o*4v^3<44p13iF&21AD8u!E)YtnlF=xMk81 zvp|<%Ne!<*GANoLTvM|m6oO_s-O}BcB9PsKs`jNNaQRB*`k&dBArOoGfd&=%6U4%_ zWvLlDz$uT4P{hgtbf9B=^@T+YGpfJqbw95X^~ryKe+M|9uQs>`nhV#V!aPi&R4`CL z4WtsvNfZmwfe?f|!qyNGG15r1P!J_ODJ~?2h1y7iXk*8@jXUI5B;homI@Gnk79lV| z0azsTR$7X5kwerRbgNeipm!8bAO^vIUw;$$Tm~XSf*90Q6Tp63SEBCNMLSw13bsmf ze^lZ5T=@FjB|&kz*@>hh42yQP>&4dXSiqW}p2^AXa(3Ham0`ICiJSCRc4l6qM!!*$ z%Z)cT67SwurTan5hiEcD!3;jgJ29 z*!-}qZ<6)mk;8qflNe z^n5bg6`uVczvFRTgF^pSqi5$#eaNT5tn4m?ewEOsS=r@hV>OuH`jNPWSo;+^t0(`> z1Vf(TY04n_eL(<5C@QxT7cQU`0lczdfJ-$1xvc}?xDR8aJ}=$kO}hf0o|^MlfZBXdJHq1I1NaoC7U=1U{wh(O_2%L37r3RQh}e^Rn0CCo`pD2IS|Zo z>ec+8GPeKYN>Q^4BflT-StW%He!9LA4Z>|Zr1Mr#x3ypcez|&B9t+FpkYk9$X=xrI zfPo1t=uLt!wg%cG$`Zrn?xQ?wSGZHz*UacO1ogNph!chYR!9JrFhH3SI3=zxOrS!t z3gDj#-~pIU+m%(Ihgj13*8sX~js^1>f_kD5K1Gs}-rWm1((fhtUGnPUlfC5*3N-n= z-25Jm*xhX}PR~}Sd$&~ak$17kWMwB$WPE=`=Z-3W?Os|4z&_)z%=vk|mHUcTw2k^K2f6d}dbF`m}@ zUcl49^s@Hh*Mk6x77G8JTYGYgBl_ndhWXq6u@Bkz@b&lw~(?r_W6p~N&>8`#M5()elvtIHWxv5Ou> z=f3cD|MOFRWMTR1QvW{BUK9iw);wA zojz0OxRh%DVxi%KFs`0E$qY(SLGVbMl7OH;0f1Of{@&f)E=&jD9UwvzxuX8{BEKsQNGm>@= zFok_MbRj5Ch0_wRzB>+m_3$4~m_Lt%csU!)&FRJ5d}rV=ukaM9(M*CL&d_Zv9B=ZZ zHh-sA7Ds~Z{AcCvbm>`_uGxid)gvRdufwxY&DhX2A4Qdmk9gxs87_}PE=ZIXhf4*~ z!31W2EnUR{he8mrd}BI_7g(%p%Nx|!^a5Ie5<+BGGd`v&XL00##c=UEdc5mG!h&{K@eXmL`FdMf58U8D){N-C_&o?4?HpBhwSX9 z|KB37&R!atc-P-Qa3TKmPq~pjl=@R=o&3&6i-`QI5VSu=TDlk*T&f6BAa+P0Oo_^9 z38%Oy_mFbsoreTy`IO}P7Z4-lUNnHg_6ky;7qPSy)Yc~jLcH$vjGTvV=olLb)S{}&2xe?J7T-a3N zFo@oK*;r7d&-#`=F5Yy0)#aV3YcjV=c6~VjoQiC@+M*O3mJk*nmg|fRRX*O_Tz*Hs zXU?J9>BV}g)v3zm*KW0A&B%$r{%CQ!`T?Hoq4{>A{P>@y7xa?bI;o|UfcYB+fm3Zs z-Y?Fqyl0#7=w68yWb>MJN^Euxr&{OBZGRM*2?&N;pL$RoLAVkAhA5m;r^)PKK2IUm zR4g?Qe*Tmn^x0z8-y-hglT^3_Tu`a!lhBaJ((jT`kR;gO3S+oLTxe3bUskE_o&AY~ z*lTf}n49g!B;MxFl+`^`6Zf_9LmZ`{IIF5`YI8 zlO?M1M08Yr;A8lJExKjBsE{ut^b6%P7k53>!*UEK0gVeoM5Jy=R( zt;HmkY?W9N5z!;+MfUxabU6-7W{(7}JGnxs%W~9~ZMh~X2aIokE!+0~Vmq|jJO|!U z=KJf?CbqqRtZZ?ZOt)Of!J6`1Dpu+vL|f3=y-eqqbTnSk5M`D}@QE29#7fU8h_0=S zdRONUG*WIgvjw-0$;YKt=*c-fz-N+u{#<RV8@QMGmW69hO8NE&|lGJkE(XBby8mDq7vT4OFmrL`yM0z_? z5~u2<;#i3MRvw|H0MwgIf49ZX*z>dSxcUW=#_2mt7}7}fo`!Ntk~x<|E*xIPrMJpa zK|LoYyF*W%SE9Arj=2C{^dGs^B#+8K>W`OB5cP_c6uF3EbM3F1rZ-1K7|Ykjp;e=S zw_z2xx}53XQM9P#Eh=YW1=5SU@?AG%n2WNErOiQvFv10-g#iZppoCDsT103Yp+ik%_Mn5nRkt7IG&;(GZNy$+^gWEa0n*T31VmL*% zbCXL2^^?$mhA#va{D#N*DhBIajfzJizJ3GHv6%)K1Ify+n$Arp0{v35ca*CSAMR4* zzO5}vQG_y-dglX}p@AJ^2BR!s>zjcQI8q@XDb0wAuhE4o%ia=Zt@0<<|Dnl95IG0{ zvr{;%z(G+|=!U_gNrj173@y5_S7NAnAuq3ed`0 zt7GL-vOZv!!yrrZOBzO zFKFMs+#OpFZfzz@H*xm9)^cVu#M0*~xa(73yS+p7$LgW>y^mUcrGJ|+k&t*;MxUGY zB$G2L=wi`fKDpjLjc^yyUV35K738BYT->!{Osuy4F=k?IyKTnVZXU0aKO{#c>*Ye` z_Ig!2Zh~otWn*OXTbg4(8g4~y4aYZK3NJCl zlSqk$z*-YPh!R_J{hZP{-R-JpboBsv990NPEoS(*g|=g77$d5>Jh{H2^IJSv;_24Yc_~ zGzIW8{!@5A(qUhx%Cxebw3Q6i`dyaw-Rbn<;%lb61-5he*SC~I>p`4)BBZF%cAt{8!Xh}Zxn5n$vG zw7dR^@erbfQ6_xl6hO|rOwx$FH3gY52?Z-iV87Lx8#WLo1BlOXDZWEkrpo`Dtzzlh zT6U$eQoNJ(ZLfkaSC00Tz@D!cfl-3$# z%2JZPYCh>MK8yh!0tZ6y#Q-M41WEz}8c7fpOpcy^C`uqI#gQC zM+zAbBs>G75UC7@NNBHLf3%7t5q!i*4<@z7mQqYR-d7b-FW!N#^VaLao;P{gJi*4p3(q-Z; zYg~uR&TSKhLJHv#pX7=Yb=n9_ENhGD8 zkbuh}G>r!c1O^;Xo6Ab%8i=cbqYKm)1RK1p5FeUaO9;fr}N@=uhG^dt+IM%KpAEP z{wezFy-S_x)iW{* ztc+h<*FWqm19(dKR$I1&V3q7-Y?S0x!KK}R5mIk`2oAn*p|N0Sh706t`baG2H*EG{WLR zFb@!gXpjzh{zp1PZYGQdCA>u_|H?R7b;K|t5UK`FSAel53C%J2k0dx8c=-$81eAl| z&;8YhG%XXz!}kSut4N9wREPo#j-uvB9Y6vCGT1Cr(cO&9$C8G^ML<d$cm!9f!XcKaRo(bkd%W3)JyP&90lfRI?O zS3Yo(0tDDYtO3a~uzj#{_E#%_Kt44uyHq7;c}S@Y{bWZ`X{qXUFtD$+F@EnzFGAU! z^Y?9j7?}voZu8IN%5vEC?vr0V!_Tj)JxR|(dz6lC`BHjVhtW%RGr!u>>qvZSW3+GF z?4f#{C8xUpOrOc#_nrP&07!MWvRfUv6K<|`>9y!x^bUH6W!M#Xymm!jv33>WX`QN* zY>YXuMke^#E>kOduoL>+FL|hXe3R~#n$tZ^c_Nd-10>}QGtvkdKsDL@DCu=!$Z8#; z-kT9?R(>hGT2q~cgK(dA8}JNv9!DLKTQwJ5*)dAjvao?5-PRwh5c%l(hj{n6HR zJZ`_?!^;MOdgx4Yp-+3oe)Kkk>r9oqTAgutO=f|%iXE+HkJ;|P#J#F)aOYUsZtI9q zp~eNyTo#}4IRb#m@wN9{wZR0H#c#jitxAt4DPJr87yA+UV&PAwHtq$+zu-N>`#ARG zY#aagb5*NxiAMiN?EM+kdI;{*t>4us)YGDkUGr$H%MJprgrL@|MlYCs1(NLbude$} zr;?}opEJByhza&2+7HGWul9>S-5XdpoCN9i+xHt6Gn;O4cFd)+kl*Quj$;^>Y)oYz z>i)$$eniWgargz)&@077c~tyjJC#2+8>ic6( zYA2*4D`YyVQ_>aPm5Ff^vRN6BfybKPDP4gDK~BoU#FO_DLE;EuU`JhNPbP2Cb$7M8 zE6hvIx6@$Dr;OH-6EgZ2jn-?mfYyQ@xV3qjY(t}wB$ck#B>5WBdD5I!Ar0&%l*+nn zna9LGAmGD`G$Qr3L>Gp^9y!Ki^F}UeLoJLX4Pp)1rhh^3E<&^$X*?+CNOq9Xtdu05 zI|PJ&r-{IDR2WBv6n6Cr;=m~y2^w_X5|1%ZGU;Yxq@8MJ;Yn9qSQ(BatQfR5W=y#@yJ3Xc=yf{ye5`9;uSZoS3FLQV2;?SDZu!ckgjXy77)8yA7l~{ zx9Aj7P%Spxp=`d|X1_-W2GF>v20@Kc8RaQN%yqnj`p7BVp6G5yb^oU7EtP26QcC*SLoaPea&K!KhKJ z5jnUiYjOq~jukFcN()?Ag&5^Sq!f;k(cD87Go|F;1Xi9BCH&!U1-x+~QU*`jh$LGj~FIxLs9a+P`>2^Yf ziSOX1r~PqGa?yja_CbHG-yxqT(tqV|&C%!4f1l!&^t?T>bysK>O_w9oO|b#}l|I=( zT~S8+mG7hX_U&|Ett`F9A7-GI_|Mt$zWCru$nV>3e=@XGyJ^bcI_|~sWffy*T;G3N z5`klrttSfu!{2z1{n7yYcW|T5rr6esYpB-yjfJA-y(H6q8uen?5Sm;4MBkvZg?3zg z+Q-|g(tV&q^g2@>>gq9;V?v!Je zr3jlm3C{_~(Kfj9Mai@lv8 z?%g-pSSd~94S4C0Xk>p_r)=lqI{QPg`0f^8m)o~-G4d|c!$jY1mD3x$1T0^CpIX&n+oj5!n6dc}qe>*$uQ4n3Gpt85d zCQlThmbTm9kCO2RQ&`Mvh3~@J+bP*Ev(HrL29(tu`&iumZwO` zNb-`gCrZY`#i=L{PNXDOzjygL$pFn5f*k8ffcs7iMA{ zPKZQ_OHGt;tUE+*nLIBIod|blfv6>W_#CB{!VWFasZ$y=(4j0wSH9(#p*YP&>G1(?}l+3JZ0AJ52Ru*RoVpI%UG7B#QG}{7$LvVAfP!(_f=zXHav(zj!D(w z9v7camoU+3oH=yu6^r)^CZpUlnHI^*V-Bw1b2&svEbw@-MD+q z@5S{7&VOq2!hCx$X?7yep+=UHC}CpVmU7Y|0PN9H%7iQ?95&rzD%6w~HrotvL&9Np zf{0C*1cSU$EKB=UGZ8bUAYCVDU9CG{`cl{TB&&)Bf8&lToL5J}E2vx_LUXquS9vdK>zXzE7F z)w3)y$wU(_1%S5RARS#~Md8S}+<74D#u96Hs-e?kn;&##=A*f-+Z*{w)k{u6SG6BZ zF$=k_FlNdj9bt&BzsMdrZ3_*Xpt;$ssrqg##0+O$&>5znMVC2tN5u2aw73pKq3SKE zVt0gSNw9aEVhz8xiyOJDR!p&?TBCOT;a@_~sZ(pS^#Ex@6`V68}ZR%G>7- z+vJptxUC{C4%n>5fO_|vdzl>Si|x@ad8V7Sigl1se3 zz!KuBf9J?@q_zC0tGl9tCXR{q>`HVHW7Ia_JfkXtB9qykXPg~@9T(1EhT1X6z+7*K zozR_mfRWpBd1>~4yGDv33{Qb_>)gYAKqqN$qlN%4W%)12GY{TRvS(WDk@2PaV1@=< z5nO!YQQ4ETtwtLDSu~66=hGyNgXPkQwv4whqFKyV7ld}C)_o$_(iz;PqVYS`F*cvg zwx^d%&0EEmjoDGG5%?{#&f_jRMqLxdGQulNFeEGEVILI06oU3_0oo>cR`_|p8!G)8 zoaxWw>mi$CIiRdGY5K53oEgQ@=U@f>aOg`mG)`O_QI1e9EqIdXL!KZ^KW(kl^Cw^m z(FgsZq24icQ-hO56#@{Pd5n%83`imC9f5fti4O{mHyGjJ1GhdH_AR)f(TfFZ!Vgc+ z&xhm7LEGFWmu~Bwq2KX$dNy)6;Y-;-w99#uOYFgvdNu8l^>&ZBIuXxRf6_f4xUPc^ zvWp;$TSUb@2?z4$NRcsdekLIDX(3Jo=ed^cR|EE0TE*2NeGU%TkusVV`l3xwN!UuZ zPEYIA@_CrM4RUt>l`hZM;U-*G#s`&P{zk!jQhd}sJ&lULtNrm(UF!eS>|%{}a`|w0 zwiM$bPp#9Ao71g}oZJY?{z1~6E%$X*wn@4tx}%O>&cJ3*lor zXO!dVcO)EnYhok89DS!Plq`P^KczyV;~t>OjDe!zLbdg{SPL zAj>oDjB#0h&=6jPt}j#Hi0g<`6+?8CfssHWb8?*%Co|77#zb?bZ7FUY!%DG>89|HU zH==1(`@T(4?OWG15NUQT{1xnO~m{R(JGV#Hx z{3gksiaDVIA( zucr`Zl@TQ@#qj8Ow?sbp54=UOtPDo|gkbk&gBaTd?VwVseKI{T;(21cIndKw>pNIi|2_l*L$e@xGI zKYPI7)vZ;qWGR6?-fXYbJyE5VylfDwn!z7gD=mgW^788H4IO&QZ&+43W6;lGHD|t{ zg(5Vxvp|OMI@<)ARKh6X`uiQc#L0g{#FMj!V^kn~2pnTzFe&(Y zQFVT7&Nm`ktb>#JPw;(Feg7`HM-Q0aLcXE}b-cG&M>n;lnehisaDL8}MwbUMm*?g1 z>{6fNUKsIvb-SF$9W3DBLv(3&o{a1bXsTe!lLGY%HcmC7{R6`B1N>sBekop@p%f^8 z9VDpc7Hag%c4%j}>9)Fcb3Q@nAk;u)CHd(iDCZwe=FoS&!EVzV!;^f1$Wsev#GO~n z-Vhy-MUoo!woXUH z8>#>ayNd_s`InyeEypCF$t@AwDLg${F6enufkU`RAUqV|7}XKU<${!SVqd1AFqTeB zx7T<^TA1*pK!yM1R2hF%lZ!w9qds4*7lN+Q)p~6(3Z)tp%>Q+L2i3+E_GZRRw5;`7 z`%{05x0P5n6C(nERo)%9MZAPNvn%fLfZgOeg3EvE-y+xIqu0f#P1jy|74%DN{s{9oTRECErDQ|xrJ^MKs|6<$t=|-^j zbt8Ox>I}(^PKYt*z+EAbG zz1!h^x^u6~xls=DEU(KvRrj;ic4M{t2!AdtJenDQic#z1feNe&)CJ^is1Q~bF8I%5nds_YHPC6%CD=zqFl?}QhX-K_JMf``~WAnP-hTTpDt}5EBxCmHq64a+SoLD6A46NxK z(@h%LCaW|5;Y!XJ>_n!Ls*{XqL9$0(3><`xylY~+%T{?GIXM06it7f5((EA*m@Zx~ z410qvaa=Z}F5i{Ts1-VS1f*zO*q4ePI(mw~7pZ-X9n)3M{u<|i)n`ln4}tifyLm~S zmO9%)X(#l|qu9j>!r>QSd19STG0db}XZ#qZx3x}%u8^t-QD^(CbVi8kF^*5^m`5X7 zdBY!MWI&+|3H8H@=gn2vX5lEsjMRfP03?j0ah`uk@3sdKZE`e3S!uV)FdS>Gp{!fu zD2~C-YwIqT=OcIUj>BhH-}C=(J_fEwTW$X%^bVFQ7>rpY@`%XldgW;2G}llSU=KZU zbV@%yOl&F@exwcnS+pS}Blr3WymgTKCn3_of@_DP;v zU9Ll1=p`X6$cI&9FD%1jjTu zg{9`?eIC76mq=Znx>4!)Kf1F4c*!3P@7KE8J6^stt}Y>RCofmeFWpiz|4ypAsAhw3 zX7J)BZDZ#MTn?m_1TDpg^jeZi6K79|3aJn|`N31^iW#zU!V&$lIn}@JQqA8uv?&m;w6qcsbFL{8bMkD#=x02Av0ysh1j@7XVtEieWlV4T%83l05=5L(Ioe zoN`;|NRqgxp>M()1P9Lhmc_h)kqXR~bm;R5o%-hzHnoW|1~w98h9G|6P=z8C6|nW> zF)JM}{{d`1IPi$ zU~M6>6oz2f4Y$r=*5nf#52$gE)ytXO^(dk=G(CPwB@j01=CC^xLRqL^ zOaK$D%Mmvz9JJo&&-F(GGS}f{@1a)+At)KPvQ(nAF|Ht^>mXcw<+TkmfP z(hUgAwg}xQ#`}6>=Edi_TMiYd&Gkm(RjsZ!!-|AY6Vm-}HVumSHTbfF%08hp>>1cOS3b4Nhb~%5Hg`A)j0~@%VP0t5;+sT^2qkcFQ zHQF`WovGhoc~p=Bld!5Bj?4G2ir79{*sD8WXGTkJsLtdn*=OY74Z)}2eZCip z#b%(1FAnW5wjif(?^Wmb^0yG?Bw)%zx;}=^wfy-#L?!FYCpJR$E0AfK%|EXJdmM{f{Nis&xx@T%0bf3@9&j+H>rE0xbLpS{@^s-A2W0= z1~hr;e6XI}8L9~T40W)PHSWTxIE2Yg8m-re)?HLvB8xmkCx4%nqUuUvoCnZvCt$Uh z5=gt1MOvYb3Yuqyh8sJ-?R+^!Gecl?_>pP^kK?DrR%1M+;57}}sAVYxZ&!om$|bpnd1rOhS&yEg0Dk$OU`TmV8%sutc{}B)v!0?&D0*J zj51-S`|H%;bX1gSSJiDusN;^qOmR3x+z@X9WOPh`wrX?Qcvh0cw0LvRvOrF)23pfW zqrJk3uMu>#;An+@o}aTvgOhA>_OdAbNRM)A^r-kcwA$(4x487y4|`v)1JgMv*jZDG z_9Q%RJ(^{Ph_RZr6RJm;h;d@R=$u(61TZv~vP&xIEt1@}fsS#x8-Wq5gSa`_)PKcX zPBvtoafWp&(yg81A|_!&Fz+c!JI%E1YRcB~sEuMJAy0n^KdSBqP&M^l3ZLc2MVvdE zFjQPuGtWFYxJK-x!bqzFKO-FOoApxu_WRfRX2Hhar_lYXw|`=*;JL6AI-gjeUTQ?y z6i6yZH8XA@LXi_CpGWD#hd<2=6|5jS=p~S1Pe((`Q>y}*&##}84KwQlzeql@`l{?&TwmVC#}iCDcp~Q9H8HcAOVCgC#MgsFsyr!qB|k2H zkT({FS}cq>B(s_rn#8ci;T3nWhkGi-)m# zMK^UzBw?0fNHlF-M@s~6jbtEb;1@l{aYrJ{R1}FeVTlHAD_QN75FNEdOty5o9gV(D zAST>e3*mmRX*Sm2@?wIq5)7mC2wE#oe6?*qmvjgir|X?KPHO#?Z*h@R{jpZ>DWb`G z43P~;w^0V#qVF6L26ktmB`zz^YJ^JxEj6JD*_!K^I|fmhfI((tg`xI1(R5*z`B};Z zE-UY9hD+*hMMv2gi&>wttfId4p{7OT2GBPa+=3%eU-94GnS%ZfQvo;dDdA;fDkDB( zE;iVM)@XlJA_I#S)_CIywKiz)ENMaEWyLj@2+(Cj!z!5PB-Nv;5aM*^wMST)4ovgP zEHl~)JZn_$dUytQUmIrxN8uxAW}YK1KEW_ECbQsB0vG9FF2qLkho)2dkRnoL$p|hw zg}wJD1czZ%OQJlVd&|4yvAw1qmvV=bF$F&VFQh1@{O%hKc9m?LohH}KZ(i+8wFbDebqy!vaYaB6)~f9>QZcmG>$}h08?VF9KM+jt*`P=g z9`N7G{6VJR`guSl=R@abfnC$(9n@F&_4C1h=YUdRlo7{s`4X{EydMWt6`cN=^5sZO z6kS4^BDo<=&HNreYti%6E44R1Rp+CyTEUkoPKFuhrU`7f%zCJDSI(`1Sr!o5^RCySY*t{y9^7yp)z!_G{dz{y<5~NmwjvV~5|FB(+rQ zZF}!xCv7xR*66`P#=ViR-->uBV&X{JPw7dLjE@gY7Zg-jE^2!@bd74aq?U(`KT2~< zf8jZ4H&lEHhD5OAQn)m#()wkb$rwtR^%L@;WVzBPi|HlOTy&&hF^{$GLf~x|hyS}pbx>aFChpAK zk0^QO8YNwfpORB>m1iw8+4TkikEh+)VGMuir7M%o5_K^A8Gd{B#tLOslL@ZE?+jyi z{%y71zk2OS1>8T4wsLXEsz@O;xaX;@QBd-pc(IpKSAPZh4HF{==Wr6D^vmq6tNfxt z+_@Ui1Knfa_Oxq{NYl~Wr}b3z&Ez3b?8agP^A&=t%0}_?8(wjnS+q&;eXYCug{yF5 z(tjCFcY;#u8vEN?o@CtJHPz&g@;5bCcj~`~pKkbAx=}85?_5j@m2jRvf`s%CC>>aQ zI-p{bn;f})FU^EBES{5Gq=^qpyac<}DSh9n6B4IAHbFVl7SzSOn5dYjCU~K6rP!ZI z6D$eXYSEo81b7R4e?axB8(DK_LXo!-0;p*e&$s`uS#o=5#*F=lh4C)3&eMXTCX~ix z1lN4^^et6Z^!~h0OLV!9dQ8NXAf!SN`<17N zm&i3?%`H)ysd1CN!QVQNBEUso`y_p-uh{l7 zX-}1GZ!*x4#a{{~s-@4>+qZt+guGDx#kHg_uV&362(P1QzD|YcA#D) zT?ZBxMyj~E%36C*t_2+0#y+f7y;5AJE_*P&U(VQM8tSkc*(RTKzq6aFd?%{1iQM`$ zx$Pa|Gh*R;Zm^W@6GHh7UYhX!g^OCfD_YFjgkDgP{+`6p%%O#n%1#6CNAZxY^lNcbxX z-aUFuWNvtpd!bn&7GY%tQj^hr0pVM%rMgXC_;+pjc;(l~nMq%}5z0TKu6J)o()96Y zUHW+R>iQtB6+FJeWpGb7D$8;_P0A9xjn@!&;G%}k>!TW7V%0ObtckdJlXsVe^dMy6 zi4{$H-qE#GRil+7Rtuwl6#Rhe#cVyb@52LIN6xkGeiSiaH5I1xmN1?nnC-n*Zc3H! zFHOGnZ#Y{6VF^;M)8dj%Eib)bJXVH@{le9(opE(8E%eu1x|WUmNu7fw!Mhcy^zBf-_xZ3 zUIw2fiAcHI-mNMuIF1{>txP6x$yTO0^7wO+lt{ZMC=y|}Vq-n(%#wIi6XphYWs?=M zo?CKcE`E7$_GaN#)EUAwe?-P10%5xN3uQXU#f)iig{-q!$;!dRI_p=86#{2(8BwhB z(hEhaPLu3YYmZ)&w~bOZBNLjS^Ix6oU;CL%_}0DF1j+5NhS?tDhU;QE6~hJ?Nva1L zaCNjwXx1WJdsga9F+S1c}cevr9LIwvr}G@&cYgyMVUt+mO{kV12Q#@ z5ZorFXqaI}Hq!+sWeQJXb%DjqN_8zfU1SW5TX9CjbfzB+T8)jwvYz?(6wYHpDb2k| zIWy+Mf;277+rXw%kM;T5RIv-S*a)>IO`B-4Oxr<>j`U`_L348IQ2hav9g9Xo@|^YoB8PdBY+ zIJGdhj^L`{Z&UcXItI!)1#2+tpDUuw^|U zkRpIdBmTrq0K*1;JnbwABry?_QPC@@j$-AoS9s621 z6`FpjS@ZzZD17;NLqur(jO)=GooLw~W8tQXQY;b(%Bg{tk{S)0I=UwE);&|!>AW*1 zY3GdDL_p%d;fdnW8Jl2;rHE@~vGrE$l4g0@GdchZkLO9))^Md&5t&)iEzhnd9%_MA zVzK&)o367~N1sdpLU#Y78BMZ;$N%#Avb8%fzkT>-Zr0}v@^W~R)jSY05rnx%F6T+J z+o9cj7pJ@f#?Q-^o83O2 z{~xOUF}Sj>{o;k=q=Sxa+qP}nw(X9Sj&0kvZQHhuPTqY#|5NX&Q#EUS+O<_{&3Vmh z{Kl9a6$D3S78BT>l7ZqXc6Mnonk8{lv-F|H_$OMZo^_%p=X<06^t3Z$L#a%Lrha~c zMJzRwoiwElUsdF*x06oz)R|tMx`mbu*}S!vIm~gGbvtZi`a9d~?Vgh}{l31sGN2n2 zYvcYW^ibW%QU(@8M$Fx$*h*1mnK4F%5eCYnhvwPxa=fO zvoXL=>5}A$bkDz|pkb4h2o&wu92xHG0#$IW70CL9lNkTnEY9IB9;AKPdJb@%D_&Ar z4=eplo|vga&6F6wSIEkmCT|m9k1WW{ZmJ>Vpsl$d%8qc za)T^C9B#B2oHU2$q^l}gQ|{Fw?po7cPqJQBor@?v)6oR2Eg>RYIhTiJI;9XFDGOsL zOCFwB^sx~a{r=aO=+U1Eqe=FAFbD9jvZ?1zWi@6+*$L<0@#EVa>yWL);gm6OIw&9m0vlpmJwjw(H&J;9V=@b|1kUmb*x0wsWvq zA3M#7tuyQxbEOoASm8Y|titZl)$Q#niYb)R$;tOi_iC-j5URg*0EL?IrMuidI-N!H z&w}yeLc}Wsbe+}6>2=-?#>32W_`2vCH&fXn=pP?HpLYy7_xyRfI3o2Bw)e+J?d3t|zQ98k#daug%+tZS%Es+kejdh6neD zsFPa1tEzfz;7hpuC4EB)MTzacFz6#rlsWlw6mycKm?81(VQfrK`47Q;Y`z5tUv(}< zB*xqViHIa=xwZtElEg1r-kVUE96lm1R#@Pj2gM7~WNHz{dZoFG7*@QZTT)Pa0qB60 zM>5>lg;|#TDWj|c0TIE{K7b;LL{yKMR5YAWLX2{uO7c3l0&sBIqgZbmBZtFZ(*=7+ z__%qJSt?^5WmNx+cqQrqIcf1E3yX>>I1+RG7z)<>iupd}k$e(w)xB~76=VdPia8m& zedXGUrYzZTTV(X4I#YrwV`Rh`*`f;>(VnY9L2gDn$*gBmS~|Vz-J15eeEiv%^@N#clQFgun`Bc@buljRaUimX z;ao#v5T`koN?~nyQG{11dfM>RD^wQ?p*DoKaJCZi%pC5lpq@Fl#8Wb;iZi%HnP~H} z%jPA>rHyPgtZBy4Mv!bp#|+D@aG>Q=r%=W-h^)Af=@9O^qr^FZn$d8SB!wj52wSwp zA``oS19>8=La>z)*!7|*!{Z(*!oYHKAP@vm-of+ute}P9+$jnvDmJD1Q-;$_ZRsfn zpWamb{Ps`Fu>q=a1Dns?+2G#CDL^+!y%W`-X<4etE#-^0XR^auZ!7<~ZXFq6@0PoY zzPXtq*z}mnm{YIvGMc_`#5)U=PdqmSw}ckTDiwz8aBy0| z^sg_{z&(NItpoI*gyh*w6~(G7!lCwkPJ#DAy@PAobKcBtncWlGPItzHjnqw3+@1T* zx{XakxQB`l%`owP2;%eV%-DgF=Ka~qU6;l4qg^%l2bN29^Dcn11EB#%159B@`t*#2Cu(ID{eP`cl>PUrtY2C@^1Z_$EgcT)3|0VwIh6}bZO{S`kU=5)HUyj;#LoSZJ4 z+|CSrj(&%WIPk+c15S`>84{2Rdk^iF2E}X{^_Z)W(gy^J9}0$YACF2O2cbN^To}3v zUXsk|P-+IaGsh?1QcRAw2G762L}~RXXXiVNd2>avucRhzsX~W)Axop;v?KIc@l$-Y zPy}dB+2Quhe)u06nlADwzqx!RzP_;xK57_Qv|cW~x8F}E3(xO+N3X|DOYftN3Th1{ z9?!8qDVm-WBN=nZ=OmT$ICqlCCu!-_I~jUfT{=EKUarYI0pc@$09pk>i34wo{{KKI zyRi(fYT%OX^2hgKm}=M2#jwTU^7DaY&a?CXhf#@HSPzKApc0LSrR?ukmU;sdRfbmQ zgTKj!n%S~$DiX{w6c;rRqqZEcg)GOYI5A^0e*e|2bkU6>Q;v3Al>a zJ+MguYC!R%dJqhIa{h*nkg$})iOT*0&eUu1ZBo7!*;ZVFK2Lj-NAuIbz^nUSh%oC@ zOFjU$ur2bT)?&|CJReubhkVqB#Fv9aFNJZp4_o{?w``x4G9^nv9V=z3GuP(d0Nhva zJS7Ld9}XS%1WDn=!|4Zyi$a%wOd=*uBX#`$k3A8ho8j|&_=O^)-{I3-)RK}PftTy! zWL=MJ+T>ctav5Di) zH=W=rRo9UEm2(ZQ-T0aoXO(o&o})-slO=IS9L6Nzz|&^+FSDg(rsW3>KXDsEJb zEZ!q3!(DlbnC2`)l#*PkmO@?@q5XshvC?20ROcn7O#8^QvK*D;5Zf@JbGDcfT(pN; zcjz2%U6t+Eixq(eELRngsJIHR%3?>X8lW;k8sQTQ*#>Z@owrna?-rfcj}&1CG=uNd zF|n?7_G4clP9zUvSy|Vw?!n>?#$t4qlyFlmsK5|7MiWAVK^<`Pgt6YdZN(5mrzlcG z*PB#HztxAZu!Q2L7jp64BM{=X(WQl!RbP&=vxaq$4dWRya!EgF?MVDrj@@Vn@bs#V z3XRUqG=jJRt$3E?%~YfSP7&J6(xO!WJ$3<{Yq`779_l*;)!+9{Ja+H$7bh1fxf2EX zYFG=Sxy`2vb+s7HqMi-Q3Ru<{s~!i&aFr0sx+fAUA0xfT*l@amXVb#g(Eya|(xQ24 zT%&L;8qFfGP_#*an1;6FjhJJj!W9+8cG)X#7wQiLkQNGmBA`@goX}s_Fov92$$)$#O_VHU!^u(fML(dlmHC2dv-FRvc44`W`=<1}CEcZU40>_HL>VN}de$ zPIRZO(dE_VZgISLxN}&0@lPosSFO&lyD4Hrh977LPzfpsWf)*M5k5FM!3; zdV5OQ8W?=#Z-dvqdd5#NEB*7B+8;u$^>p;~J}~-v^e#uo08q9RTce`h#ZxD%n$mEx zrwn#m?)$qF|L)laZNl5My3eQT*T0fc?Ys{Df||CIf|r!|=?wDCybO(ig%IWT*%qm> z^cb60nc=NLo>+H^oQ9o1hbHhe(TQ_1g-N2B6?$RmOBU4wYa?>1LknQKLkp~f320mr zLsVF2t>51W4QU&f3Fb{C22W#r-2oKNG*2< zY=nb&j}~wxQ7C7&;mqtHM{7kf^8gDLKj$|Gho_z$({SyN;@edLN!*b7OKs29_0?{h z??CQaLo#-dJ!#mYw>wj++c?DEzWnBZ`Uj!YzkTRxP|PYv9pWZ+K|Mvx@z`8s zn=Ln&rMzGZmaA4YtPIvS%}wEzC?9R78{;kC@dhBs_xzjU%`j8?c~8!4eHx~*@Bpo| zHNNZcaU<*wS=A%1n83PgjWI{w7>xax;c&_DTbKV8+|w`XLdPzLnUVQUDVK#G+m)GI zkc-ge8Ik(s`5EVHwI*px&cB@FXVYWrZn?v`(g-ulLLpBgDzP1dH_D?iW16)(njE#p zT!pd2K0_zd+?bq;1##jgh zGAdCx*Yd)ZmYrm`S0YN)*Etj&)S)L9!&b5Gv8ayDtlZ6INurWuDC=EsX8t3wMZyhE zq!|7&zKQ?%RyrKoIGlLZwGn6{2gJkFxH;ZJ1)~3VCwh116e6ng=j`~je;B6E&f7yaqKUf4)qToWbz9^UX>*suOJ(V|C4K{eE6CBRvUCG@@2-Kf#A%o4`*}rURDz&l(Bv*D0VJ z4fOvd4?vx|$*{VS8Ug=lh9-vg{d3Jlxp{prePK0phseQl_Z0o=}}Z z2)@79gcG)rY4&9`BdoF5)y|>@#=I-uDy3)^QN?a4w#uro$hnm@R+|`_5Y7l}KJbD^ z*b6=xv1zXYTSdHVvQHZg|yrn9&&-5Efo`&jb$a&(wLw62*{g;5Z%A*`wZL!2H72;a=PFc@vl(#Wm`SN@M73k@|^+7844 zhFAnH3RE!3CK6xdwPTnhq^f0OslXivKqK5~ApSbU+K1dqehH5?5$8geELX3$Ae!8T&P9BgTwt{Yg^B#pXAw-? zqp3M!RS`Juk2fPplp~E9pC!_tB;lUu>E09(n?+Ij^SFD8&zoYF!vYmm+Dl3IC5;vQ z5T{zbA=D$bE|v6siezehKJ@SGElCp_Q1U8XD#_)$SW4oFr}rq@%cj)h=ANmrQjk0& zBm5mkWUPx~V8yt1T~*NBRE{iv-s6(Rl%}TL{9SHyLO0K8{yR(2CX|8MMGVhK^;jZ&%jGqH^r+3&s)ov@eT6IFe0-BWBrlk2eJ>O!L4{+;xvH#9AZRB3-iyGD*LLYYyXR=2S3(Glo=4)ykoW z5hb#Wq?u&zgDW6J|E?!IZIFeXH4^&|@dz@GmL%uQq|Id11`Ye=h%mw@SaaGDZe3%m z44Jv$teU>Z;Jn)I%rCzk2csuf4Qut`d9k=Rc%EBo7wpAr^xmg#E(Z=@N@Az{=F`LB zj?+?g z-jb)@jDuilo+?}#xUuvhpj)l?AUoC%+cTZUu3cheshI`whq6TDv1vNQ8)?RM@b ztV8`h+6(i)HwqPN?pB8la2(yczOCFI=^Aq@#9z8)-8=`m$;U1~oFIkS{g3-8P&==U zXxv_3ug^BSKCgpYypN7~v7L#z#r_436Q7&LggkB?6+EanrHEWZVVp398rVP*`h~t= zi+34c1R4jqG{u@Yefr17OpN>vGJ9`I>ZJzd8Slo~xQ=Xcmsm1Uc9FI{lY<4YHZ!zR zvkfH25a{$MeBHPSu`@n2jaq@}h&iqVmm~^q!{gc&p1=Nz$*&T^ybHCt{*vyMgz#~l?zqScFl;O!7nb?8JGkiYmGHW`Zk$1HT@uFiP=gtpn~?9!~+{AoJHsJmVUQnz`=EqK4W+ z!}M_^su&$u>(HRoId zC8h)yn|w?vs|-Ae?Ng0h|I?EoTC5GfMRfC{T|c==r|GV98;tau{gxlg@!>p_B{K)z z)Xb>fJ8m~>V$#kvp+L(Q>ur$@XM6@(R1VUtvfj&xZ-+9@>`n?pfLprW6><-KlKxhk zEgE8045o|5$FhPtZCl1=0wz&Oo!E{f?~0vym!XVPyOP3?Uu|9IbJM5nxcTj?Hf%OF z=UmS|0E?E2Y=fPArz zdPc(ws%}}_lucJ{{Ihr(Fa4oUW9(l*TY{WjxT^yGdJrYLg76h4sfZ@nmO&tlVw zPq`5o$HwLS4Gtf3NGxuSR{(L?w{@&n)mUS??8XMD5P`Os#S7Pp$Od{aM6|_!@8cDq zDVJAP_7GPHi+xQPu8Zv7bb&V;lZ4YVK1w0D?O%#3jriq!xSW5$$CcjBi@%GrPek<7 z?`muD5Yoq!(1+h5DQUl5iR@2_4&&mZ{rZseg=Yyv7)l+_tIbgf{b0i6;v`Uj4%eQV z?aj^2rP}Jo4gIUzL9DISalb!0+zxLNhb02L6;-k$LfmlEV1q@%mdmOCcJs6#?4Tg* za83Sd+A*zE(|NE;9=UjXe0nSPXJ#-2wM4Gc5o;_|f0CaAdl%8cgJ60rMmMun{>FI$ z`&DzpdT`@Jwl+>~zuRGtor3;pDIkgUO_vlO|7$rF{0`DD5mSGtHqRK+6lo|<<1^vm z^lD9{`#0WJmvijHpQVnmMAU3kGx?)vhg%1EHyaSUlJ)tS|1RiyN+QHO#Y>W!@M`NE zb%|t3&{)OoG6xh=3*%27zfBj!=O}!svlSI$%tAfo5dCaOl^oR~QfjK-3wjJ}ufgM) znxz_{o1vwm5WOW;x@szFOY%_Emslsb4Jn%|Zof}Si?3YO71n@FiBR;jEMTdBZv5ROCL*~&~95UmF>d0M^E9e-crPzSAu$$TC54pNb*M@8f=*clIoHrV(S_(4D*;M@`R*CY2}SOXcEF`_}r)zil7m3UkZCj)&_Z)c>uT*E``G&073t=3gxg z*#vYYSs_#7Ewo5u;!cQ4}2xaz>@?_ZcNnTfYNpL9@BqS;T^}Hse9nRa0c#>iC6XHJ=f;RV5B^~+Vc4~g*Op}J1zWG#3lDquv zvSo?KrA3{jLSL)s_51CrV>Xyu@LQ0UlMyB!V~%SCBs3YG^lW?x(E+ z_ze%sn#JEW$P-Mt$EkA2s<3*3;QqqmU-)ha$Sm8AZA!RK5^ODOgj}&${wIv|Nvcum zULgs!oiJ0--x6LSpmQ$OA+o8AKBrCNj9!-guc7o-G+crkD6eI+bt`cV=!4z5Eb2AO z*{oNt^sfI4?XE~@9bsp<$0;B@5q!{vdmup&i?g?Y?OYn^K(j`@Xhtlm7pcaMu&7U^ znM4N8l(beC@r3{79{1ZhJHq+Q*DR%5 zRRAp7ee+SQ%ZonS;ysi3u(j7jsm5^UODr~%Co$9288W21{Ov5%m>~VJKYChOlsbVd z)(F<9&s9RreLcXIreMtYV5R7PE@AqwnyKKcg$RV2I9PE<@TrM@*%I3rO8kn^PnSrk0hh3EqF>g^__By8C3r0Z zfs(vyjU`VK{kY+t^wYh*XY%q*j= zjK}n0LO*cRCciOb77w)B{@r$t_QfdejUVssN6kor4Yw=zr~8MP-#ewxcQXBm3Lom; z&>dqn8amXiDPBm+=;!=4t5p^4h-x!xALm`ItFLPi(}+a5^GtW`FO;?kv9>itAz$5>H`VZ1388tEIHKV5UU5soCk$Qtbs-Tr_5%Ds7Js)!aA zY-On4dYxo~tF+%vjLry!TMX$Z20|YH*A2-6)3$M%d_Lo?l&uMzwH0M=3&)>nlucaA zBHDbR9V0R#s4&9yY&o|o34^mr;CQrHHpPu1CeQ_znvTAUk$)m`sad`nVd*D6eM38O zKNh%US~c1-4@VqZlg5#Nt0JrxvUNCiKXhSy-dCoS-`EcBo1f;or`b z4L)+b*^aZUnaIO6{ec;ep2?vg)Ek%RF}yK#+Z|yuGy9 z10=jLdZ{x6|F<=GQ9P7c2sL$tW{vUD7hM~HbrMdCBhz0FyC*rR$XDGABXL!7AKWH)6uHVned=26gP^~;Q?pL&{bu+vapO*+WL{*-@3z8g; zJTk7aON>8Aa)4H`^y}5cT1l5OCLIoutbV`Bm}!rN4b`o=!5(R(gs22D>9(p!d0-z- z>eW{8R_LLRVVR?(CZ-`ce7*M2f1Un2*fPE2rO|7a&_?#z!%~1?TrwYI9WU zAVsma(fMV@uifDQzXhL=g<(dX%xI5L#pdsKesT?P08h~S;f(a^W>CxE)BpeD81^2O z7j%L8E%dtq+lSCD1Au4yV5?M`eMi6J-tT?qz&qAtV`d;ZH>F1hitp{;oG;A%2XX6@ZYqEhiFP8dhk^M7WNFJXM`wO6qC^=~0g@tgI3faPyD{GUQ=?h6 zpxrGCb8%YhN1aCTjH$iAP+_|T@l}_@w~dY{#hPgMMQ8OUw}=s@@g=}6eYK33N-%OZ!W=6B;&91 zL1ygND_TOmO*T8P4+I{?Ph`pzz68~W$I}ORJ0V9+%?zhKiGf+kNZchD4o?WM(*;mU zCZ#vR3#B1EbbS43dL8w>dqc9Fc1T&~`~UR!1Y`7v12#v|RAPSNeSY^|23Np`T=q|~ z8+B*S#2!bNWtyo8eV2MXF8Y31u}j$X`5xnu?n(N(8G`*@uW)43a-eBSmFrI z|1T4%u!~cLG$JWiuT%ynM?&E(m;L6U1HpNXW0ewgKq2=Zq6&Eblc-Wh?;Gb2^e#u8{q4?mZLgI6 zWLNz9vscN_U7n{w-H$-nWD(JIoO`~Ce__7>I$vi>?hj=Vd2n{<2XS1uk6Fzd>s5M4 zqq2gf2m@?nBR{|v10Y5$*y0X6kIU>GpPey!;z0*T^>RFH@%fB8XHK#Q2a&a3ujOR_ zR_B?w*0ZVQJ>7oTj9lWDjGGwwG_B5t>xP(mC+?5rU)|K|keQc^Y;#XEE*W&+^&Y=@ zzV=$#;c{|~RRwnY+IKk_*gqUcwDNABd7pIGSbjTj@n#)$Yp&fNNg8r?r%7Bpwe)uI zR#r;pNZ$S1=t)JU$CtjO-i!hm`QekLbyaQC!>F{)JVz3cjVE&oLUa19{I%L>tme%T zMOAhd8V`+)&t-mhS^U#+L%%_NfzrWbq>+OE+qYVrIXRNKFBr&uLJVWYZ#+SJq@XOC zB57;$SyGe0rpKzS(qAR*i9AVlMU-A-y}{;ei;M{|0_8j{R8y;1)J}GvUg(`6#q~d8 ziYmQGG7o~9Sb`bucZ3=&g?T?hv(JUmXn#_}|DMl|ekH9mYg|LhUV7(hn<`#V(=(QtKUL1oqgv;>M#8i!Cc-x1o-&K7ki?ms1JA$vz0d^Nm~~MvbyOp; z3LA&Ym-+rH_pcWRpl?JzSsMOa7Ik{Va1eFFRqe)@t}NJ&u-p(QI-nZHRwyWK3gwhn zOp%0|dFxeKRz25_B*fC0whYU#Nw$a6rgp+IR#`y_H+zRELbgLNy98OMh;QC003!9b zjhEZ$0fjm{)8@fhJriCxDzAsgmJec7%fap=Z>mRz7~;H2Xq~CUk-@YuL`g{rmWsPB z{|e6%8EZ6azb>?HRJlbJg>D7+l3{pyN=Ti3d-$4)a7L|T)H~VhrfOvKDz;Xvp7?4w zYveYRZdBrKBT7g3(^Bp`m5_+fc7IJZ;c`8OLnwt)nrIMuPNf`X!?$tKK8KZXHKGWh|n)9MTYiNQw zf-F$FAw-HTQ7pI*b8lG3Mc0UHbKZ5Dr-`x5k+>Xs;g*|ROO8cUEd;KtCeH4vCRl5# zwpZ^Z9F5{#K?4h6Z`X~d#l{6HIm$WsCiTL1Lb>f4{&)d2DFeAmZ62Us1H&Wk#byGHFl5w7$JS zM!hdqcE)s@y|uge9+NY6&7Upy8*Q}Bwjth}jvj{;-kkA9&HJ>f|8(YaY$rNuZ#=7i z#(pQe&u=d9#!Z_Va5ozk@mI*4OIl4oi}LLU##0x4J7aBB za%D%c>_umLQtf+t!fxH!$uvJ`bEM_5xYviyR_gz4a)nPrJ6D@|Bq*1G&@FUO0R1Sj zk56`p0F9bvt2*jai_=!sJ;kGBxn&L+&)18{OK83>6m}P+3Eg7IT2)j;Mq?)Ap3KFI zRIuAXWbx%`PXDPj$~!`2odvU4hT7HZL|a%hX><^7ch0kPHcWvRK*Zirmt!Y~DB;m5 zHk1LXrlb^hC1U6}x2k~W@mLIKU|b>(*9s=K^9;S=8Zt1`1{)uOLnzYl_CY^C;gNrp zjXobE0~;t*#cvM=d9$r7iICk~&5HR?R_>5u7snv1vD1l+`_-tue?5uR(QkkFFHO5oSRJBvPEm=+CcOtDzU! ziZJ3n7?+}%6BHtO^#{YfTHtIS;xC>5;M_P|Az68fea>j{cBo;CA^{$7#0oU?0MsR| zAr#~hio+JG4n{6( z%Vx*`K-mraZRz>9EG8!Y7U>T|N!$;t@lhp2eZ74}g{|6tE>jPLkSSjeRsD>?31|S^ zWQouI)TpWhnZkl~LxL)B3=oanKNOuWf?fw;CK_NZsIS}q7+5g{l$}q$w0ANNUv}uc zlhfVQ_ap$q?S4Mcy*HBm_4WB*IMBIJt5*{qD3(Hy8pI-b5cBK9BVbZ$hgY~EhU!9i zuXwTLZz}iS?3w--)?%|+?`a_MYet{$e|X2gfhS7|aMc?#33NnP06_Xv1;U%W_hByW zPA{3q%I{Ce;f`FuCek_)U;(K>vCV05pKbr|=c?P&9Ywr_{zKL3y}dTfE&T6T_!!iO z<1T6f2KZP4My4J@p^8F9LZy_D!K>B)8-xv+yLVqesqtco@5bJZ+h$t7PpYx1-bmk z-V#ju-cmU_lc}N)VWC;%zTSCS*Af>Z*Hmt~PR}93fp==baoN}Yzyf-%DqS~fwR96! zGK{%SiBNbcz4<8%$*I~*kK+56%|m_}&8fM>+_rDuu1ySfld;FFoc{vy=AXmDZxaul zD%6*X_MQ!yQ)u#SbD_DBZDK;o&hc5Zs1TpGFipj&4)|0ixC#vzF5GddZT8VK#K_N> zWa#&)CWTSlpY+4084FS)JNdKlNiiGd}oNPICo za0ntm#pTaH&z-?}=)hPZv~OySEE}TdE*~Y8f1+4Y?ci-Vp^4$WwLpTwVl=?P!g{Qg z4XX;!ke0UllqHMETW|uaY6%JgB(VKJU8Ycg=SG@UpFqG_Z>`SCO%|eDclW<^sR`cY zIs7+~&zpF?kGxg$XaqTxLSC;>xiSU?0HLr~ZR?*XI1CaOs9EY)yD(>|ZM=nRvZU=v;k`5jJ_BpA_Wwg&~a@&QRxJ=!&YQPGhi2R8Ik zjRB=dfZTkdfz7dJDUVJEE04D#qR2Ko@KPy5BDS7$_W9sZK+bAiCIr#OZHWsw5~&(A z&ni2!R+}Lk4)p=)@4mDp7%zvLyFm zyW3YJUnMJ+c28-aHhGU&34saBJqMRgKq6^_q>Vy_;*q4(*wI4JZnzgE7@?n`nqUzZ z`UX_Hga8WywYhGIOkf>BN+8463e;j5R0P^v^%`guBPzH z)JENtMl!>|)0??PUt^bvc4R6R{#YV`Uxf9$-b*W3OBLhQ(rdfi{hc1PcB zM>QQWjY$!`w;*E@)nVK1+-(zE$=6hD|2jT=x0a5b5mQiU2tL36ddG;=YV&5KQmx4G zrXNVL;taE7zWxn2CcJ1|EKDw!Lv>;6JUFZylqrsRmGY6NL6MXh5fSU3Ua z(>iamROjw8<5Ab)vw>J%d!Nf*ia&m8CK zRO=Q>j2R#U(G^!mx~Zg6^NDAAKeB3lg;^sFw+40*;mtU+JnOUH*B`?Cyp&Rj>9XnO z*~i_anarD8y@LLB1qxFs>>89KQW^yei7J{%qrGb!cmkDNr-{7pz=byZ!6F-wgGvf1E)}*c*!ybsVe=Tk?9Kw zSmVpgc1*|PM*AWs>8JrBU5i#jL$ht`$sw1Jc9&6eXFLJ4>qg~S_uVOTW><7RKekP! z>IPF0K|}ciwf@@e2Qi7oo4KQ%o4)R84c@z~UHp%>WE{y69w8YDv&GW8s&u{T;u*w{ zhms83O`(xi+p>vyG3m~pR*c3`=`kGny6WQ;;CAmK7Ox6SjM0p?81VrV=IWkVpb^1t z!R#QCGEk|NNfC_dHv68kELoN{wXBB%t_;J8Yr-3kKu^hFYZ~|t?xxn~jIyMOXqPn* zIZ{Nrdjf5IE+VvvL_kS0m3 zDMN>(z2rRw)RI81DI+jQe$YlZvOS`SB!V%(~z-^nE!o=uX|LAC-hbfdr6eK9QR#l`le#f$wje%9RXFljI2<+zW@mC7O9 zFK``X-D8(>t|N3k!;_h0^|d@ZrFeC899I&GF~>a1+ECWy-OoHrilODyxM+K>87k{n zz_Y$={pmt?s@LhifFk=)jaf&1weTQg=k}4jzwg_r$EiwnsqUo7$)4ss* zp)N<{b-)mJ+Z!#RsAh8@^L&H3x_^CBY@w~T3-9jInG}0eczN}>#Dw3zvZC>eBzSAN zUZXD@rBCS06T^~5eX_(9oAHuZ(AY?;BGG@^PhvYQOpoB#KCA?7bde(3pHGTRrtxf?P;_Qyv%+~C0u@q%lnQNu6{^uYv#CV-xxhIK4W9pX+L zEGAIoTrZx^@LV|Xwa}pqk>8Ru2*RU&EG}=t0zlMl4U0n8kSmFpz@GET^eK{2It$9o zGN=Rt3>KP0#IPL5Km~k+lJ1&bZ0iPiHS(nZ?iI9hspAu2^zXN;%Zp{{Nmc(k_}BNe zWhamAA3Y!U`|4hi{g{wWeO0`z%B;^8Ip=57<7xRUc%RQ^iWDW^$LWb{m}U1~&GgVt zfH!JFUENwRohxVn8=Y96*r=;Qr(L7{%S?m;_jxFq$$c^NEC92#-3ih;Lj`LqQvuCrcApB)1y-fDz7%WpV$HG?9Z*Kq8o6mY1Lv;7Pe;Rcq@Xl zt`aqAon>*B|85DrMAzN(3PE+HsJ^QbPZ!4j6sBDJXF!*JRp;g|cv zemxlYpHJh85<*3diJ2oO?*UJn$kRi=&4tzo_?%=GZy0#|WlV(&n@@Biw(d8Xa@}%V zfsnt0G8=XK;>#e@--;sjO0_Le@)c);@?(kx3P^v-z!L~o<$@6jz65|pQ5XdcrYp|F zeG0=q<5ttq;x2ycG#0A7vazi8&?kccul}AF0&_SMB%mCG1|K@zaAUnwK{_8$ENISm zt_V-cfsc#zS(R7>-5QK>4*Z06yheuY#PXs_e;RsP`KJ0(cS=&z`O~Wt2pB(3>U$F~ zb%W2Xp_8@C%daYl?r-!C=cGJSztQzpYf6eDF)KLk%I|<*@<67Tu`mvP%{o^x^vF1O zv8gVUaEYTf(JP-aq-~C#9M-&{!N5NRSTG>B!<^ z3#Oq1nI>PdS)dr`7zNbR<6@S#5VqQFFaLEhx&Ox3J*}#A_w#b7E}?VOVOdCJkXZ7A z!Gr5W#1KCzlgovZKCJ#5#l+V0-&bhxsR zote+y?eJPXk<6XUZr{GQuFqGEn5xeAbGg9Ij;{Z2=a^~ddyMrP`gQUk$UAxV=VLJl zM*7k4{d}1_O20?H{VSb%CEEK~RJVWOvkSb-RNv6e&8O|n$kV0b%y#`E)^5`=i;Rk- zku$MIWkz3g-rl|-$3~1=_!vQ3T_ab)Cqgbv^RfS7hnA#cI5P=4d$nl z%ZD>@`W$YSAHNZry++%&J{D!#rGBsFC_`f5Y0bgyWG-J&Vy{P7c-UBb{kKUAImeCD zy3%|23|c1mz698TCbv3SR@*0GR_7;SPR}P}Qotu+^y97;igo%*p8=)!SSF@og2{W? zmKGOBYw~dnxwj!^er%hoT~6w_wfny|A&G;ORbQ(k^PJ25!7xa-h(j|DZK_n97X(#h zE@C%NoAY8ua<}OlsI_tA_G`1L&F5ozbxc(G*av*isvxoAja()82L95LT(41kg|C%T*8Jya%|HYQ>G zaRj1a!7+%^adtcbrKl1whw?E~D;LuVQI0TGO#p30)59_;r56a_6Uk-FZ4Xi1hnmN%LyfHjpFaGU|l%gWErpf z9&Vk9@rHFb^v4N6KJz-^xBk9V&FZm1Wtlt;2#(-6m$w8#bLIDMpJK+4sj3xTJ{`g< zRy3hvt&f~Ew9QQD2vjRux-J|nn0>1yy4kmjD&4I6=5{QS1usRUA*&>9R4_1&eWwIp z_zto)ERCyAmVg|{{0phRa^#6sCyszGvHDM*Dc6vcvc8{H-21fYNCfWU%1`3zA$OqL1I z27HI?MQ)WO>#?QU1sDvYA!tYp?{p@`qgnctl!U9AFg~c~I_D6kL{J;)bPq!>Z?6~n zYj-?({B7^EwH0^7hThb=?~fM>-KqY6Bwb^0q+QdF?PO!ywl^Ewwl|#En`C1r8*^jZ zwl=nH+Y@~Ad~enHF*P+cH8uA+eY!7nFN>55dbhq@O}>;JLWH(Td8O?c@IC)Jyyz-r z8|v?3!JhtVyZdNCd?s+tBFADfZHlUGZcTc6XbJ&7;}Cc2b$M9aCe95ti!e+}q-FKS}!8@=k{dz*_dunz&k9wr_eG3zkSlo5iv=U=eZ=V(2YZ;TZZOb0m_%H=i}qx>4-P zmwFpAQA?ZrjMHGjkrR_y%V}Z1(cOGAb9s0cbI)M>U|XZbyd8o5dT3r7(t%U3NksH* zJQ=9qg>#iG!}%#wR3zsyZqVL=qAU~n${bkSN) z=i(S0rZVhW96pcM>(3zlm2biPN>?cr48d&RVf~4uxy;=JD`3Heyz+10)Hx5eH|?t7 z>KRJ~RNE%kO+Q_b%6iivOAT7}jZ@ur-Cip=p3wND`F$bc-K3Ats>4V==>sBO5bEB;jBA#Q)HOP7v#2wWFs}Drgb9Dnh|u@@Dy7 zohODtdjubla19d@I+%IOoq*@Ol<77iGmlw7gS=zx}#XyoUc6Rt8??N){0j}?q<88P2#twCh~-|`<;!jWR|UR zeNUF7gs+*!d=X)w<>8Z5oCq)G;k01)oBiXH2q-Q1xb^Qc?7FS1K<{y?aK7?B{Opo< zowcApX3^a4T&4R)yRRG^Z^J2Mmy7s`&Fg1+%*?L1g3{z+P40PmFj{ue)1}l>q@0Ce zvGbeoQH@qOc<#ley1B&rF>Gl1nkp|t`bSDb2{Yq6psN~#&DMYPFZLmGGnuZ%W#f=!Y#$`3WP@Pw{EIuAGMEezdLNeEPbQt_6{6$^4?OQWN!!o@~F z<2#cW7bl!+9A*SrD+UFaMhMV&VmKBYKeDa4;uW`fE+OG9d^m{5+~9efaJa74cSFnb zv+D)>P8i&mY2;&H{(pHZ3e%ZGp3gM+yCbj_18tnr7|AIhLL+B(kcQChf8{NB-p6c1 z@k^_K&##;p&wd{T@!?H6wT5_K20nExv?Gv53p@4^Ij9$EO?$|X>4URk_CU8t_sCGQ zVBof;coA^CE#ktpA)^)Km6Qw4O$>+C5G#)h@vjioHGpn6P44ebwI;A zlGFnD5caSHsWoyHavRMn=4()ya;ude$op@s$;>ggCM!_s`fsaNa=##ApV{$Oa54GgP!{kO7tRd98{DF3`eLl4fH@p6I7qO|B2a^2(pkj3NbvCVs9O5jf!>qUt*_u;!$h(835KKLPDS+4T z+q2o5;I6-3XhcH#93|jM=cUYQq}XNEBGie)Do5{g>MSv0ZX93y?yYZ#`fF59;l@` zV+}me>-{jKNT38WOj5fNhlF|AQA>mmNLib^=+n^FAAd3iucsMOL?yG`0IbF_*xX8*0X@s?IikZ{a!8ccNA> zNQ6ctv}E=0-qQa;H@sgn70b=>wEvJ1xAG~ zm?lYqO-GD?V+#cBX6Djzg#Y{(2uRw*ZR5UN!^kZZC&v7pR*Pe0rtG@A1${h{1zeJ# zpZ?W$^P!h~k7FQr-f>Lf7SVrM-S`z07FFXtjCXJlSQr92EPQ{o5Xf{?eyz8@4`6S`H(x6sh*yfAnvH zs!-vwthRJMdrHW$~=B}q&P(VI3U0(S)F8- zH)(@IvgWii^j?=(Fc3l%hM04 zJS#j?()jOSwKRggoS41$WQ}V81}(ZoN1i)%^3!4D-%WLDN~X5KP-`w*vS8?F(U?0w z%}d4Q;bl7TedzNZlBU-`+h2!}-#{y_O?U29OzB3ptV86oI)rJ~Mcdz|s@x5I;k9z)U zbp;wGz2PNvd_3&v9zNMlE%W*@&5K~OH!adaQ~c&0vDGXAP1XE!dV+x2?K839zONay zCfV(CFd`K(DOV$;@9Al_d(}01uJd-Kxq!f6>Jo*ZL{#R9HQ4TKvU}wlqyKfgipVVF zHbW9j+tB0hdQbv;3?upU`C#l-@v$prc_F@(G9pIY{FhkY$ot&#q~XJ27Z`^_vJu4{ zweDr~<#yT>M%J*-m1^ksX*wZ;W0}ru=;L>~n;in&=BF)q+f`_xm^RR9cbV_QFfx?B zj!do(lk7B`AF-kn!nPw$7i!b^R>*j*u;5-jSKJn{T~+tHUK5pFl~F#^l+RW+J?+xi z&i`w)xB2t3g8&Ytzp^!^C_mCxKVM&9xr5JlUp#LI!9Z3q9zAbU*M@Fn+-CXYyO3Z@ z!-TsI10pcpXoX){e}pbhK8MjFc@(*tF-Zn)M@I2xa3Lq##l_{&Y`=m3Z{|~%3@69O zpE~$@VKvP}DMeg9qRZ3JHmY(X)XP{`0lXAAg3Gpm5adj5KCbB`gl1ncECoqxwo43L z({R!&6`U!@;xw23x69p<6yWaJZ~C?ZiI?^o<*b$50iT-2??C+g?tgdc%)YgoBMQIV zHL>JwVf|AL#S(K^N5;1DW-&kPz9LqCgf$Z~)ZzeDTIzKD=)X8dY#z&e5yL@X=NNK% z8JEml=WNN{zgWsQ^MYz*A0S*RgwN~iYFmtG2%_r(;jG5oBI%Aq`+)txo-Ep5MxsWGfo_Q7<8^CUz zWXYiX^!c^a6~8#mdKTf%{OI)l9j!k>(-6zhH{Q{+YW=l=F_euZJwP_wC+!KLLmyG2z+lrF%{ARt8 zae3AF7J_>i?HTYo2AgIEQ<#VLk^xgaI*5_qj^S0_bfMD2FWtqpkL~nLL>)Y|6nw>s zI35X+)Q`fNwCWnV;>a&CS7OTplpG;6HTHz-ayYyan=I&xVp%f7N&L0nGie|di~im<#^sk;qH@yJ1rf9UZX)bHw&<+%mYx_@S;_>YuxK`T~J zq}y((7BCQfM9}rgGhtZs8d0|7mj^MWR`gBRP7LnKGu)*#rh&$|v{khX%Fls~pvBqF^d>2Z;2FB*W;pc-lGjBqgBH~)iDZ^7CC*ten<}jP>&yf)O z-*_#+fI8c z(pFLG``;(8^N_5oIHBwpj*8MB5Ax4HuYPt@G|=5oR9DhtXYr)0GRo>C<8hJcqE8;D z)2zqep1zcjEpye8;p4RE$p3;!OU-AFvVUV2zVxPU^41yyGqlH3lVho?BogzxxCbZ$u-To*NTY5G-D2X$|?h!AVb`0C%u2eP80;Z zbwj232*<0wiekzn#nGW^=26)G|5XY@`*7|?8aNaFhR<3p6!0xsR17%~mMKtp(XiK8 z%S?$X$*?m8g}blk=g6p$T_9+Ix?tB*%k3B~qG&&FrKWGUoIAf3I(`)^rYP`$7~cR1 za?%`2Fm$PmfEXwY6_ipHv{$kqz4*ORno>6Iq%-DIbc7SYSxYbkt9ZY1L11p+{?M&& zFN6{p>UV``PWjggD2@1YQ<>kxxw7;invB*FQmjz9mzNwdnho-|=%ulNESA&^Vu7d| zEvb66at}M=w!glRN73EUh>FMJ*s71o2Ji1o(=Q=YXShfE(|NtPX+!N+pU>NpZK=1` z%CLJxj~?ImwEENNUT3CgmB>Bg za;-rr$G47?U8wI(sv`v7PG9VET}&l2f3^oG!m@l%%43mpd9ZVwq7VVY^3}FIIMPQy z-2w*JAKKll%qgZ|nd7&1kS&)rX_fEvWY2Y&_dby%z=k+v_16Wq{T}!W==wg}oqcrR z(i5fM_okO+ydk$ncs|{EBR}HO`T4xoXY{3kCCbO1=mC@_NoO{{w@_mg+7Eqdq@Os* zo_(?#*Q3F|6;8-x!(+wfzFMamv#I}u8baY;?bzEz?HoR^Z5L@sSdZC!4?*!^(D`*c zcd3*ZQI)z^6v3f~dD7dzSwp{EBl7tG96I-4u=*rEKTD2^GVxt2e(A%Uw;un_)%dzH zQAp@j<}Wf_{2A)U8?4gS2wyc0U$yl}yFiVo!_w6YqU0cc>4AkG??CHj2e2xPkAnZkd^IA?TaEz{C~O!(`wSSQisiR5QW z73Wxv&1p(a3*VA1iWjFW|Io90;Ub^Ch0)MjQhd0%oHOpxV|4`9mD>VevV3E8i zE!cB9lrUL51EqbhR?lw}5{I!A5CmoDS{_7DFU-F<1us)3Nq&=LF(q&!i3q#Kx4tr(z=*o_>6@jEbGG^mW8Fy1n4awEa~yDF1eT>0YnovMfmT+Ix$)RSno-i<{^W5~dV zTI96b#=rfC55U}Q+$I?ivj%~u4~0ed58Qds2v?2-?g9t~O};Z@b2Ofuyn)W=sNd!H zWcWCcDI(OW$#GaMWWvAi84K=jzw^?qSB#|9R^eYVU(&4=lA z2HGcn6a@i{OnlE>U)B2v16Yu^!!xjtIBRb;)t3QWR7U&i+cP z)`xLf^%$j(t`JS#h~d15y?<0t%Cy&p5wu{=&*(R+GuwN=t!|Hrkv(@5{}Wlx0nKru zi4%G0^xkKYLv@tmSA87_DPj?>(DxqyX^ksl^s=k+IMy{5!U0leNS-CS&Qx4)R|ZFCb&uC`?x*j$J=UyPQIaZsg3 z^PkSBftjluEFL(rU&Zm;+QWd23_z&!hG@=~hEH_L(3+@op4FsAa27Z|&v+K!ZBB-S z@zCR3HNguqZG4#>Zjs;+`)PLp+V4KL>8+0aVfsMH*p_?bEcw~Cd%6GmRALb9Q#WU= zEj{{D8oOG^-oL3k#^k8jSpgb#N;=q_5E!|aWo!Ib5Q!OgkZ&nx_`$;U8Wf4x6}Jr3n~U*+uVCS{=1D^qde@EDS}Vb_vlb=wyJhyO`VVFw7k9dD#BvuRq!@6aFSL7 zk{gLDk~Fm$wYxNc-mwONcDZm0=4(VWfj-FtN8GiMLHkEr2UI^|wLjziOHkBRlay8c zEBlZXQFfzVe>5$2NQWN7^0kJ8(Kz9y$CtZx4mY0rf`L7cgN6I`x^Qck$d9~boSljO zO&&eeUzmkb1N{LfXa8C&tS~EPA3A&kfzK5+MzPeIuV)`cJVsSn-JLH(9}}~au|Y+5 zH*w!a_`Mq5ZqGrA01I*#)&^JL?EbT(RjhD_7i8hI&~&V2$`Om;=S{&&*%^pPcKooo z?b+6C1uDAQw(plTJLBiS;Wk19sMH{?-f;b|ce+->@lygi>+<9!$Z z!k;8Hp=#S4><-EZcNYvFZDN(lUN0CX8o9V+~?zZ z7p_!m$NI;E{k6GF@1JedD$wKlL_m{MBkYm?^%i4+2tJ4{=0|77U4PH9lw;kY8VwwW*zmFpOpe=f)~ld9F803a;yt%qeTvaZz;v1@geN4iipl!-VU|8h>V6=uP$;vj zqCa{XIna3?NS>FZbHe0&AUl5iY* zu4(fMx;(C8K!MSrwDb1+;lyMJ5N9(`ZT+t3<_0O7ebly%v5#h_^qKUnqr9 zE-HdDU?po}&J6N>Dji5%9h4pV28sJgvGMaF@^Y;$zW;bezgnQ$^5bMuSEKr)Y3%(S|4?Wth5iW3??wsL@*# z*B6zsg2pk|re^_2@VU5EOF!9m_|y68sG685HVS1|J&}g;=l9;lshyGYoeK{N=(AKA z-;~FSPr8{gj1O`bpG^8f#bU(MrPbNM=h9fgE01#-$>5B0ionEdm>PpkI}n~idEPqX zycCe7<^#NKmDI~QoYAXL36(Y#&lP^zf>m+#>M&_LETfK= z()wR=j-3Xcco#SVF3-`*JlA=B8yFtJQe}|AuG#$AqzUnV)HjM*MK*=n&5FNyq zgp_HrU6Kb2W&S)?HFP!3EzGfWZL*a0qt2acK279y)M0nC<^=_gNBG6gD~Y@bNuV6!Ne z8N%A1*n%Oe%OJotq!BQsjVdwth6&g%?lTF3N)k+!+*&V9)`PiQemf+}W(C;)jpSDi zv%XLx*bK}9vm-Lh47M#mW&F|f=$||`RKooUG55dq1bXp%7PQ=hW`!Tckn>`u4sf%P zrw@#3SHCK1S7(UfA9FF8feVQ6>dh!)OLv@XW}2i3v}7oBCQxu^@By{lGyAwOGllhW z_#Ee|c!pirRV_RTx+UnQ7cTaAy=z>3y^#=8!C*_OM;t*G2qIGoDLihM(iB4ukxOhm z%JpERSDv@N2nMI9(2~Xk+SYBB-V)VXB;%HUm-C^%Gezqs?)65bX~L}xHcIjT$kWAjH*trAs7-;tw0Ca( zJEzVl>fw-sLV_SF;{eoCDkAl|Rh=c3@-YRmocV96lU`XWC{^aE_vw8H(9Ozyg*p$<-wr$|tS(Vak~sBL z^&()WH-(6&O&noT82yfpQo}|uT_xw2Zd3Fgi{&EW|Ht-HEHD{$dI{r#Jg~9d?#xf! zc0BbWI77b-l$t8F=b?8JFsljusA}dnxhX|tuLLCH7ZAg9`ZFdOm|qZ$w0$CAkF?pw+pjO#4odRgE86W1;!^Hz&yAl&oR8ciVD^7AMl@==J;^tsOZ&3m00bBtdB5!Vm?|GeB`c@Lm73beWbXlI_S6KGlwWzlY zR9dMv6#q5DiNoh3fp5GK8q*5((rWfTMq=ngbsJoU(}0An_c9cEfbtHrOR^AtM#n~5 zrfNQ8VjM7nI0!{@6m|MKno5cGii8pj86Gld-I{e|i2PFc0Et1^6;xl+@Efqa8QKsg zc;4&(9qX~VU2&ycMk2+>)YNHL*q>h|e@`Cq!EH&O+(-kCb?Z!u1xeeedhZjESrH-R z-mw_A^1Qf~115&^xaVa1}6u4F;9~D7z0?)W) zDzuIznr<|(jyodwIu4~GD?hRLu+DGpW`)iFN&96^RRkK`S0$Vn8l*@VH$K>#*;9Qo zh^9!UM813)GOpkYDyq5)b)@h*} z8-atheyy*(kUVEIdF(RarLKI9IS>8V3p@T>b6I`-7u6`IkOJuT$ITxVQS)|2N zV{W)QW_LJMMN(^EdRoK?sP*u&>)WOD5%UU?zg~{|MH8-43N7}7RO>rAeBKY%Y2KM} z4nI@j(pQu4R^0)1K+&%o5Z39p4jUR9O+tvr&(2!!@oIK8F>W`u;0*SchcnEe|i9DK$qREA9$-$&A%P_eWiR@}zskuV;K(0n1IRX6q-R_DE;{M6pHU_{o}- z?P*B*x3Bq^SxsWO%L0zqqvz&-0(S4Nf(_wS%u~8miJ?Yq``j|*3U#2G$U$J!yTbX} zxx;*DXiZUio4o=1UNJuGribfYikdJo;48K0KqX zulmRdnW602wDWvAm6(tqs5M!I6#=%QM623*4TSkEm{|;9Q~_LY{agz0snG3 zsWh_v@-QEEABNh&;>VZH{|THZp4c5|rd=4%^#5Wn?1>Xek24_jkn#mkkI@_579UWA z4G3@!a4-7D*swSs8X5T=HIlyfk9B#RNg0?bb8W`>mTuE*4cT0m=lu0J1XHvpHq>wR zIK2%c5$gV|k^}mGt%txa=bK2D8XtnDr=cDjAg>mnmTO&$GyYN;1NIN_TwK7Z2{$ykNvA4UYGy@Yh(D_I%dh{c4L$R(F~-=C9>F!iQ8IFMPHjnCfn zaB{I%)$+##Xc=BkW-^q_N{xI>?Ro0a1jltaBU=HUI&&Bx3fG#8VXT1ufc_n#x?fw2 zM{El=li#eUUU&JbZmCQ7NG=-p4m?Q`l2HggO&kkNyy-6H7&Ed1--|whQc_rreFz)d zDIe--cRlLak#WOs-i@*QuH@{MWP{EB^=_`92IuRpWQ@k~y`5HZSa`l^8Aqh2whMc{&e_CrI11dX-mNX0~!Dw z6S%B5z6n07nAYzdbQPP&{@1Vco?W!Pnah~)$o!%Yr%%_zr^Pd$ZK-vi+xOM8OaFJ? zYkIEo)Eaf>p7c|a)lZRo66OsdqmPG%vt)v(?e}F6&?5-a1>7!PXWxkn=}v9AEO~dB z(b8kc=vPgBi16dj(>ktjbiMCC`8H>LYrCLpa9OwZgpwMp{5L?yErXvhet23qRP&lM zOBmXbOL)0Wz8I@&@^(P`C@Fg`4S#~5r6)&O_~H5`! zNJuUT9l2L4nxuI~%5}?;r`SKZV_ai2j!VqI{kzG}S?5NsR9t*KjG~BwvOY_=>2H%w zqtT+!5|aM;bzRDPy7JKQ8c`@`ct85(VZSz>c;%F1v;H9Ou)afx|>>-1pvh_0|VedMAsnjCd7*$DhUOHk_*VKt_lY!&`%eyKcJInFJ6JgE85QLQ>OR zif1jE{AVdaUNXEECBzNnP3*Nkfvlv0%<`JxGEwPQjhhp7{eRIGw9Pf(wG#E4;(F9^ znf%_I7k%GvwUGwD$hr_|$YNHgl`uGIRPK2&S@>ISwyq+b7UJZxpkO_Q#MyY|GcwWs zaD^Q@(rBxI#c(qviYm$ro942?l_+*M*5)O-^d$#tKDP=yovKGfTLs!9D1aY^N zr48w~W*D}Atr}&NTZ9e@bxA^JTCjO7e3kL2WXUj4qiml#N&?3o+~|)I7_% z&Q%ilrCXZFU(sHJZ6JU5CVY57fMbgy!-Wq25p^+rueQvTjTT~aZSi1Unsq(vuLEQ( z&6ZhLc?8odkfH_*IOt1JEU%9`dFv|FGB_oha4oKBkuPFAkIilTV$(f0=xstsV(Yag zZoN>(EQ1{x9+SGn7f@ zXaONA({__7Et8}28(Or%ljW)UnYn3Mr=YlcX*)jSC7t=alL0w@y;bpx`&!u7Y-`u8 zM#%NsU#-uMw_V$0y%LJ(aZrDK(u*v}T@ho83k!%QHY z1nH8=q!rkEl*iZp*=37ww&u2NKjgYsw5`iHA@8`-f@wzt1G#TzoBjuPwaEzQY4v+U zOKbMUl#jrpsMp}tmrn75bA;v;ow_TU?VT>QJL5`&y#w{iRGiYa#0TbJ=2qP#Lq^qC zXT+gZNfb2Nt$8x{1hGd1VI7%op`aQwRs;Dl2HO(*cp}Ty2r%gOJ*94MgOmxYi+wF; z{er}YT%ek}i=wLdcx=P3tM8+yX~I|>1^WB$;Kt~Lyl{<$cG;{iQD9`*V zx>xX+Gl>fuNJ^|MDKI^_FRCmF*0)fC53`2bkueltZ8W1^`ezJF$49<(>nTl_fYmNw zsULR53`+Wtk4IGJ3QtUs$o2niL=f6l9+@CBmnAZ!v>IY9H3q9WZNP-JS?NgSmEzHV z&T5aDz8h_lYvU?Qss1BJZaxfT^iq}t6joLQlYk=h0G}ktxbK6=l;C?6{kY*zb<{hw zi>gm2(5`6SfJ-Eq1Tki}0@ZI!GakHUu6d zk4p;~czc#PT1UjSTLm&zM;W@^c>i+ml0Cm(NJfGQMpG~#3g-l)*tgD0JO2E~^i+9A zb}f5VhTSMdbx!)W#Ha>-Ost8kJ#7=vbQ>`92TZOgbZi;otpY!?ds)hw0$$}my2zX; zYwEGN8DD=Y*qv%)GFa&FCnNH1@9`Du?$m`!H<+ppA)G{OA9=DoL0haUlV%*k!uQH5 zN0B4F181@5YX@^9hTgUZaO!x~@L+W^$yKlVR#Yy$xc~lQ2)CP8OoYZr|99s}fogRB zNh~6R6>snN2+@R_Z*`%Nd;6?8L4m!>;CnxJ@uKNUxIwfOI8c3jrNG7w&GQ_Q^(K#9 z8}hp^9BV%|z@Cyd9-X2rxbLi`OX-c)LB0+|OBt$HKwi|xmNz-%%I%$yOIrV}&$5V|+yl(~tJGC5$cck=J7{=V;@Rki3peyG4p=cDF^ z3O|JG^v}v4zDXsA`G=bdRX;G7<3(XKD;ZU|9a*0^yLFL8HE(N5dUymJho(&oQRQoR+4dkuN#V_Xx}6+L=V}bVw-_m22}TKRp%ZI4wA}ej zTn}x&@cBtJ>og+L$W}PLqMR{>ZN*b*QMJUy%XJ3SgBPfEscqet@UvTfsEgSBllm-~ zGhx1YVmTNL?j&>^=@sTByz994H;`L%;$UpHk)!IzS@j+ty(cjXf`#PJzsuLs#3_E+ zGX1F-q7_Zg+2~^w!gaRBj}`H+KzqHJW;aDu!$fS(5~64Bkxkfp0S}Nefg?_lXwA#_ zE5hthZS^DZ)!8q0DQWQ_0aLOWMPT8$UdUWB725AEBphHkO*=A&5qk|SC5|EdBsOe$ zOdk4T_vu;oO+>z3`cj_lSp02$={APC%nlAUX%6OO7A%xSd)*A})z-_-8|0@)_42($ zHHk$PdF*(I;`Ox#jz;AD?8Z}w+MQb%%=t_;q<@|!mga=y=)c`*(iMGPzf@VKm-DqI z!x6=E0ExZ9G033dkdZiMOyd%pf`3UWG}7rdS;AG+pARitGPlc$9c8ZlnEgP@Ao#U)Ec_(_T;tfBmU63LLOCuc`EJTsr}Cu2NUSm0S5 zR=Hp_+k7wXwi_ik1e`sWa*hP?E)3r6|F^1$VvRQ(j9!ey?f)+2J`(r8g^SP&tumvjTxn@#PDQqRPIChqgU^o zB5l9qj4(%6*{`-b2Wd3P zL{cVi*yN7xk!V-#j43t-J`YR+c4tf7RNV5o^nv8^ZFHu+ ztha$yZFTW71&}GG^+`NO_@IUqafz3?4ooxztl@5=e5~36irl;4)XCt|Xa_Qut&0_s zy>M4%3XNu<05d;qb6s5t9Dc*gPEHcQ;P-=Xgk}YxRqPR?bti*Cj|GKdOrl@eY^u_U z*T}B@I@P_FmWC>k1;ZxYqdph7f#M5Jgx_~5y(pidBV*!619g~SkzMQ4)32$#Z`y^X zTVx1OHk|^=usPmh+>@hoP)nosFfSMu%laf>BT96~h!-n@X4V5D#*9hS0t6?(j+lST z!$ZiwA-owHlNx=iq%Mr(8Tt;AB;Y)r?d(&nH#I)p8^(4CT0s}x++tXvss*B!nJob7 z7|(Fa%K}MB<6)8jV}3cZh6)~%86hVseEi{+Mb4fGatu`9gtpMN8gPJrLTI#+ZS_lT z^g3?!jnf4&OQImDQXuC*FWh%uh(;EO00SRVVozL8G(7WMHo=7>@xwk3|EP`&@f^2> ziK{R8uApdJX1D7e2BR;oavJSc0i&)D)MX6rgLPfso2|L*7NS8yVj;53$JylP)meG* zeNBoV9o--zXoP@7g)YPz5aS5k5{SCy^<3H==j{GNwz;x zP_%NPE@&{B@6%E9Hc9W5BUrX#%@_HVxB9no8*R5nt_VlWs?jnFt9TzZWv$m%!YQAn zy@xPFxl-fOw}VcFK65WLXXk3GrxnN6K>=+euzzcX`#6a0AmggLYcAU8Ch&-Qgzf(+T6{md3n>h`>YUs5T|^xBiR zjiIROphdY5|Ng7C)`!7+HpnMdg}z1p0{`z;TWo%skT7czkJWv9pfzccymz|uv2&;$ zu{DQ5j(%wM{#fug@wXT%)T>S*lY>a_Fs7^Dr9qpwIyt=eiMTBx;dFb>Z*?_<0&Nf* z)(8+JnDAV|Vl`qC;6xFGrZL0b^y^lWxE@5bn&8FS9H?*y>{>`}@bx+1vY0<&zC$># zFaFmK2QgY;wo4ONQf6l@QW=WX0z!IEbr}AU6_u4I#p8dbpMi!hw|dQJpwTG*&%nU1 ztP~lD&ClZ%D)`DQ*x>I0Daan45X_?(ft_-7Og5DuuI`@Y%E0ov|sI+S` zLNt*X@j~51YJ_&qsLcWhckPrEgcHmuN>vEY;UP8s3HdilFm-@5qy!_zPwMf^_K^R= z3c^YsmpA+B=lKC{)pyKq)bi4HqQ<^wyeWKWS1aV*;*pqtUn=U7hoa`gNlSDeiMi?q z-WsXRK$ABDj3H_(sD~%OH(22UM!q3qjf9qKkgA->LaC?$NRx4W+FjN=iq8uoh4_nY zmHCbaM1Popv2VEi6hwd<{5G@ZIU}t^E8U?RtX{Exe!2dONVkFzea?mw?WR@<3}u5! z1_^e8XVf!Ebf62XfvU4?iZ2FKLhj0dL6QkV2}Y6js){ZH#v%5czWZKLl-Z5@IxbI9 z{NDev`V6%-`Q{@V_1rD}=T-btqk?De;Ra5shRtmx0`SH0l_yt+V)8HqG-BNP8si#s zruC{&gk(#6eq2uuyVm>!HfP6YCSw;-y?Q*lQQt0+dpSfj8(WCPqDueKRw7x-*ngA2_$-7@h;xsM(_VX8da5J zy?nVy+01;-UhBMG*kT$ed9r(2+yYf+13~A!T8npsy3grI$4^M|Pk+US>@E1~Tt;Hk zF+rsqfAumbaa?2AYFDE1pv{G~YMHu9$6xLAnqsj#l%NeosirwF*v@rO_|(DY<#$)T z!e7O_V4S>Qp1R6dEe>@_45AOTJ07aEefhb>kopobushRFBi5YJRCn8WE44>o4#Q#fZD`W*){^uBp?mj1?E5ml1k8@}_ z@IwXF@hD&umED%+VPFVCzuEcCj=oQ|W z5X%|X)a9@-3hPcTg5h`8OC>pX=H`p1Weos=b9K`s)cpRRIV$%l0;Ue@PcNP~^ALnF zstGAFho}=ZggiP~_x9qk<7z_B;aw3+o* zFGSh1FOwcEkVZcRXUGM^r6U1YKnHYFO|a8K8-3Fw|3&ii_bMoylw1rxVSl~Rs>1sC zGmT(KVKwxEDe*br?CX)jm-PPVRsq+{VB1U%P^;3fIgP%w*ig;n<(a$d<>GKJfpw-U z6SEOz43su8P1p9TZ;g9VNGAO6r*gXiT9*hU4TCzGV1)L0jls#P`-gaX(eqj*c0}dO zla{o_6x1TRXJ4e7iiOT}U{70OQ}ObWcj0hZSix+sz{?^akSL6_mDs?wMDk@w$YXXR zL4`?KbzxUe=1c>M@U`xJxovD@0h|sb?4<6;hInhg?pQ)n@4b)~#28mR60ldSAcRKn zFMOhn6Fz3ET9tRr5XI$k36}!)y?rhtiju;cG7}&|pi4sq=SCwc@o&?{++KdmV`G@{9#W6Q__jXfssSUg3J@P_EJ3}TNkdsD}54}XDrA4VPmaCQX zOpPp(#{^ceKB>RHZ0(iY%eDBIc0aQs#7fw=7c}E0yBKv;51NpT^lt*?{Q0{cN_enq zdbG}^{rLr`s3$;8ilU(T)aZ{D=xlu0gw!%7Ga8kE=^xK>m&J218ar9e(8gHYkbb@spJS8wA2OAqJgAYzlcIH=UsRUnm zeBX8VCSU9DlKf9!Kr`eX{*`@j2q?vPCjw=EoUf{TSVUzjrUPt84gRcly4w}1LEAf@ z6MNzDvl_=vDojCPr6^7+M4FAfJzFgf-tfy>q^mItZ9NAccVeCy0DvzK@eAux$HUH$ z0B0$aWkO#FmukJqufZ3G+o9FI)hciI^Jtc*L-(At^&py};a9h;8f%NvqQB02t!~|e z?Q+KMU%jz{7Ldg0g*to3m!#RNO~>Lh5=|R`?-BJsiM;-q-#3J8R3dIPDn8S9DT|C7 zjaHtvdXlNpTCP2{=$ozT+eSE~VXVG~a$QX!H;jy<{G2?Z(d-BAE0X)!c()ty=^aa_ zb67=M-hEkJD|GBLRyXbT%$@TwoC}d9>EG`>GU3GOJ31$+JB&r;qadTy%QuQe+c_cu4$0aG%i%A$elXRqp&Qvh8JV&Z%^UC+|!!U-uK78WGaAJ)Ix zxkW_*bQO4URcQH2eW$OuUzCK41rc^Fetk>r7Zo1G3uA?BJe zTo1}9gam^93O+g*AyXF+HL+r5A!J5e>HvghMG{cDA(0<^Npj*5`d$|#!B34JsL%0% znc9AHpM~X@A(rCgyU2p@Kk|8ziGTkJ_(_0$JL3gt@KDHlk)dqCAN2Oxh|D923Ubo0 z{DTn?0wbt2F!pD~Ie*RQPu-G+NxizYlyS*#Y$ml!!}=+7JW2~u46rr99{?qM$)7;u zDwI*UXten%u%XMQ7PF=!`tb=$W~Fx46bqs()M%wqD9${(MJ!i7rJw<+e-axrWm<4y zw3#N=>_7Pod;`(ai#pXDXG1NEm}D?GQd}M)nD~UKV9VYZw?w9OXsBU46r3d6q)C%O zrR;hMlU9CtE-M{Q#cPOPh{m9-(nU_fAj*>9@nKq$C(+U`Q13OuG)?*?hXk{SUbLd- zqmb5eH}pz*aeIz%pb@mOnGMn0h6#+me}!+vMTY@au5Gev_=AUD5J%_dPW>T=!4Gnb z6W}7a8aS|JtxLqst72aGSPFA2$hYDHLyFo3vXnHjdv<&d36G8~k78m_4uZR7zmg4+lfn`XA_tH9Kp5kG zQ1yB&FvuiVORbkDS=LFo^bn5EaY>*=7c->@qC`XfngcO5-jJ(Z2+7Zv{I=kHf@vvt zFss`OPr5|;<<_Nu9R|Y$2Z1@5lq@dz^D6K`RA?B7`O|~*Mx1eQF_- zN^(%jlkeHJ^F*HgiN_SjY38#Cs+%FL{foWprVpM0pZZ;y>ni}Jg!GG%pQl|+-Q~ma zPnr20EY%l$Rw2Ol^YOO(esC`1uCn<{u~=g+X>^-$5Ff9UqA}g+tPcJsb~qzdJl?>* zwQ_DMlXmu`0v1h={HgSZ>jwki-HGc?DETa7+(ZYI#V#eLv?f-86id)6B0xn4R|4Tj zDrMdbO|sYRp!1=ICSrU%Q*OVk5NuM*V&Ui5t90bh&}O3@Ycj#qx(;oK4>c|gN7ZG9 z4pUFI-aFr4&1dy0rUBtm$=5Enf{S6SEWj{0d$exMC*!PWeHv z#U9L+3XB*#Dlg^wTQdt^0JyPt1qjab$|+!vv?+zDXIM~z(rKJpLc`#^&jEbeB!Q%s zuWl^3rr>VM86<fNza-j&`c7vBBT+4R0L&gkJ$D`mvS2K*K2iqoJ&`#BtCH%CPW2iex$QOG-?3n z71&@du{DdV+HP|xNfbsHb8_3C$E{e&P^|lmM8I_y^LZ{S?=tEKd$#f+PDEZN8g2K{DjN)?#Ci;8;EFwRx zPDi>b)XL6xU6WKCRfo!J&zw|wo+I{LLE!xRW> z9sG{=Je#2NG^f34G4ekJBBdflqZM)i`}59j>k)KV3`bP(=10eIguY7@PTxuw;QhJa z$L<%@`Ww|7yM+&pTrTV*{qK~184v`AK7-$p(KS{9pOnV0{z)IVf?NA{6yEanbS(EN z5zK1!%uJ5Ein{}~s+hMf_^$u@pB6t#el4@M`Ie4^be=SAIzTK~>aqkv9-t!E3(-~OYy}nxJ{Z(c!R~=f8 z!FqW39hJk%upgx^&~h6}>C&7}X6f{obcI+emH_V7Rl>Ji&vdo8wO&XTRcXVgZ4aIu z=k>^_ce0qw-jrMcMo#T?1aDS}d=MSeb4MrqQFH|Kur>Nsprkgi?=cCxl*v8P0Pic~ zFq6wc^dIgLY+;i_H0U%VkjaBjZgJBOIpy5(=Z@GHA*kz$!Lx0;wdWW{H@USUYpoS2*FS zaSk^cAj%*?5!AvMIX~VZAJQcNI?QAO`ONR9{iw-rn;aY9-M5gl&IjTZ+QcZ(?F|PS zi~<2pCQ80&gbR?OKo&I|twSAHrKMS-9LOBS5WhUS?6SylJqkyHB7-+T?=410$#3fw zWCs1E+P3uh&+}A#U^v9$GI<&Z0X7;WK3?HTCp6+(ofp}d;Np#T|72HzssfM@g3-f~ zas1L<_$^HdeVUltdc&(%jeEJ(QGUX)VPHe-7G7{G3wntd31czgQ&_3yB}(C*XR2!J zssOj!=wFh8uMH?{3C_|B;c19aDPbr&V zxhSzo-d>}CH3n{E(s8PVhtX&}P^P;>eEM+142XVLU73xMjIC=PHLKqP|{ zYCIHE=NFJ*E70u-jgI8l1v~(m;X0T|*pl+;M%=0b%Mw$<*+(y0$zw!HiI|-!@WTwK z1r{V$XE@?3mzzM%;kWIS#q_-WCC7PP~ zSgU)d+v8DOoIM?%r}OFT#rxSsgh{_x6ON+&4xGal=@2MlDQ4I=U3Yyl zMaMJjOilf@7u+tu-?c?NSkw8Wb#PMVdn5fg+Yfvf1zDAzXN}iu!w;Fbt;(%-j8}f# zeY>(AH3t8$hr?+sq7SyoCb7MEj;>1GPED#+eU$RabXcCYijL2|(S0=SC)sO0TPipE zZRuEIsN0`+TC;~Nlk)39|5A_5%v|`Cls=DYPQe~^0vB*&(DfhJYbutB)N1rJeLPGj zrK=snS;x=5Y8}e!*cGKe<#&^815De^{iIr zTR1owr%q*T9~|H_U9k0C<4-CJt;?Q;=Z!{BoF_XLe5DUxoeq1b3636*3t zF>etTyy+ezC-12`c9ET*_|Hbgwe96l%lx1dz8X_V$0WzUyTZez@N?IoKPuM|$55P^ z4TH+)586V1yweMGs1I|d58<0N!rRjrR($@^qVXS~$p^ln+kDWcRa)UsT&PN=-TxkC zBW6X_8Usm>G)+vzAtsm;L>)};o#5FxUNFQXAquX<7@utHM?w&;A#EFJD;uu@>D2)} zBL~w$g{M{1Qa$7@ms;mG`$`W*sUlddXCK?ffvlN3OM2ko^ zl%l`2DupA8A=-oIyv{1;RJ@pV$qTer5nGYS+rHDd$D6RDmYbQ)EYrrCH+M8rWMc|2 zH^YP(fQ8Z!2RWOG2bylAF?gJQ)}&)G&LALV&t}=Hs7nu+J<(7>t ztT6j$A^ywz(-r?0eVX>ySLa&$GZqnfvVGi_a_495)7Ix%=lSAzo_dY{`@`M&?ceO! z-QnSuGkdT$l`7&M|7&p2k7*T}Yuf2tYqB%@ul=#ai!G+vmq)DwgTv+V?a!{av;MP1 z2Io@B*g?EiozZ=|(zD=7@2QwHEB}}Jr>oD!**tF7pQIC~VMU(%I!1!|<1DB4;~0Dy zaA4EFeBLTnm6^E5O!zYjZGu$d&hw1<@v`Nfdg(clcw}dx_ii@=5qIW;(Uo-}&?@Y|)#j$XwZr zcyRb-z9p~g!_$lTv-U>$acjH@j{~dQ+y8bqMI*-J`ug$M=TJ!36RLW-`qT1t|J-_s z1V`#FZf>=%sq3v~v}XVNjhFZv*AkZl3x0G;o6y?NP2YyMy&Fg;I@X>J-;-2+RTMFN zynWj~RxR^abiJ;*69>Upu*PG#@& zue}x`dVVkC$saU1eRp`?uct>oE@GO+Ag&2}_T2wY|QD z9`C+&Kfd%H=a=XAp%1<;dql7Fk^2-)o3<}D9v(su2)e&+Z>t{W=ghr76zTO2x_q4D z0|SAxaw-M3V3Gofu3vIFW0SQWPIHiQyS`nS<3lnVt9R0dEbvOA)&gHUDr65ai8{Jo z3=CfvyQ`u}*ua0e2(l=O?Jui$cg+18?0mHCkma~^dd#UMWTf}=uW@@$8iYo2W>)Wf zpSrVVSX{PzeSZeZ!SdGFmwk&V4-a}Cu0xwScl?sHavV? zv)Zi=rP==E73suQqTTsv`Zy(giKI`@=T*FG^~h&1d++>&5*|g zB~70sw0IuF8S>5~WzgIYn+zV1;M2|&T|OmizWqclk*?`{ z2@_5S^t&X(+I+qn8nX(!u!^*m*Np4DHlf>)*tlD^;y{ME0*X+hVgkfo>nXX}P&U4)UvZ5czQMYJoH53LXD-L#*tEw%-^ zk%=`34PR5N7IMMGFR>i_I$r1f8!w@9cm=usgQurn zF%AT9)s@PBp!(ks=NjgbbD_ zyfUmyzG4?uEl6d|WCbQHQ@$iBS|mqJR;P-ADIWx~CWD4HJthdDGBYiVuw)cj9wbyY zfHN;F#6BHJ+YG!lZ^5e_$N4`RUQU@WLi^i5;i%_Eon^tJ)K;oC=KK{&{a!jDaa*xO zhtx#gOV9{{AkLZTsRcoKSi|1Ja~`rFD>7DXgq=69J3&srKg)EKV{6m`rzNEdZAush z7u$rigH=!eN6h@AQ?7_IN-i|up;1K`4Gp6<8iF+(YH87Kc6>oD0Sp&s=1W*0|a>)2ho8F@}c|lt*VFdA*73mU)TP49G4~6(F(o8&wthRtG0Zp`Vcwv<4Q&5 zFmcuaM8KQ!<{59V7HysnnDUNqZz$xId$@PM4xcSP?^W7w>G?R=-u7ZCe1F(IR;1y| zB`#i9t6lqQdp|!n4Rcu8>u7tOSF8=ITM_vX>@NuTB%U#{no!i6hYW7PtDqUq+2q{s zuOE$NeV(!uH_T%V`P*TH8kxrN*l_ZVJjoOCxI4Z7zD3&~?z>pX-)9C56GVj)#^!+! z0@G3kPc9Xf5v?Mu{B1qI0s7B)xUCbqdDXd{9@w`7f2G^pjN^UPODb$0hsb2?T_?Bj zv@^^-wIy#;Z!3l2YP;oi#m>pWx0Ki8bDCSuo@Ob`-CpgjuP!QO4u=+rXw}UujkC)W zX6n1pd=1OUoCKXLMc+K}N@wKV607{*xxG#IsH>m;{P-W}qUW{-=hs99@ulCV+ZeW< zv_D*iB8x|VP!R29lm5Z9_o?`nhi)hs4jC%>$zs`ZSWC3ku;cm=ca5*PDBzA=9OxyY zHM%?w9{F46_*%oc-1gF3o9xKf#og(ZUi7Vak;lCd(Qr~bvA--h-`$BwVLP?Uq3OH2 z`<~T(0Y?&}m=R7Y1F%^}8;^_9gWM#pVNHI%i54;|UO9wpLt`=F!7R1lc|8e0e6SZqf60|@1kb8W{i~3Z-#4sRd6h*N6aG99@ zk86ufyIMcWXNK<1wrvP*?e((H_`ZBKZ#9g)+2v~A*<0Y(yB(?mwj1r4V*9Ri@q29CxkQvJi-BD!Dm*GseR)$?rD>3jTL~oYl0WcKM6c=E{RkN z<3%pshOt4tV`7t{qvyK$4Pyf~(gjhl!>=-7qZ+m2kw)agW*HX_O^IW{SmVk@Xbma2 zvLiSP1_T&s>r)&o{C~7bzp9X>|09|4`GmXj3f+Ai3SKF5u!JLe6EeB2ER=nFOhBUK@L1t`XaGNEcoh|ziqsxAV_|T} zYMVyclQ1N1TM1E~|9Nig+`8#s2Z{FzqS4<3fW*+{qB?`JB8c-$)!kSDg3Yx?Q93Ka zCV3iWg-~T3=#|&V1PO-gW^lk1Fesy~a(8RVTo59bB&dNa7H^&Vd0uZgh<1h$iVGu# zfHo0=T{1!<3J+rhuJv^p!tLgS9Mo$#!hB>wm)F+eY6z2l5v+1`J_it=pqQi=tqB4B z!8C`ZO*}*le(&q)xU!B*+BHSU7jymBXhlzIe9K;{3dg#rdKa@;KIQnk+vFneMxC4& zgCS>>{!_!~x8*uPME>!AmKN2ChCXhjg~XjR-HTg^M-h;|qI1UOe@bNTkrg| z+wWKJh!~0o*~gy+Wa^GfK5YM9w!6IduWc%%cKdK072)-`gOkRwxt^Y*0y<->{oh>) z7`h#uZ+A;${;d-cb$5TPPh%44XzR6pJRDM?q<%S9l1DZ9EbGOw{Z-(P1fCxp)8W6= zINN@_6Na7^K6SCQBwjRS^)Z&cmn|F2P`HxXFKO=8(5FoZrMrP*-Yt!R%M#K^{o^u{HQTTQz zM-cHkIu5@7d2C=Rt?uM(s(%&ab$oQ6Qm&&u<4N1nU{HFj^EtnFlB>@ZqCC%jnsOA` z@Q8GRa7)27b8kHcooA0Ph^HepEs&>LmK_!1;+f?$w-SCfoP$L}M>Bo^1Ac(-hB@`> zMB$Xo8u&WP{iq;s(@Hss9O+iQK{Ae972n!}d&j#6!J+6O2peO8mmv6)O7OzmeI$a| z^twwdnw^<_Nc3EeZ(hvt<}70|P$=7wcz`Sl0uxk+~XQo*8E$~Un62yBKjwg#I zT@yYV$PJSp_YsHfoL|%ufcLvhP^+hq{KYWOd6YhSp9aGcor9|#;W2l~L<5fCdQ zA{sGVY#Br30ikG%?a@1?KrMJs7GU-5`2xj|51xO>{M2y*mXHn%Sro#eg|mvnt7fMJRYXf;hxifG)?QbwNLg#%zXul2iznF+ ze>O790;CaQl74|=V|p@iMv0AUh-__NA7I;)$xZ=zypL+=E+AxWAw21ukqDg@+qqZPO$yh3;LrOL6m2l}Jl$oD}?fiv{h7@=_0fq}-hdzJ}K-WAf zdzO5tS~akWZo;TBuiul*(s%bT_swnXc5hU_-@?(|3TETrbOZ*eM@ra$98^r^m)`iHZ!r5!!efv*ml^_L4Emmr2%kRLaqCJ)`d^7!CIy+TOuKL=KiYq5^;L#6srQN}fli zgz4|xRX?Y5Jr&1Nc&umqbS~x=*Kb)%g3`8T{my-1KX`Gaw z6o|$2Cosq?g>!J&0$t`+IGTUd*zC(m#kfekEidu?475^rdt3Cc^z6yI7Po!y!53L; zXl3vFbhY#QS>PYZ(M6uZeAup*krv6K8*oymv$1ebkw=ITAq^zXAlPC(({#+2s@Y(i zAsW2ZTm|xorw?_y>hJcZ)R&I;S>b-Ta3dQ0zOm>xk(!o2v~BZyh&^o1RbaY13Q^v_ zvEokRXv`Tpy&Gy9hLF%rxB6xN7xZ5m+Wl~eLmmw}JpW{;7_=8i82kEj8kDL24l%BtRFO!dV4%6HSS`pZBkRLTm zewrfWXGJboTI25XLnCX->|m#wZ%KqD^&zrn*Q>gzzLYa5f$sQO{q+eio*IEUNcU+& z#IxN|o1~n88Cv1D>Xb~y$AplaI7#a`^haJwg6KQ3a>;kZ) zC-T|Jpk)?CO|2Xy3QJFMi*^!B_|b#>+bB4hSlG!D2H);P(}o!ibB;_>gNU|BKb(e?F(njbi0NFULYc!yI|4Z7iC}izYo*Kiwk>{xnM6oY4eT!lB$N{zIl8Q(aE!U{ zjdcggI8)`?z#I_cpfjAKkk-Vdnf{0mb7PG_c}#>dFy4x*59byy!MLbhBt6P={GL~c zMOjgU&ofn$aA_nwy6(tP3Wuy`cf%Ivc8>`p5eAbicp4G}IDVH($4aev>HSx1h+P6N z&>)h)L&?Jy^hWdyiB5n+KH$yIy-8PU>9Go?E(t-#2-OOat>F1H{HPHYIt|qb}&A0J0D*W79;YXj<=bK1e%{8hfJ77rnV!1C}=CfCmz+C2G1RDJX=GJjVQ& zNqBAzp~1A{k$TKzX!c)HJE_a1-y))8qniE~TERP&@N(?TXA4Z)1SWLNvEv%NIPs36 zkYJ&5W}+$xWm(Fcc4!gKRo&QKN9@MH*5l}z2SMTpG9);d{2(q_1;NY+?oJL2AQW8d z={FJ#u5oKsE>#qU#HxswV3EiL$#;mPKNqrP3v|mT;b}rXGTD!gelSIFK1NItV)7`H z1hBWSR$Ynf$p?+-m$D~^ovB|6YdDC$AovnsR!IOFbsH+D%x;muK^LHPt)2J@-bfKgnO`ZF_%}WoL04jXs(4r^pKn0T&X(wFt!%E1)taFftJtHalLn}uF%^}XM;jIg*PMp^^~ek1Z%8a1_(oI;CD3YT+HIh zFRk9#ly%)w^=!$VI3JsPAcHa*;*u zTfq^G4i50D4s^)|Awpi(g9ypNu{z1HVOGXo{$#KBo4YLB7E?1uybMX)^4u|2i};Be zkh%mi$k~f7eDLnLK~Z97|2*0lS~!HiS+&_V$7djc?N+y`D{-q0QzQn5JQ6>gZ5jAZ z?msFXKCG%;?EPN}*nRy|_=WcK=elbx^)pn2(U?_$u{Rdm2AEh5)RbkiLY#A=qJyU+p0I2X+#X0}Xn{1sFf6`K zzsV!DSQfovfq)e7HBa_am+1S{jmyd@pOw(|kGDgl(lIuSu!SIssGuiWR9mI zsL7<71M@s{%n3Ii!rH8B>vNwqEy&Xh*cctu*tuj?aL~S_MCMyp5{7YdBPPv%o35S* zM}HaBDTPF|;O>f74jh1I6aiH-Q^0SD;d*nprlGRQ<&?Jl072l$~e5<{@e8} zq+87~I{oqT^2zG0>I*D#1pofWdz_{4hxMeeXtkOCxmg+=PhfZSdd_Z*yz_oNU9;_7 zv-9(*f7+@A3)qNc;cyTuZ$nS7H9K*rgfvqLoh2n8dM~rw&des+_vUTS-97dD%gtm+ z?v8bFT%Yd0aaUF9b01T4{Q|{p*~$IvpCh6d?2)yfS6;?G>NQ2z`X68Nk9MR80tO}K z^QTc0cH3tqX0DEDbPdK>RPHx>2f0EN!|@a&|8_vr7)eH*%3(lU;tjx4deISs&aS!~ zJ_a)821*JgIBXe+j#Rwt?ym~(d`xE?HTOwSR=_MsbwzeS0k-%vsc7NRi5vGqLru1D z1Ra7@w+GF((C(4t_bP{dB!tb?-l*kLi`r?AvR^^f6Q4Da?B8T1o#U_4On*s#FyFxX zIMl2;*)$}or1)a^{~MVmawcV1_T0I@zc)@A+&gGgfbK>u`GVWO^1%I?tYOq!uY0NJ zML#o}y?PxLF@L=1mBT{BP+;*p$X@q>(Ek{ms3tSw+#w(4Na$P0ux*E@`l+Yy;Po6x z*WqAsH>1EYI(6y(NZtH2hrrox?6r+E)siciQWX@suT6P}_I1E?%R*kLme(pY)cuOR zCBP@(k4%`EI^2B0S8%UQe+6@YoF_-Ag0<23eU0`e+?i?CQSN#-BYk}EjqhUAxelFF zJ@$`YyG?lxLm)!|O{HSAV&o~^daF>G8R*c?IZB(FJ`4HfbQt_|FnxHfQ0}g;v#U}) zNeMXZ8d5)lA$6+qU?>wKQ6xUp8!2A`WiDFC(1yTh0q!rt@7SkKW;E_0MVgM3Q%p7o zBTF_OG~wi+dR)ni!j8~lK@vJa2VqG%x`YX&|4{&>bpvV?51ithia#J?O@2ZM%1SHuSf>jvTA_iGi1NvMsb+kE{UlSz+!saY}vjbRgo65{;wpE=aV79lg8ZUH}s z0~e4%3Dboqn2p|>ninQlwazjvbHpEV2r)0CCylL@fa+Y7kw1Dy`3SLV9BW*0jVyn@ z>Boi*@UnFNBOrPBNb4T&b6lhQGv(>e=n4bDFtvTlj4-h!DHx?arekI@=V{sP*Rz@F zj=~X_`Y&>%F42v&te_0ym6m3N@*xv$p9-?`!%^42#fOGlt!zF2lltOUN-%!NIMmt| zIH+jzs1JBo1neX{A$h~827pNpvhK-hluA<0x8%ZSEV+mZoEv^lF&j^KqNYQwz`%hP22z9j)=2yvBQYp`3%X)2B%`>)PHWhN)enqQf4X=A zC$sh9Q((iQQe&MEBxQu7W>z77;Z4FNp(QziQxgPaDZq| zVrgmP+9&C?YLNP^C*p^{9sJRj}6boe09n6V_*%ce<%6(I(l~kzXX@C3p z^YzlaXPlq@`;OZMs<(h!;9`XLywAZJeG_-y6|m-9Lfvx~!(bHZBzLVf2Cj2W_HI(} zv}@kxFBA{2IxWgR7Xhq4N=G?5jXx8rU3YvxbgCQKuiSmAr(W~f!06HYf16!Cg(b*& zh>#=5W6%SYlfI`<_GxzC&HNp|Vt2`lHPhyFd>%C4GJxwua*eiJEmwExwttENWszqs z`p1Z}74h6&sH*sLhntt=bFNVHLg z1cNXDymJ9QM0DJkas%+D9Gi7EzpH%yf40-|ECMa=>l0g-e)CJJCbnZFl19p6qm0m&rWLUk4>gVmUhbVSL`^^Q{O~L zt2PV@&p~!jsM(-X9^W07d!^!`!_Q+m9SZxoEvcXJ#{ZzGB5$uYH!nTcQa!Ybbhm^v zns{~g6y`prPrDWgGm0qn`UAGvEUu=N=Dfa4{O(W4bI@i}cCL5iTrJ&YcOMs!9}`$yDo!q)<~%%U z0v1R8FHfDOfk?-3IBo_=T>cMzEP0nc&+O5USx!&|;~73&B!6wKUn@#wN~4Nj%=y$a zBBf?pTdI##vp5b)Sj$)dKWjXYQyc}!5hL7bphJxHxF@pDsM;m}EWjUBxmU=hH3!b; zSG0+q02;uIk+E5+1`L-lk7Y^OROblcZ$^a_Q4S${8#2iM+6bekX zhU9|4t2#l|6p6AAv|ebF4qGw){`cvU!R^Cd=fQer5f7o12$Zsxh&hlDOt57=_>%j4 zFdI&x!ZWIE1sA!NLJ|}RS9Whx3dxM=LqQC}j`&H5f|}!j2)HUmb!wkHjTyKi^>T;2 zKz#35PgwBu#T?M32Ffjp3I2*%E7yX$U_s81oIr1v5;Pxc@~0&dcm~*#+AnE zI9t}y(GOwojG?XIvGSFDc0K9n^aOzaK}WVF&sT#mh&S(XGNXnkG+Hn1$QEZNSfS*L z|1VU-Dmg4Eg$vCjs115UL?43z!Yk`zafcSxz6SBsD>#e@?QeVg_JSF5xAZi=_KQ`m zSxP4DHuSyTCyUd%s?fBC^eKvBqma7FIFPoYC*l{g{HI)Z|4z{HdlY7@GVk8P=?!PQ zj2UZoKHu#XcB|TN==;4&JyswAsPUe`-|Ny`=L-IaP@X1Al) z^WF6R&zEyV)!#svo?h7DTgBA6QQohOMBwMB{qfe$ThHb$)G{akpKR`XHoVELg~ z?RD6Bb~x>)(c1WMGI`diUDx8sHrV>-BDF#nIM8d0$Fj!kX~EF(_{dgUemdKHu+zn@ z%gJFpcQ2v#bLGWLcpO95E@@p-C>v|M;dQQ!ZEkd0E8?E?&pP|$Wj5fV*8!S3d&Sph z`wHIkHu{m;AC$$tRnbhFT{oQ`<0Y0)+uSYo80i9L-OS%9V@y9%i1M>_h1vV4cFpcm ziPI0lel=CV=|;zs;76j8_{VX5Pe~FFQgSkEQgH^_y2+>w&5iFm$7HW-qacG}uYM#> z`ht1vTIiKzoZjqfky7nG^+OF! zw=X}I`=mYxOgCD=C=@r5?bCNMus)WNC3o}nO0A!9S6l{#%k)@{8t+Rk)*cm(DZ{ln z;iUPLQnx`OP+=Ul|L|Lgq&dY7F=DM8E-Pb_`{@5HJhfUK7NYozWUj9sB7le zk}P7d5-$im8C~1}uEmJ)D8Lw|UNUsyxhrx3o2tserKEkg=X7K-o6tBJngYFtzTQa~ zFW{gLlz|KmgIFXS;s3xg0X443v!t_vMdjB~ojLT+{2a0##>pUJLP+udGjfm{E&7h* zvJ$*b(^==!RfrHil&)20GG~bjPY|dPTi}8G00YG^)Z5Qc*U_SGn%Zlt#F!p?kc=+U zT;SW$pb9t4cQE$w5mFv0N(i_gVLB=R7ng)Y9_n+pj=2m2P|^^u3t!2~@ciwNdloy< z*{;ra=C$Z)3eWyW3NjfEL8uuTF|YA}#!!sMgr8&!MgjzH%AsZ69WDG<>^6-Yjr%wy zuxjyOc4V;Rv3t!ys5?3afgUBnXd+c&C1QkZ)(gH|+-T0HlE=}!2AIr(zlzu?gFp;X zRP`7K#PWt1{-G2y%`M^PjYbx7>cu&D`{LW@1YhpnOJ);WH$nw@|0tV-lSKPwKo&|4 z#uQGljL-^PlC>82Bk-hA9Q$>W+>`7D-e`)TXvY{oX0!vCpL0036!4S(hqBMqr$u7| zbk-F`{<*RketX$3(-^*C-iy>UsRLcW0RzMu6$g4FmyxlC!fEsuERoL}#c4Gy_-b!1EWRYO=wlt+DZr@{{x= z&)(71=3>&LXPa;S!>$;FS3n_5>c1s5VsW+|$^o|?jXX>kC_RX)ke0R8ucQ=XnC8?| zllnd6(I(=?x=;gyq+>2@{BeIy#tCC#0@1#arqL#y4l++?WVYz63Q|?&(5bcYnf{SkGHwP#w15ztQ_C&*7x04 zdivNj^h+FX7>;d_@^G8y-1fhX77qhE7I0N74O7<8D*Pd>@){^9nv=uJg5{RI<>NI> z-r*2FSD55+oSSpbStnnQe9j-I+2#A*wV5*S@t%=URTm{pBtTn2)l$9Bt45f!p{14S zniq-D{&aZUa#0}*-}rjKOG~urUH}sHo=Z?;P5D7f_w}uB{w>kOT0mtkB&5Z$u5C*)BaNE7z}tMa-;pZ4c>c z-S4Q9KayP?@xXlCYkbrdOKxIjvW|K3K%Z=~(U{x(zL9=$aJ!_5FNr49FUSc)H}e7G z8h14db7=Bz5MXQMR550pEE(Jc4OO?|jX_HDuUCnNUn8(~?^BE6 zMEvhit&IxdxRW;y=VwEvNMI;aX)!q1;1msx2723Y0QN2lX6#c+`8o4{8UWHyGCUEg za8WpESeVGBml=!vJuz(u^K#0>7eDf({}BL%1Im2zpLxhSXQUwwUahPJhaq@W!a95f zAd7mSu;pOsP}Z(gf(>C$WH~m*G&?q4c6&mS42 z!eEOL#3L(HR*H(6WYD5jLF@j<07&r($37NLzgvREmYBf=nhpQEkkd?MaD8st&5xmcOw?0&JPH*T;t&BTfnwPs~Q7h!R#uDoln_pTfY7)N;5ai z|3EB!9(=kwurd6orKPO94bclmrPJr1p-h}s|BM%{P_Po)`sEk6HOtEu(6 z4jvg)FBUlJ0ZeGHTDp*UCIQ$l0~DMp5SJVZCrv=UCAGw@$2QAd zmr;B11%=WqmVn>KWX92xDneDMI#>v%tQi@Alkr4Nl1nNjj6I^aJ~Q#5#+Tco9hWTjl{~B#f~DsHiXtoaVd&%L z7FB)nvM;JMeE^?yJ3J|Ntgc7&jhEs**0A;_l42yKKI-033mAO{>B|njJe%9;RI~MS%K98Q&ypJ87dLS7}>O`=)x4 zke+RI)FQnbU^e-)S2DitomK5jsYD|gtdm`a4>34@D}1$h~Q#nYY%S)(sArB+DDF<`7BzW+`w4{l@% zH@WLbDB5l(0)TVDyeb?J2NUAq;14hllKcUi*`dv6r!)_*Wr{0}A5t^y4|R%{&E<#; zerVl660J);4B94d69PoRLJL|xc+1hnqeJerJs`7SI9B~XlCFWV5@uP(wr$(i#26I1W^D z2u1{%tGYvzk>>ZQfKf5KkR#sVB5 zWK|jy(E4p73PdGYkr}1LWUA7Cl>NFB&eP}w+I0e^u56Iyd%nCFg05W@40^sFUv^h^ zUg^@fD1B$CFW(OiUXmI0E4IICybq!^y#T8B6sfbbQV;8R2hnj3YH+}ZSwF4NK1m<7 z^;YlrB;gbD8&S|^KXbV|AFu7quv*^yb@FixtH=Z<#-09=5#rdEUz7K^ac#Z$T_eTZ zjF`uzK1%LtjJxjV>U`Y47rEW_jMypr*f-4L#(TlTkgPjqmbK>cJsk&1EQ5fm)86`(^;~4grc-1NV$!A2*mz;C=9R&i_K!Up zf9=RO{LvqH)c4?r(u0r^v16Grk1JO+2cE0=JN#0|jDn5>O8NPGH-gQFWN%>uE*Qx3 zUM{cw&(D&b-j5i#kL}qztriOdBY*SVIiBRGJlZqZkjg@~_HYE*<%K)lZbgb1<;sQ$ zmpObYG2yb;qH&QVfcB5Nw^CD*t7y`Nm9^8g5L)$R+Q>W;C}lokNEr@J8G*=xieuRa z@dKng#kHVs!Zj8bOVvQEtTK3Dn8aPCco2pKg9@L~h^K%E@6RG4=l`Y~RV5mj-R)x@ z5_j|+v4PlAMti$~K!P)1P>EnH|6kL0rw~uOEmUCDz4T=PEC<=sZU)GmM0uFsKo~q> z;mmQN1R!8~^V+o1nbZ43-LX^b(I>I{MEnfNA>2@3a|c8r`q<3{=Xusr{1A&0Q5584 ziu1}u9dSIjnvkR6E(vu1pUb1)>D#{`dTPuakt8|>6}^c%76Vb}gCftDU}PBEJ;G>t zQbGV#0-3m&pj;NPtD)$R5f<1uiA`dokc2d$cnIbi%}MAClU9&+B@`g0hbNxbn0Y-g zs1d*i8iN^Gl0@6(e}MsW@)_E&lWCe|(pg<-j~kC5Q*Iud-Ee6!rn*Uk2^k(jVd{c} z9GG&Aj4IM=3c8QFHeHdS0y;6uKKd_Qw-)oJ29ZD-%x09J#Bn|aiV9pFO(f1&Uhn<3Bt~WWOE%A2jsuzNQ+V+modJUf7DZa75w*s#BG{ za7KhP#bc@kcM5|x^`#hUb$2t@ZdCHQ>)dR0iJH<1;R@<^!5K3}njiN@x{1!95=}gf z*K1(jD8yt8K@UH}>0e9W0?9;z8m<$k&V&pmip4gCp>3xaQN#SFc?G8V{k^?$Wy75U zKtt``Z$AQlC-%jhM5)si03k3ym$y>?tq7w7gQKRZ68gz+95-p@3ovioUQvNdq6 z@v%|0K4Nxx{X8!kbIa!xh+WPfaQtdsF=|#>9~YMu_x3DwzAkY z!pa%_P;MX`wheq66fF*A)?Sk+hDsFb%Q} z6ZN^T`}F%bw!VGec1{{Ij;=Ip+I~>fS^@hrpg$M_QvVJPFaOMrSckN`lVk8jsvQ1|wS^`jkF5+pta0j}a_)-(zpEV|QFQyAKW}EZZqUPIQUkTL1teYphGqm$)`Dj_)KNNXgg&iEI%k?> z#PH#%CJ?1L&Sok^Qe#}Nf!D=}oR~gNU4oAy63d^gB;IlP!nF&zV)%aYCUE<<^DEry zwYf)1D-s2S+yqH7%=POUN2j!8=r3_On;1`s;37xqt!$itvFXAPId4cJhQyI2XhSOv zjI=}{B`MGm6;u&=OA%tp3~4;?Xl-Nx0AC<{sPB6H5B{)oF-KK|>AZQpADOm?fbG z3gMmtTTJp`nl>PrYiItQmM$YFT!8nXlgE6so(XM35CS<+$7f;!8JjScf<{I#NjGY0 zlB?zgy!ndk&i(uJ2sx*HyYM%bGT6Z03WDyy#5oIu#_WA%QUlfJh48Q^Uo+L+RAI_|Sc_rCqCv&MAT$|3;wG>$={WC5F3R3iDi+#9$X`|AExhmOs5@HJc4RTJ@PFfg zu)9GrR7`pnCg13iQ;VL60DTrCoK>RbNgYr&ObA6%lWW za^WX{0YeKw)dw(oq5HXdC~eyXI4X2Jp6q@%+%7J?2a5B}1wHx4%YzY7^v zak*U&Ck|n@oeb?|Y9$Fa+4-aMMJPEo-?aXNcnai(eyiU7Y;ph6_8YkN?FQK4l-Kv_ z`+Q7oP{PUte7sP3-`^Gklsd|MtabavQ3yGOcLOaOo4a+I2mV#LNgM1h=POy zkp`J)RXx;jgXpkjj+1h(Na( z>qt{%dd&$&E|jXZ*sRv|cwBw)@%K!qKFmGc>Kfd;+HLgTW4mFCBuT|Y4DF2u{@P16 zfn02~jahwMj#xipLn9h(qP|eTRTXi#hFDIo3kQR^mm4#5#HUDfP#U|pTSd@qu1y?6 zk>a%i)y3`tVjS#8Tjyx`2Lc0XpBz*n7Ll;l>`AnV<3m^I>YUKFcFqpz6@B%sJA^f>s?^uBuLrUIfwW9yNRlm408CFa*t zX|pBk=eeNl&~k+bmxs5Gpv9rY&rdHdj>YgL^uzhj!R^njk=xPO&n*wy=kQK)KmhRg zZd^*+LT=OZf3|aRCvz>{%U?(y8j^9`RIhWLd(?)Vu=Ra{6f6u`ZKbZ2mur9fsvv$! z7BSt2mL*8xX+e9`=Zka}GzPqF%-{DvEk^I%k8F%y3+(u?omo_W$BQe(imLv?eKos# z4}*6T(0P5dcRDs!`Kf7e?0P&bceFHrJHV!2D6?Og3TnpXsMzheBf`V4PZ&O%TB*p8 z`Qq}0BL6P4(6oqByz(Qe+Jbba?%(hGp(dznOrP|`_3`AO{2d);_rJp5#4v> zNt}oCOx#eXx`?CNXn!98PHHQCK%Twq<>!4a0)>_{#?;TQbYo=C#8(}+lF-6chHr*N zqz1ha_jmB(E_SPlkS`YaOt<2y!`5KKxd$veo{gf$pkM%OMYg={`&nd~q9HbTy_9-= z8rts(AF0$4Lapd{sN;hUKZ#B!p!m&~`DjLxI$_>oS|qh%DgRr!(6ye2R}B`)%Jy!K}U~EHJ6fc@qB(YD_y{wyIy{+{Ie0#H@Fwe|sDC}5x z`|oDon~+v|SO3e^+{CIU$Jx)M^40a#Phh?(JD#h^$1};ri0+TMGDD=s`&kQB5A@Z zcaHsvOP=OY+UqT?jemU}KT_{}cZ#T=@8+zun7pfqAJ=wNa2p>TJ95Tct85uBxd8&4 z`^&@*!_MZ1v!NrpU$Dm{c3C=Th?;QA?NZ}@Y=`41rJck-cT8ztdjyTGl~ctkp4z}5 zm9*zmA^$yw*ReK2(aZ9OR=GD?#zc?)fD1iUV(ST)_HBKZsQ*Pr zyE~^dtY!Q}lJ`ZK^@BV3-i#zuD4rfYe#z~F006fR~*^jpUVy79{A=bPhob}C8z6W=$t zqsjqps{LGwJ|El(C2al{nUYBY{oC1-0OFyp%5<9j8OMkBoREU&Cth%H}@}4_4e4|PY zm)Tclgf&}y<-~9BM;+jKUPD7|&fnp941DID7>)P~f5%NCOUw^}au&!Sw6!WL+Gd6% zI$eHIZ+c8=5YPpqu<5OX~6$mT=H{sgrM0l5pmLWsgLkyCaxP^w> z*$vi6Y2wM;+m%>XrJh2RFBbL+=?cL^qeI;1^ov5=^#p>Y)QwCi6L|Z1D9~m8K|a*B zH`Ho1BOW?NLk2Y&Zcsoj7iL1;Y&FuMJ!`(HVn}f&l-avCbb^-6C|ij$+|b{mi-8ZD z^M?zE)JK4`ZtmmrEPZ`z`HY4Pov@%W133o?>feNPx7}{!R;uPsrE0hgB)4vit0?6d4f7fu^IAhWEZmeQlXv6k`Qigr zZl&QlCUk4v!z47w=entdkE+nt*+#2gYFr##_%Rf0c-|pq zFV)UB@SslWA_AiUh*drzqx@`&`ORM*fxUlE!^g4pQ+-m%KzZ0sQE0k89u~D(W<6z;->Z18%v66zw zGt8uhJYriR2xLeuA}fjeY2fkH_m)}JELhSEveZk_^XUyTsrdGX)-u4NgZwB z&QaKzp76 z&guqEoWgI+-q2lT()z~Dy9Xt9CsCG}9#fi3-sEvJ6h9P@da%b+3g-#kj)$@?9pR?R zuH#ry7Cm_itAhFI2{P3#viFkwv$I4iVzH|+;e*1jra- z(ZqH8J>jLf6#{;Hrl;I_`m-e$7)6H7Lvo;a*(TKe)W;Z14;fPCsV`McnvA7{ZcT zJBERbd1M%=4E(LQXgi}ol>|Ls5^Ee@ORQzhq@`+Qc9sJ-35Exo0U002!*2&B#c(l! z@9{4*o+!cB858~29v9z$E-4Ddi404Jsg-8toD4Ha-?0d+u9cMzYN1i{xbHSjzz_aA zpn;8106`**m`3@c7T-J~O?(zpoKYA&xKkorr=k}7CpIK<|B#-lAMC|pE>3hWdhZj& z^miJh!Ecd+4F>B~%$g02-3v6uBbm21pOFQJgOlL6D_@^Y_fD~1x2l&Ge7=#p1%3xx z!Nh4suax5-K}q zg8*C<6e!wUR@e_+tn~=l2yDGUkmm+_iR}L=3(m46cpJuOUThkfSByxr&B49J*?}WG&Iui)S#$vPo|6tWK|Sk2 zb}>Y=ufInyq@itcVMRI^i__yOok2h;7e*jswcP4zFdggAr0WGR1q`9JM45`Jk=50y$Gs(e>QuU+m1HlCR-6JRL8UR zmI?TjymyYjuE$ECym3o5qxEv*_P%PxmtMPceCw7mPN=XGet&8M@W-~o35)+~W}fO5 zqXgrZ*0$9gLz;N63U{o_ty~XgTKH(R4C1{@y6ZsVtZBu0!num4gY!Y6sMERe*-807 zJ>6V2d0^ESy^MuysV>BSM7mwgz7I0Fs=;^*gNOhZznvI#p_Wg)-y%xB?A}V-8nH*1 z*I^^ zx5aRI6in=r5m0FT4L#S`xme_~LC?!WsU8(NQ;aqXI9|XU{>#hzO)wOsyz|Ye(m6nt zRc~T?3k|&;w8O{4JPwQ6oyBB`6%PK{N(S67747Z51pdb7;f^pc+|q~yVT`jP$eaB$ zizR1x$o9-a!ok4MKv*5 zFL=j{YxN`+qW|~~_{AkQ0J-)*Q{R{DRbxnw9R{J20%Y8Pi{5HTGJAO}NJiHbIShD@ zSZMl?;+{L1jfws>5dx&SFtGxWuJd1=wUqz0vcEl>;7hK0=;xxUlQTuPn~a6&P@)wT zn?4qeY-~8D{mBXA8BG4-Vu-U6DjS?`KFD^q58Uc6GhTFg-}C?{RF=2*Bwefe*-Er> zd*H=`oKk{!@bpUlYpftt z2W}olRu~1f0`)0oV5|y!!MWL!GD~i%Uk9SA)-FZy`{ZPN=}{$O!GPZp?YgePQ>d); zvLBef)?MP{Chkf@GE0|I4+7|CL40|a5nKSPK?|G-zA(=#4|%2V4WHj4 zX8nmfRn_>2C(gpmrE8GbC%K33Il^|Z!H3h2@-3yKXeqOZ zLZwE47F1urTxx=}+nHDiiE!b`MCDovML+bagJ!KA#ZRhZ6Rjqryu;99yrs(3ci&sr zvO}2Y+C)D=PP(_h*W1=9{1n>;RR2_Uqxa~xbk=uko7hyBwHl*voAi<~-0M~N4kG+g z)yC6;z{E-rYdjd$&hbuo(5&7mq-fR5FUyj|w}lN6#v~hvXzy>j`Vc_TG+d>~=&<=y zBc5zmuo+1{BG1+ss`BJe92sk`g1QZsUzsG~QbFhlNjgj1c7hyLagZH>P2!kw#Y1st zLa))Y7f}j{e{LR)Zj;CMCn98~UldLU4A~xQ996hL2n(S!4-tcJ)^z)QqeD~qQj}B2 zv+jqZQ|no7wIPk-IXKVI5&%w zDKl>>k{o$!kVv>8mQO)Df;a3}BR!eNfu@Y@G9i3p$(nfBuz8sY^pp@oF&;7nkTA;i zpzS8Qzytmev&M7x>Y+%1p@Wg|$t;l*2e%5Amv&BHdVW`li(Th<30Hsur7Vn!V9Ie{ zc-n4t^N7QVn_Tj&rRYRUdXg^USdI441R9$czE zoCN+5#VGWO9E{;=r+q0CMhhyy9G4{Lr;M!&fTw}+yVDskF#AG_Iy*i}F^58F=UK~K zSZ+ULE{JB=p0XNZX3DQrS=OEr1oQl>vvZAk<-1?{7>qfY?J1$u=s|1u<;C?@_qTop zT_}n2*%F`opskm=+6vR#*0_C)aA3=0l0JzMG`3noQo9a4NA#-)%7X4|kGUjD;1NoV z5f8hSP<#1+#*-&i%z%TAM&Q43TKSPw6NmIW7-@#L>5Eh=he`w*St-f zuPvqntY;K$5n@DAu1P~2r@@FhSTDJfOP`Ws$w%C;3E(E=#>U3c-2)|w;6(9Dzl7g` zO{Smv8*8n4RYSAi<)zS}=Rz6Lj_I%anKJT! z{&#-dg@Il?g{J8upvlorH=A~pv(TBst{C6^? zfdMqj#IN=0)o^?3bauz-c%fE#;(g1kDw@*=@dWDv`W9NcU;6=|G13GBlrlO?Z>V5g zME1B;TSaI)JO2jOox&;<*!dop-wr$trYpc0@u0EDbTe zlnb8E=1Mb)O^3Wsr(N-ezU)=%l^e|UxGOG#ZZ1O3n3R7$;V%mZ3;%Qxu3T_cw3U!) zK(;U(Nn5buG!t#-N}BoRMa^ELm+CDz)mfJNz5)z3?a!ZCtyatlR{mB9??45W9s8UY z_xNR+EUu!PDkjb=A{}#wMS{kY4mN_KzZ2Z^4M_-d5wy}Rk;Jv6;Ws#@51czxm|$nP zeSohFrLt@y)maf^7Fm)612J2pn0il-!ttiDS?c^RJU)Z1LAy{TCEf|dgAhX>5fm+8 zmxO?y^kg*ijop&SKizN_d6L58Kb>c^Y&4ep0-580v*kv(*Mmc6?hWOGbdJckaRPP5BmmDd&&Osk*uxy=J&F0UddcXr6XgD981Rxk*7VV8 zC+JFYs=v#9@*=hKnDbGeh0`(=z7&pF{&UecsYly(9R0wEP@Ohp^)$G6(nqH+*n|UG zGCCL3hb9a4S-1(iSASlQ_hxn{ezvo^_m&T1z(>195G0|#do@|MSgv+J^l5)~Ci{9> z?znFxyfSaM`Cwni&94M@E5Re{=$YqLb{mIEOEr@pmd{K#yfi1zP#nkyM7> zVfqN}2reQZEJ8O=U=0Lh1-{x%+WQ@r?Z#Gt)Lj#YL^ppPgVcz4}K8nRKs; zk$@eRcbqN|vY32h5gm^)$jHcmsEc@VZ1_F#r>rf=m?yU1!&C>XrwyF#(e)ZYlfTn9 zWDE4*>hs{nZ7GHb=qRDUKb7J0(Tk+6Rie$23Gt)n?oI{$N(yADIU5;1R;>yzAIQ$t zq4M>z_cG+c)2H!8uuGZZoDENx0Qxz8dvJ8xKk7G(dvTk>3y!5ikyyM>0U^0a{sl4) z#d@}koe;ovhyMqoLb-NOfpuyKlg3$v!D)|%kHP7%cvvuw%-Y(=9#0Aa*Nld?a0gka z-Zk5Ti?h71u&-9#36J1wUD4{5cY7;6p_|c-Vu5l`vG#Lg%hPDj-qXnY?L2<(VH3cF z?Cq~R+9UjN@Nac=7wIA@UAUq!UQtq5e4|LTd z60b^a%Ej5n#|Jm0Q9tnfcJ{D%pi??#z=)^|U?J!b=~D6nP}JzRv*}{=}2fjb_c)bo{mijVeF4T?Cxbh}mjZSia7T(e^xi)iygl>dDW@xi6 zEN!ES&09KYfowvFHS%-n!a{}i?Po2d$E7bf(;8^JEIB`XW+f3Oso`LxlVau@=16Ar zyn^qE{;Olr=Pl{SdrzL}wL-wPcl^*#92v>w#sX$&v7{S2ayOcw%OsI4L@JSPJbEG&v>K_DA1R?YEeG8Va4lq-rCmaDD z9V}cq(~q9XX&D8F)Z<>zhi_Mk_icqIJ^^Y5f2H!Eagp+wVpO4 zc-|@bKfPQ(HJ)$2d(YdGZ$)fIUd{zfxIvqu5>W|3mlw$(u0mpL6S6oI>I7f+Xo>6d1_fN&W4JdIuO)5xuLDE zx7U;-Zw@fg7SS&lh6Bfx4|5K|ah1U`e#7U(!5mdl1J1p-D?@{atlNcEkaaLFsUtoC z4|TZ>4l)LQguR+H{{EQvlp5D!_=eH>p)cr){aak0X&_e7O{j`s{-AjUKkzt8tPy}t zN2hudn62>bdB5lHp+ssCXg!en_|Bu7`ml%Hez5D9>S4$dhrtICWU|v?+u!Nsy-DLV zP%Q=+0P6wC!~AWK?Y!Qe&uvYTZ29Ns$@4t~*JM&2|-%V*rimP)H^XH_IwAZL}tyxm;kSC zk0(GC-x0{9_lU5QB?pDx_2OotO6um#oj6XCURlHoWNdjz06r8^=puqn1giy3>zV9^ zNjH~1NKbc|3x7bR>0w7W zGSk{X+{WU0ytSamU3mX4U-ZcCIyE%h zzI5@T{Ad^}M4bdqBkHedr&4qmYkhler1#P7RM?u-b{rL+^&rZx%`Y9zZM#cioX<*n63etQXOp-3kDLf>JQ=TzAjH-BD$b5UPl;Lyd?f2|EP3SNfw>ps^%LTrXbb+HH2k z(a!hAUzO1TYRCJCx$UpTNA!!aTG4{Ki~9yy&98FG7(W{^BlQUllJ#kIud5cc{%xis z4w==jn58t|sluCCW$JC3}X4E|L%AZ}wRNq3>QM&>ncZ695ubh(#pH54teGcyX z9t!d$xGJbW$C;6jgl*43&iTw0d!%qw$aq7d1mt*HG4uU*^v1OV?-e5Ye*@t$Y16J7 zo(sj6H*<{QmFe)Uh*!~&!gN5KK+r#fjgGN`5^OL72TgJRrI<)H;@<-&J5S;x?geHj z+Dss|Nd>lFfXg=YL$6KRgT@XPo@268GornPhpYy19ffa(PEhU*o#5002b+aH6!{>K z{0<9sA~Jo$2wvzE2yuMWAtC{A*)4VEO1v;{9a4S-Y33F~u&g|XD@KN&=R-@gLHfL1 zZ6Cwo<%sq>3n9$|KBi+kt4%*28(=k0Y|l57cH14fSFLN6#rta?yevI8kKKu0A4ADM zGB_VsPvh3nmwrFPar_8VdDrVUc*b43o1fP@I-Q&vW-F`t-$a?;M3lg0spZJ*mN(t64AnoHf6H=w zor^Fy_ZCXaj4cvY4fTZLo0`wouQRQs{^dq?fWaz92KSE;o=QCLLIw?Mk*w8G*5pdw zb@RLlXOh@t{lTrgsrt=us;D=#GB&2)6}d4qmg^bbbhFo}3&KTDp_Tns6UB-&)lOpp zk1wv$VnY%uFC@Y$d%PL?R}5|)qpr^T??3GS-_Y9LhkBi}mIH=0sGM{KO-70?6bi;1 z00s4eXiGd=L&4ob)McG@Hfru=1*qR#Ps3U?8;|Mld&e3}Fj%wb| z@rL&7b0+rRG2Q^UzrxP?$>mwF03JJTVNy-g(0H~ow4EdlLegduFE9vA7iDa3M|<9E z0f+r@WK8Q*-ltUEpsWnWY9k1r>o*YzXoMObEc4JEQu^W(-o9Zg26bj0ot4mCH0g*u zT2m;iN^t@`ewOx(?cVf%j0x?-WLX?uriQ+neN6DX!~Sr;eT9?JBteX`q5gWwR=OoO z%NO^pUe)Q?Gc8piU;U83K%M)AWGBmW_#cB+-~R^%Jm`Yz+i^S&6;se!r?mPkgF5A!eQaqFu;dbD`sR_CDh7 z?{tZ@J|2J*Jh-S|RRDn_^poDgCF`~r;!D(Ay%6bFt`9P*cZUPP#&sc#KE@y1g+<#5+BIRz>#`) zl~^^jq-?wv1R~Zxg`%2~6jCV$P>NTW2@<49S85L@CpJWZJd=7r`^ZMp1%jk?x3`F9 z|3bK~5ztp|LWHvw@#ZnaGsswfnXSV=C$dFxp?%dmXy3Vi{mfMK*hIz<2*zbsMB>!X z4RiR>ns)H~X|g=rCOVf>KNW#st$L*qQv-2linvB|;L`%K$i*Cg@D}@-pxMcFbQWqD zhAdA}qKhH$2}%Hq*Uo;Oqb$M0$q=pH!;9Kt6V18X9v%qwJDq6L|H`3Fwu_K3mTHU* zS$=>T8@xqI+*+l7-D)LwZYPg)c-_+Y>|?hw^4k#O-n&3vo0c}sf}x8Q(A%>Y-&+zL zt1(`x#%pbXwDsRp##R*v9qoT|J!-ENdit9dT=D4KVhAD=a&2qCXO#hbtPO}P5af(NVC=XGIE7h+S- z4S`+ujyiN(;3TIYOJ95g14VKpYyqTeWimo7LsRV@Qk*;?BV? z4ecS_?u$wlEu8F)i^MKq2@pcxFi_j4oRmVgtv~`EZG4Ojx>JmDHr6^s16iG#Ih|`s z=p&%omV}!T%$KBtwvdabc-({RrFPO3)9R&k#}u#*O&>Jl|nFLuOBsp*yPKmB?Xp%<<7IPO zAJiI(QOoPW5yl*y4DhAv>%!jXLsQPCK?sC_j0BC`zNJ!8b^0ST#kh!s+6ZM9yIFcU zD{i(ro*Hw3WMp^tGJm>W=7Xw)mdC>g3c}CrQu(>F-ldD~-Hq&jYK)BS4(%#9grDT6 zi3ia6@u|0Sz*?HE&V|O$Vw~Lk3U+Pc_E6u85{A#sr<2uX$kPK8sE}zJOvKfeh!&(r z!z`%k0H8P|ai2Zmv-|C%iJ&8gUd`UGhx`7c+k?pcgR{XS!v5RcZd}1>p0`=%fo{~l z6QFz@A{!1jn_r_lFT2dSt(l|}vzWczcv9L|bZf?0`u5+xT+BM&%$nV8iixgVk4|@Y zuI!(9Dst@6sC&cUu?!lW9`;_h;J*IGt&jgk?o%QAaRfb}oBAJiEt!9t7^p&DtPYW8 z9#^u`Fo1OZ_C|-VXW&+FFGtntQv20n_T z4IEW<0;us9$Bo$k8K)vbIP$0bJG-P_PoEFH`SLs0(piZgwD`wUk)*GdV6wiInIdaC zXX?^qLB40^U=a3E<=p;4#fD`{%Cj%)4G!sq4x6GA?3ES-g#eT_Wft*nAJ_oR7Ap!u zZ)3A0#Gz%MeziPC(r7CWd59fm!Yq=3^+u5+y8MVtxg|v5)Gh{309I%FU+Zp>*bAb7 z7MTz{tO4l`9yiH}0jA<$zW*_@LCQxYo-|p0zYpVYG5_=A?xy#$qZY_$8KkHi8jLh( z7_u8^nC{SRy9i&jHrs4b!#gX>UuzGnrWVcgGUDtTO|>*cD(YxE!Yv!gOpt1O#3^udg4Ar}2Bs0Fb6y55 z6^_aAtJVMo;9n5Ne^ihn9w3XS)YE&rI4pYh);yfpv+02+g;o|G{bE%9%V+Z_aq;ra z@$kd(8y9K%#iJ}9S!SyNuqSl{@k6D$nV1#tAdFU4z0}G$e+<3 zbGFjICbSEX9hfJ5QRh!j$9g%8fYDdB_u^}gApOY`($I3nE(70Bey}vbxL{cKZ|cwC z^b;7;^cD?pI|sq7x)|834RML(2mnJlWuQm@?ODpkr0(m>h!-}pc(9m<_h@WMFgI6^ zUQZt?B2r2zIAAdx(Q9{H(ccpq`OK z9|M054*KiD?8}q7+YwaYVgKVONT)y9QPt^JZ=>1i45i}Lx5i;Q%{A&qoc45grvisFW~PRw8Ij!vNC!*KX5!#T4pcY zkaE@0-aCQZ3(a)=ba!%XpWdr#`Tnrk2m|5hZ5rQ;J?Tuq->)dlPopHXgm{eVw1&1C z`McwG_BOUh(to@^7lDBHodHZX!z*X*dFPc^WKuf{oIY! zlj2qV%MdT(k$;&^hlW^B+KXQ@c4Gnw!g|7f;MMBGR+~2i;|3f=)lQr~A z%=7ME_3ln1bd%x31VzrFnJg>Vx-w}cNFMdZb`kQyvG2{17Q6l~WD zQWe@<2XFIdE$=9w?ROiEM1(nS)r2j3{{G-)(CBgL@O{*wWo4{pv6Zfifc#M*Z{12n3wO^XP(82H%egzxkI1O=iLr=eLa?NT(xaiP=7RxSx~fkkIbl# zNGSB8=l?BFjt)^()d^-)7_x+X8cKm17uSFclo4o?9!{8UUdT+yld53u$JKQ2@{(T9 z$KdZlp7W{=*juvFvDkw=uDSIoZE>Y-z9!2_w2VZ60_S*5&npzejmAtA*WXWt`bQo2 z(zYpUosnd zEOq<<0*T5{jOT`U%&p;OlcAVkxPTT7f$3>Xyv05gF+DAuQaYY6UEdc3!wTztY6|bn z>;dh~Z<6n9^su5vk^Dh}scJ~6GCM4F3bZ%LU7H{#t(Ra?Ngu4PP!%o9YD#W4aLv&| zmlhZAS5+zhzQxaF2gP7qCIRf)uY=(U)%qW8nhW!<8D9Croqz|+chxSoVM2U=a%i>S z0w+dH^@RVR_rJstZ zf@R{We60^Mycem4PErM$xN!a54a&qP{>ptv>y-+>B>WdCZ@;~w*OSP_p2XXYm{#r< zL>FU3nJ44$eBeL8hI7+;!5FtP9*CfPYwrL>{M+Tt)%AgJm2Lr#nujQz@R_}4^gp%+ z1h9$g8v7HP5A2o^>_sOzxDLu)kU$3w5^Ged!_+v3Y>MLUC#TC7uW zB(p{*KmNa-aFukyEXOVLJ^6AH(~xKiDy-5{k^ri?(E23G!akzlX~iEl&mPa~?9VN# zEjNQsNX!I_d|;A%9*NF4GuAWG$lLC^P9VAJ%f{P5S1T*%d#&?)9)&_dXygITlW5Lf z#R;<8e%eYj{9*A4+%Kclr;0%5RDIp4x!5p|+0?vGFiQba4;X{3)$BixZXDg8@V}py z;)k+PH?yjPN{X{7lBWZtMjQ zIcavrklby?^S&zx6fvM>Jb`s`o%{T~4SKmotmElJ|;JC^m2_HWDh$C7WK z>aWgLwHRb2?^8{Cb@T1vr}xTie<}w~sAa&ERoRnf?P|<3ZQQV7J!`Rh|NHM92(tSQ z^szIikEIXovUVL~=ApOR8J$@-x(ypPek3xvG=M;6GO4WE0V_`AN4&bB>r77y{>yt(|5(o*eZ`6H<$XMrk}1vB)s%r#1SSm}%SM*TDIz}Q zh+B?}+<+O5%}h!}>pMH_ZZhpIV%2w%#yWgDVc-n7i7E4tgquKON=2??AZYV9E#Wb- zM#%?WUF?3D`3bYu)S?Npr+y3 zn31+59&0H(k$8*qzeeJ#p0Xd+oUz&g&?hWd>!zoFRZEGB{eVcw%DMnyVEf3!(m@9E z8Sy2fEZ}X)EY@OlBC{4=$@*nx*2?VH@=2;v_Bp{e#~w1d0Y^LuARr77_e|+?*A&!o z+LCgt((~~$C6@)lWlQ= zH;28kUCv!1k#wmC#@I$o3WkJXV?tccEZ>bWS)1DRXi{;c*x_Vb82aXII99++LKvcu zWF*ox;gU!q(<8(Z9>t_3I7NA~j?nX| zU9JO_RcdtM&`kJfAhBiNzC;;0cTlICb*%^7cj{8JN}f&MoDW4+EVWM!pFM%Q%kiJ0 zYsxYfaLm-%w)c+Xy&sA<_nkQQj@7;8ojsY;yYROC{cR#D>ia5$PRc$2s`_g}AFv7d z;ePGRZrv@-ewBE;f1AuD@^cs9=2qSA zLb;gABbFsz2fJ9j{h)FykkDyJ{mEY!da-GGW%Gw`Z1LT9&K%lqxP2e8<0s{gQwMj; z^`^S;Z=dR_X7UanKHS^yrIJ4URo+0IWWYX5@5lQOkpu7V?K`>K+<$t@9{11QCiU9B={!)U-W6@J!!YGi1Pamu)o$voVhPbtfLH**lXP zCUueIm3S7n8NlDGYkxIzJoZoJrrQq zw{0I_XpOdp9!n;j1j8-PkaeYO@UNV*sWFyiQ&~POR?Fh}c3@Iy?Qm?&HQpyP2}Q)A zJn#(Z(w5-$l|^vs{q9(++sPudIK$Sp2w?gC6^n3e)irQU5*Pshc$zNgE8qc3W+0zY zf~D&bn|f`nL9E5?WDQ!Jq5ryUus$=Z=^yM+&Y2{Ln*d>pS%9cT8RDL_42e0jU8gN^ z##*ON#M$C3|9w?jpWLh7X1K0n+ChLMa}2mr13YWnM@GP?C%8p0Z_|dvS;-D#_ZBML zJS!8phU54YQRsjr#5a(KO^Q61+lFl!o)P$QCd=w!-C{|x6YXjdW3~fyyZ#t+75v*@ zBfhMn;jS86wvC7zc-UovxyUdSBBkRaEh&fO`#;kx6N*u!0Oif|C%QYH-Mq)^@!^yVr%bM?H=|N@}4n!z9 z71#$PMHe~LW)8u=$=2wajV5a~V1dSwVuzD$;r}$xuoA~K3}GV2cQ_)Z?*cxlg&fC{ z5<5P%RfE8qlg0HN4Y8!y;bdFbuI9HQ4l%I@JQmo(hy>h59>E?Wa780*S5mp=MFD0_ z@F-(Rv5gF?L2N;62v?kdLsK~hbOi?mnEAx90m05$31Ox2lT>Q7aCNpgKMUdR#y9sR zbd?~OP-XpeZc}rMLUT`KXDi%&TX*kD0F~|K@!F=$+5$poH5!4g%L@NoI5=E-@Vsz! z7Meg*E>!1Pi=!inEjxE4!poN`Wr@Y_uNBWegDTjek;0Yf<;iQM*@&+fDibf>g>ifj z-Lv7tM_}ToW_0tU8d-ksf^J38)s3ZrF?hz*Kyl%5;liCtf;q7>d7?ilHty=rLMM`p za>nr!upjoH>CK(M_M7ghUGDzWzMZ@LlPCAu2Y$xFY@Cm&^3=ZuOo8;OU;=)bK6h5NU~k1)gDA4mKRmbl@}FlDX~uUYDQg zO{OwABEcN$3G9);C+Z2Uw&MpM zoXq+eA(81)OtE7@g_5nlrWLm<2PoOUS^}utk)e7SrPtrxolJMHfMXG|SEhwb`)4X2 z_GADQgi<7-bE6hIg>ddzKGslO6=QwMXA z^HTO10O;ZBtd0W#aC2%D9FrqfmrJ{BxEd~u4nW(2?kc)410W=e*P(U(!I$~D`F{>f zX><*EccnM-&w-m&Bn1uCraX`0!oA|cech2-xOF+4cnCjzZ3dhibuFy#c7ZYa`h-GG zxEPKs6enj&vv+kns{TOf=_i^OQbT0whsXCixW~nZfp_SQJ|+*^j+GMoayXZKqw00= z-rm$1z^HFl>s^0eTX?z42PZ;=7z#?)!+T_d^tGQm)Ij^D9(xpr{!M0 zwN0jb)g3ETk8eo4CcBgA#2y&W;lps35DN`BqVDx3zXJc& z!hyeDUHQo}d9?oM->Prc{Y3SRzt=sXFOy2Wk?A|d`-Fao%21R)^AG)p`rkhy*}t9^ z(OR@qkgu~dDqqvX)5$;r^*ZMl0yHpHqlf5Vr`QvmbM=_U=t|7 zU;2Rc$OW>oK{%mI+H{R{Al+n|U8dcT4vb?RJK!qxY{3i@o5(eM7&!Kk$6SF-OF%)Y z;KXy=8h9*;bi`FC8OJ6#YQF0M&>gdpCryGJ;`ye}vFCeUTSJc}lTO^I7ACO9jatA{gm~gkXdDYhwM@on)`ryGCmV#!lI-iVf0sPQMlzxr&L?uWI|ItXO2+ zBD+)bQr#0P7VVW+Yd%mhX3CV+ShDi-YK>U+Yqb`vV!z7fOIt56UMp=^4P>87ecP}x zGHob1#lk}z8xAsQVELBi_}GYJIj~t@@fd4+bm;4~P~_(MIL5i**bcI=#E3W+LC~Lp zFk_4hJ0K?a<4l$`K=#FvVmm1+j|!L500Gn|B3|GC6Q7A()0D!n9EiB;xyP#YaljWp zq4ygA>0WvWd7hyW08m%^vM~4+VC+46qJh~Xw(Y6!3X{(?(u~Sg>3YB@c?2MU&|&-a zK;ahD{b?AeC_|-vMMY%>DXEtOgFQ0GVO~&pSPT24J7B}1MTHX<*LCv_obnEIi#?tU z-m)|OukG9Z`p(l^ypwHL>{ZXyKAFvB`efxsMW55v-hyg>Q39b;ybliZ?B?w2eSMie zT@uyc7%JgL5Oit{s?yZkKO!JsxLdmaP58-$(mmissLjq~OLqVUG`HD*y?GXP9 zxRh($nFbLm&&BIcrbLZ@;s%rkihzJJ9}zAbgsVYp@cWexm>J)Ha zEE{oRTF9mbAxX>;Zfg0Cz?ZFINWyafFVPI6D>WCJkzj{B_;C( zF+7IXm@M05kS^AM&KpaLZ5;KsXPVfQ44FQ*5TOKUS^-~8`UbZG%7syP)Yqgx@(Xtp zMndb@%Yi{7p;c^o;CkuBaD_dznz7L^6pHvZT>Kp1!qib*fppfsLg~egaOiFXIQrnr z;-!ZG7psBqhDJ27qSjvpfe2`f8tzqHbVYYbm%@oz<#7N)sRpNx@;l0P0N&kl13-TM zUga4PAL*xK`f9C$d>5CMbl>*$yDZ!5*!%g>L$Y_@TS>B+?BD(?V~~|qS9FR%w(5h{ zi9fbFDBAyzKej&CCdd1_VL=i>pW1RFA`E~#EQLh&`bnPZ*V!2;dRBolun+K@>`qDz zNT!fMf$23@4_?+S(-J-^)HYGs9t|K`{&W>AfHN8xj*VQ$;)n#8BNlir60T_)j0x(Q zYn}#m%S3j-E@u(j_AE}JoY_T$Ngr{`vXKx0$G+(Ywr#XE@K_S*h+WRH zsAW*7ICOms9c%>gz_pMdJ~v(00Kv62^jI?Ki1u}L!}n_em{n|5d+=pVzaAUBtT}qx z{}S*r!!&J4fGP@mK7#gV#9e_A=7bpr^Gvn|(WF*OY}F@gV(ELQ{I;uWW>XK&vTR9g z<{~Nt024q0iTNhtzUva^NolVI+^8qpP&^e&ifwpsjC!`inT>40fQZU45M?g#cDj@@ z%f^mEJ*;|g`MG(Zom2R)y0KE%0+qh{zHsH9MjMBK!ZHja5#G68x;<99cP<>cRvi6V z;qoh@uKQnyQ!^1Jtl9vHeFwGymav9~*PWDgzFCx{(c5+F?p>O0Kw)No7cNdmK}p$V zD@+YkQ1%NKhcqC41&SV0OZwL@UxJL#+;~wK|Ee;z z_}-kG*?jW#gUMaNJC?uyonvRrt;hRLoqqS^9}nd$Y9?FpA+$5oo$1P+?Czv>u6LKa z=lG%B@18k;b6LLir0cvx-#m8u#J`6KI-ULAZEwD{+4FGbq=`{bahkt2f=fo>t1^wsl7C+8U%J-XeCsb!?~oq5JmEAoxSguSaH&qU=#F zT2HWbJOM@-+G)2BY)-z#{omT}DEw#z-+LMG!F5rc8Wm~Q|` zifaX?jf1srZP+Gac3HR{#WdET+X1PjZJ4evgo*6Hb^ySaAx;brVaE(C$7k5`L|X%o zC6P8_0$P>1bX?cR0$~;aF_>W?3N91pmT#Jt7hu0Vna7ezCow^bOv`0{#uxzroo>l49_8^ivrsLt~#G19t0dw%jei2 z!Zb`AW3spbraP7tJDhBbt={~u0=my!+d(o2EJQqm!0{`Q>9~&Ym}yEc&dGus@Wf(C zvBSx>*jG&_>q zLj32zRDS**WI!hu!nuzVe~CyYfi~^t3m{fhWQg&Tj<4-~lb-SWUf(ZY?{(jOE4yp| zvA0q?_8k2~r6!KbR4RjVeMz3~PRaFVU)X_Td&Fy}w(Z!?cW)(cCFMc;fO#ry|DmRs z?MFncs}JhR@Jb-Qoub{IXeXM1v|bK+fd->y9$ zZr+*h{X@+xnHI@D>F1E>A46S%mG9(j<_!=vMf`DF4&Nyt%1jqNE?|#R;_lW()LZ}Bh|&B*y<^fHp0}k3H3bY zQP$SbW67iwf3(G!_j~!HjVD|U?38Cx9@sX*Tv`Bd5+Lfi0g{f#rC~`a)&lj_f@bw) z8nITeLyxqDtedAqTw)845o8L8Z#nZ3bGU_k%b~u9iDMaSMme_Ag{}J3XDlgpqE#*2 zjNePE8k~x%QrVtmI}WiCF`&B077}?r=g9GhVNybUK}ZQq*SYWcE6WWHTZEg{0NNN%%u_; z1fUrMj1e`cgCvod8Md^HwSYIZoN2vM#ah5lm1=cSuL5Z03hCJl1q{Q57J<7slOkKV z!VWy2+5VcB1h&&ftorjWmJ~ap+*sY2{@pB9*>$c>?s3x>W`H~*3`9(mAWw21xf0+I zZR~Q%e+#0cXhc91{^@3&fenksyv?P)8897Sf@bENj4L zh$Y32kUCe7Uw$uTY8I{3^nwN!VbGIgFvmf}cR6BIN@P$hnS@LzUwhxWT~;FyU7R)S z6jX9`X*Y)}qXsu5l}KRIMkF8x@+1++VHBuk41>zGeMh&;A`O==airKrf)8WEz)+t} zIpE21juG`u8#zP@i) z{O$^{JUsge5I?4ei}#-cF?~&{t}r{9e||A)2e~>OK0FtWJdD^ZB36hCF9*&-=c=wV z)H%wB^Wp4V`01Fg+SQcz72pv)_tMvc;mB}dHGWR; z^`pW)erR)!zej^p-j?lrTkhbY)0s@09jKdyK>N@1$^_b+&B`7xbq0(=V&9o;PWE(d z)qC`>iET26wj52S#EzWoscBZzyL#gW*u{zeH}OWM?{)4UMZ3LY($6J6O8mD@6Z}u& zZ!4eJwF6%N`=(8A^ksUWlm1Wt{Xy3o{V8RV{`}X(-}da=(x1&`di0b2{{MWGP~-Za z#D9O#1%LfD@m^Boj=~G-C%V)>|C-n?lif#i)tB|Z|C)I1OipI?*LTWvckXE7AAc=> zW@|Fro5{*Q{WD6L zd%KS9>^XC0M{56x{kb!Lys}S`@!OyrXgcY)``+%!>^pvRhkSEy|F+|M=m{Sm>p$7I z-bow3J-vw(s10{KSWUyjibU?{!YSb^5o`WtKT9`;PMdlm=K-?a2{} zBIJO#N`104DHes#gN0P@snl^Tuu$cPbW2)VL^)*DZWa}oW>PPF7<;NsWC38nr zo*EpoOg345c0*!X_pq%Tk;wRoG(VZ_j)qk2wc?gt%VUTJ!~>*8j5w6ex`D-n@0x~3jP?c|MD#Ct`1LCa+KI#;?qiP_@Ww$leIB zAPzHuq6VPMJchVn3}I$j#P%&hvE7#JV=ZbYvTxxP{H`1oTIJUwEe@zlGfRh2%@s&<&tX<>7kcZ~YXVXkVfwrt?f0`3yPsSze+(jqQiqh?D_mNj%} z#FAo%lWnodn_mZdoEWZ&k^KMId-E?jiYs6IU&$xF^G1Ts?%bEQta+^!83|-9V0-kA zUu0%vSEB05BD1O&>^VnZ7GaCU7-O>;u+6~OW-(?4AqhC={V8@=b?Ya;KjC|BL@tq; zky%;Q-4b~4dxN^FA|oSiMBKRR=Q_v(uA@V2uOTAMlx#ZA4ZU6M~ z((cRr5(x;wOJ5LUf8ubUvB6ufKEb*az(GMw{-YPKCLzQhJ(U2EfBDAv>SNnK`}6o^ zK*rj<@y_P8=U5ariafP>?eg~1pFkOww9579=H=15Kim4#pWvs@KcG7v0MTCuSgag+ zA%|K>kr(JtHots({Md(^*Pf&32NV(kBBZZf-u}-|DKTNlDZ0YFfW2v<>h|l6>yxzY^c^ddT(E3>%54!{CM+f3Q zGQ`mad6pC)`0>eJJV4#lUEZFM>DrSA{EvyQ)` zZyZ&QDC@`fiUdh@j)lALUbuVSjoT1HEfRvDyW^w81^+!+`7!ju3`LeK;6ZPiAfL(iogne8sekJVJourygzby*cP%Yu?b zhAcX&p%@l&HN~4~BG#>WyMn*eO7fZ7F3%XZUEjy5rAVTNWWXcU9nq3}N5qPYbVo5A zZvtp_EgbS6jg)7ORUZ|_EtpMiSpMV zkxXYHR`w*pKHckemNLyik_n{oecur^-BLwMN4jWxx+F`QkEIDPiwWWN3bH|)72OHp z8)TyC2oFNhovDouqEB8|!}Cl}wM0WP2?PzqPr0TjVWi5kBvLuikx&xkN_vZF<=zQV4U?l8ct!vuP=)c9K{g7O1(BkC2A}Ex{Ev z&G1E}+8X##Wn^J&tBya@g0x!coh(R$gt!L_Qj&xwEQ`u4%dB!C6B4YtzNCAyh%CSm z@&Uln@vtZvk^vwdsw+#g9hx?azmqj;kXQG9jX+v0tW#>LsyRQIl$ujswy~?4kj1S4 zbfzvDqNAvuh;?1YuH+dOvRYBT0u|e4Nq1;s+Aqha*vJ*-_IJ9bLhqhXE7EHzh=>3QnGEnSr#T z;tnU1hMKxATdgKr$$Dx-#T`zzfnoA}X9yYK zk`)z%TtGHMq668w3YH|>K%Ql56aDYHJufSjY>_rpoWanHJxfKdrirehX`<#Ux@ZGA zWm#4wN%u{Mq_^aT=JA!EkDh*N^!ul&H8wNR(&%n5&Qb&H%~zqa(Ulj32Uc+ec^AS0 zK;Xv0f^a@{sS69j(wUQhKS2z-HyV>CuZFtfoOGo`5e5dp9+RBlY)19SG?`*Vj;{FZoJJSYc?nxjnQ%IY^uFHfd`2N`X zAW4u zXdGTJ`_j?-mD9@7F?94ROa&q~?0C;`51v|Hcq=wo_2v|DDsLnWj|yHP7K-*rAKO)Bu4GCI1an3*^m%F znJ5UOU=Tb?XpUELH|*mG;7N#`m>`aTaa)Mp2;*Md2?u1s1DCTm$XlE&96;!zjc_<9 z+O44p%6?-eu=ZUVjPh%;=!=b!&AwJNPbA(P1mCv z!sI%51#=gZ8rkc*WA{SsbWITa_a_~r) zzgs>{OCISr!f`+B#lb30x+Kjz0r`=t+5#DW)g3}g!_xh^;*fJha}i*>@lpwb0XQ7s zO|%_6&+L_w+l0e963+>QzYM#yWhyHR9)^EJ+x4OoTm8w?!TMvZ(ovD>|0yh@N7|NRb_x*5+)GtCdAvo!8AOlywV% zeR}^$?qe`i#X=@P_W)9WX8K4(*tA7Wc5T^KOihxcnMU4wqK`X{w7vL-&1A z^RXg22>GI_>c~|s7dxss(>kC)Q&wG9HGR=@0C>*Brs%k`DN2^@ z+sH$zC(p=s+AQD>jdu-~0M~dY8K$qOx&zo;7;5eMqU~6UsCud@D~ji8irQkbrd&Zd zYeU7ILW9?{S?*-9WSg?(I-;SufV$>j5&4j?sp+m{x~hY0)Mm13B~z#k6?Zt<242R; zweegPD>76ebD@r`*tFGFE31`l(5@a*L4dR9$Vd~BWLikao@;nT zu$5BnK`^|M@ZZs=A8*}w9t!2Y_a!aZMr-TRz~Awo-X33mcl6XN5YhbFYn#{pFnaPY z+mF3O&DP_WKcco}h}wVe52F`;PHoFzCT4J#vue2zdHnW&jsE;NwRdMl-O?h`WQe0D ze=`2%N8@Kd`1{S*Xd&KP|M92IYd@tW48idIDf-|GRkc;=bi>=%6l0C9rAXn?>PAX#?8ihp68_ zPpbZsU*W%nckP?2wuh=a#0;4|l0eJwir6Jxh{Z0BqgBwSC-g$lagz@2nW+P21neYm z4^m>z5vv7#oQC68UlWD{9Pwx)1YiLX!anYabm`^S4(GH5dH56eX z0F#T4TxusEL*$KbIT%39Mie+h5)7ZL+n(>G6q6*`NTMjfaa$yAFCb3(IrWH9f^{P7 zFA5pH#l_w|Y}@2nA#Dc+#)agIBmgz}lIa0vdX!jd_GqTHWe4fImpt$GzxbYm@#?a$ zzS8Z*--bo+3gvZn=DfWzTBX(|blU}%D&kzb;)i^L4KpPzAs+fcZjayLGpzVc%@(SVIEu{-=^(7?8nL9f^e`K^e~D} zM92%k5|?!ID0x_fqbe>ZzmXBG%j9>9eAu8L4El>gKIq9$^phcxPiYqydclCwXc*(@ zTpzjkf!>9foemjP43>ZRk4S8^A*Iazt;d$i=|bEplPw+MD$siZW2G} zb}0#??*6-Y3w59{$b-xIBfYRkXbmRP@Dkl?cO57HW4;#iF)8y3`vXkYMSb!Ug~BTR z_;48XiofQh>Z3tNpxf)TstB?=uM3(a$kwXe6)i!V*F{Uz=Ji$4?vg)#P$o9h!%Esi z@?ieBK%ZTa1bfwP>Oqm_bwQPA4>OIS(8Ek)(A1rdq0mF^7)~pepqO1z6Z92X5p@Cn z5M_B@7vMjg;=Vq(uuT3s2t4c1onq^ZEOG~UJNxz>GI&)n_L##%49e_Wz8kuihJ7xd z*H}KUuzcQ-3w&PrZ^`E;QFbkiJT6^jQ(`GATP$f6)nI9>S-VKvCFR($rCCXHRF|Z~ z$G)OhU}+bBa|wQP3GT`z2>chXr@Pm*neD47#qD^PWGG8IJzfx@A^K<~=+d@J{XrND zfb-Br8*{=ryc}ZT!pS)yM!i^!aTNIZb8+w}y(Sbp|8gxq6{apRH^9LK7$9e`vSBog zmOVTLMs?u!O(0QEnBlXWA{jA#zigD4g{hR8g_>Nl$x@XT=lJ2{Rzc3CUt5Q@Bgu7B z5r5@*y|^bB;womM>Cji(rMEG+7?+Z*DVWT=LP3dgMi`NZdTu9S(kp@ICGr6k3R2qM zMq#dK57BxtcNLXW%#tq9J`eeHvbfQDF<4&;L5&JlT zZ4Hr;AS05koxJ1VLPsuk+b^$L7kd?D0xYCLH(V3D5EIyZ2nHQA5SD{g91BP8%7X}F zA?#rR?D>Hk3?SB?9GB!+rNf*f;a$T4rf25FnG2#91*^CxtcB5~Ziu{i{@_A?;?_en zSdq8&5a$p$!@(dAc}qTu{U}&L5!vg+;+biBaiZ$=hRb+Bw7n#ElC@ZK-Yo#4JIjd< z2FzfbKyU&PMf4!7^+Y#_T*|nqQrHPR4^v|b7`c;87L+HrfM%a^jNP#3RVH0B>T)J_ zY`x!wm{k&VT05->E~}kRh;ufII{;!%anBRuVZR?ngOXNFnw+CQVK3WxBvKXkJno(; zo%i$wq(9RBM7ft%d?W3rA9TCvlOiqDu}+DR99r^EezVNp`D&URQ0hCsoM!KPB?-AN zb)8&-Etp*Xx%mR>N1-=#x$DZDt(-~4op8-X6@9qUdD5>4UQ7p3CSIZg|A7@0U7`~Y zR+fc0a=+c^!xB)JN^A+S22t2sK1Af+1la3tyhbaVNVzIO8hqjdWn~01bw5E_SxLl_QEwG-&n1vMZ%pgzAg>j@#pqTdbCK*;|TyV#$=FUyR$ zyc}$28M`|2!0FRV_a7AwUpRjJ*jet87lecG=%ItoA$P?)xZoTjkc8k12SV*%pA$Cb zghvm2>)--uSLWF>PPoMlG1H9C9-}g&aPQLnM^7DVoN`jmZT=jBh?xB``6rqahN}l= zoovb@$*^Xa-l3!4%h-N$D$k$N3ST^UUp@P-p0rxlTmE!#A<;1y#>`PW(p(jgAV{=E zTh*hiaC9*8aKyAuIuzjtq9>J^cxn05_LlV>eODC|N9iQs$U%mt8z zV_~fmbTP0Wa%F+6+>fvu#PDyp9HEsJ_#U3>3W$U!2zV7o8|n315{nNmP(s%0a>&!hR4}gRqMeHLofsLZAy)CX0yNGuKfMJGm8oMy)MB#9`BOq8ph$O?k!5py;^_IIE0tA_PL=}U@tY|1i1TPBH z>F5?RVHJ1j#!V$`08)jzAau!hDpFU4=wyX&*RuD6UMeG1X5#jTv#jPM_1>78ymn(9otFb7(Yw*$v< z_I+?T=y_cn$AYtwBOxY2RP_<##tyjk7uv2ZBO~{UKaW>>cs! zTi`6(fKv?m3vcBQ4}-2J96q%~)0NMYKU^1g(|bKj6Qn7Tg9z+dK@S*!5TT7&2q0gU zs7*o`<)(vr+Z1O9q>|D?gf>KY1-tlYgS_=0V%@_C^%83}cjec2vec}y#$WBDU^f(& zakzp9(FWAnS|P$1ToXj81F=5vlXO(F;GiC62j3Y6-2rQ(%nb_tpiggwsn)xod?pKC z&^f>ME4~a#;dKkX7W6!@kq?l2X$MCfBaf!H6KU&0jc9kWH^9q~L7Xe{;c(C&4(QS^ zt)gz&3(%Z!dI{WZ4+T9)F^5YG(7YeDLXezmG7>Is^Rxt2}I9=`wCyAjP zMh1KCb}aagHM>WKfqRK0WKFHx3Cy#Tol8t(zKdzHqId56(%JLE;R`2E9X)yfi5g80 zik}LJ!5;4W6K5`fsu%PGBoLo$fFf{Rz{>gEaBZHa+a2kIVT=Wos$Xe`5IPR9eFDm_ z2a+tZKA;=wQmPaJ0stt28{sfYcWESmRT_keNnQ?ySb+Jv7oLu++R(x>9zc>joL#V) zFZ3fE;YWr60IX(v`yBj8W|ls<>-)PjC*tUa*%!x+`a#iGaX1RsVoY345%zI}doG^j zRH%IB7_TTtn_z|}3q^qih@Gptt!sN0YmCsPbC5LlLSSy`pJIgR>Tsp#WU2-F`qdr+jjyIJJp=-YG-KfNBB@;-{f_B32#9B3+Zwg=T;uZ z5d1CTrJwQPB+r}lF?~NJFwm602;WYpSZXlO!iw%^=`f7(gQy#L-~ccBr_ighrPk~<^*|8km*~qmgk1fN-)@eVE!Ql|D?w4gTV53 z^HNRuuS=7;+TEt^0Et1bm)vzl-Zbq^`GaHkpFDpr4YuOml2lo8yJ3tYxFr+OnR^+6 z(JhybvrvF};e02;IF+AM*XX=(e<*bEGIBQr=ZtFAxcKtmh^9m9tcTsISgIr2GsO=t$V}11LJSoPh@Qyc{^FfK!&^Ffo_- zxd%@W)F)~e@M*?k0qKY!DFKt}cLM}b-=G}j7*S=!rX%Ni<#}5#CA?zd+(ldR(m38mSFrN!pN%J0Qn3b~9;nT$*xBa^0{rZAEOMo#)-lAgHlP&sok`+<{}PMXqm zvHGk!eoi{gsM}{Hxpe!CB#jR8&}jx8q>xpz=gPTr$$0sRb6GM6Nd)2N%+YMbV#-{W ziP-dymAs=OHsKcx}=YYAT)yj#8V z(~T<*^1VqmW<7<)#UYu-U-3NSrp=Xl9?P36bKG>s^f*F4@{s1NwE#4%aVStqs(5ZHH`h6k$ zb6-~E*^+G?Te>P5hNFp^ZdfAnY+1y@fJ>bMQrTHNZm zx@^0OB)OukT8^mMo+LW1f<+H`vS+Kd;;T-J$!e-unrtg7&QKj!w|vP`ZC~_d6^WWA znWAl4y69T2>iNFpy0Txaj(g?R@iWhEzx~eUwa3P9-5lL~e)HP1qbDER{@~i^_4h_E zT!mV=zj$`@^Vc`8y+8W!Gy2W;ryq{Kc;c^@Umsn0>((p3AHDD#6rg?iqtS1F^ViF7 zKsDWGE^pp=XZ-QoQOeC6kxH(wq9?5XjU7e>#$ zx%v6ChPZ@xNu@(Ot*NnP8#_FPsqcl6Y=qbpC1uKaQQ+Lz;Je@m0xNb>LK z#j7+mjdw$}yoXNqj(Ibd!-bN) zWM2HVJV@v()&;)x*q5UxpB=q$m6AA_=TjeV{o%21O5lD|0{5E|xO-IscZWa+;D)5B z@ikxt36KGBABe;Qa}s$7d%=LDy=Q&V{C^%)f&eTkX0p|>Z>yedNTTf^N7Q835Rvbz zqG=*u@hsm$?!=sL#hO<&t#m)Ny7;EgR?joOZ7i9dizLxiOj*=a-4$&e8zMF=S&>!U zw=H$1k+&krEZOQ>Hg;XcmTgLwsM?w@YKr5DHj*V##*Xh9o~k*LGt<~x5oIUY>J7Ks zEZOS2e7k*$!2RFh#@O4};zh z2N(dJP`9cyCAK-DFnQsihX-(6{B`&*ZMTr)i3p;&h9VG@;-&al6opX?5Em8ADJLBIxF}iVmeC1_IwOcQ}vi;$Qss6PbLXdR+ zipnCBRzVDrO3O)Z{GyM#F`m1)5A0{;VTiRM4Pk$W!@^7N+rc ztC?|^krEAz49Rmn(N<(z)Ua%bwuKeZLcV2as%;}Y(VX35aAZ-tFYq|&*w%y{+qP|U zVmq05V%y2Ywr$%J+nm_R&3n$Nd+&$)>3-^6)z!7Tx}M#&)_Q*Hzf22o@5&344wKm_ zJ-n!xQXAoIEQ&8V^G~(XU`1nop5*Ul5GJ^i{g6nvJgAanj!Y^wH+hObFDtf=%rMW) zH}eV!(;Yx2ryc7)*U6x;WQpm5RB|IBXUDe`m$uN>0@=rl$3vE1@o?;N+v^>?0+bKpxIKPj*4m zzF+2)xuh|Xs#9UxG?3Y81nyw(B=_XH?uw3b8bV~jz4#Ai)OUsqU#4{kx>W4 zxr-=IvF*1l8-*h>@ ztynkU|M>2PFX&{H3g=GQ=)Zj&MbDJUCA2A0ssH_Me*?acUYB0+eRAL5?gYWREI2Pf zs)?@g;q|@4Dj3m+G&%pXwe`CC=RdW0qhr7QI-usYZ}0mueA2`)Y|qE%`=PM@_lwi( zb3n(wQ>?0bo#-8!%{xZFGvi{LVQb}cCRKvy-Slqw&)W8WU&ya*X$>Tv`V9@)mx~(^ zKBuqt<;2dy3K;y7QBPkVDgW5`z1+>+#o8Nu zj(y*^S>MO_JW;?-@9XkWfoR+7orPN`ZT?Q9<$4xKUx6)@FsX55R<21 z27Q(TB+3P+e}<~KSq8fF>`PRD;%5{2Yy5I^^GS*DU$t5IGzy;xv1wwqwpje;7#dHp z>m&L54E;uO9d4_ZOR*HIb|u#V?n%7KzF?B3(b(joyhXQyXl$_4A3}agVWEh`V}j4M zt;?VelegRsii-4L34{?QD6hl=pzn!UVR*p`Aiw*ktqPt!kBX%J6}U++7R)nUwvLfo z;gAJOEIRvKAa(#ziC5D-0(7npI6dPw$1_w{#--~UNZtNiLv@WR7!kGg`FBfY{Cyx$ zslS0_xzrXoL3B{vaF%zMv=o=37(Z>M_LZHbHtdwXRf7%a^VZ0&idD!p!iO$(Z{>*! z^4K95ssL@%Jjs~F=(}MogUyHz72ziOGd#y!{oea2Y3KktQrTA1E-@XCM+$H+!y5s! zCx*rXdOFAC?0hj+C>os}5M2OnNJU1Q&6~ZTM8I@YwNNS=@gpdbHAp|GrR70=f=I%2 zWewF)hyDE1jg8_YP@9{HvwCUz4H$aJ(66u~IwufQIpv@@#|`4I8E#X~Hr?`&aA4yy zsE~sKJ`e%R{{JLAj5x4i9Cvol^p=r2@Ft8DPC|u{6#tAOHXftiEx

  • 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%0A4n{FgT#DROQFYj|0hae1o} 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 Date: Mon, 13 Jul 2026 09:18:39 +0800 Subject: [PATCH 389/466] Remove Zenith.NET session handoff zip file Deleted the binary file Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip from the repository. This reduces repository size and removes unnecessary artifacts. --- ...ET-session-handoff-2026-07-13-f256c9cb.zip | Bin 1341220 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 handoffs/Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip diff --git a/handoffs/Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip b/handoffs/Zenith.NET-session-handoff-2026-07-13-f256c9cb.zip deleted file mode 100644 index 03877d9a3f846a41eba6dc44a3e692a8de6da294..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1341220 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
  • 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%0A4n{FgT#DROQFYj|0hae1o} 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 Date: Mon, 13 Jul 2026 19:50:13 +0800 Subject: [PATCH 390/466] Refine DocFX documentation experience Unify responsive layouts, navigation, code and table presentation, and landing page styling. Improve API affix behavior and consolidate branding assets. --- documents/images/Zenith.NET-Logo-Dark.svg | 20 - documents/images/Zenith.NET-Logo.svg | 2 +- documents/index.md | 21 +- documents/templates/public/main.css | 789 ++++++++++++++++------ documents/templates/public/main.js | 242 ++++++- 5 files changed, 790 insertions(+), 284 deletions(-) delete mode 100644 documents/images/Zenith.NET-Logo-Dark.svg diff --git a/documents/images/Zenith.NET-Logo-Dark.svg b/documents/images/Zenith.NET-Logo-Dark.svg deleted file mode 100644 index 726de00d..00000000 --- a/documents/images/Zenith.NET-Logo-Dark.svg +++ /dev/null @@ -1,20 +0,0 @@ - - Zenith.NET - - - - - - - - - - - - - - - - Zenith - .NET - \ No newline at end of file diff --git a/documents/images/Zenith.NET-Logo.svg b/documents/images/Zenith.NET-Logo.svg index ba1fad07..c87d7e38 100644 --- a/documents/images/Zenith.NET-Logo.svg +++ b/documents/images/Zenith.NET-Logo.svg @@ -15,6 +15,6 @@ - Zenith + Zenith .NET \ No newline at end of file diff --git a/documents/index.md b/documents/index.md index 3722bef4..5d60697b 100644 --- a/documents/index.md +++ b/documents/index.md @@ -12,13 +12,13 @@ _layout: landing

    One rendering model.Every modern GPU.

    -

    A precise, low-level .NET interface for graphics, compute, ray queries, and mesh shading across DirectX 12, Metal 4, and Vulkan 1.4.

    +

    A precise, low-level .NET interface for graphics, compute, ray tracing, and mesh shading across DirectX 12, Metal 4, and Vulkan 1.4.

    @@ -75,9 +75,10 @@ _layout: landing
    - RenderFrame.cs - Zenith.NET RHI +
    using Zenith.NET;
     using Zenith.NET.Vulkan;
    @@ -118,7 +119,7 @@ commands.Submit().Wai
                 
                 
    $ dotnet add package Zenith.NET.Vulkan - +

    Continue with Getting started or browse the API reference.

    @@ -134,19 +135,19 @@ commands.Submit().Wai Documentation

    Concepts, backend guidance, workload details, and practical integration notes.

    - Browse docs + Browse docs NuGet packages

    Install the core API and native backend packages directly into your .NET project.

    - View packages + View packages
    Open source

    Report issues, inspect backend implementations, and contribute on GitHub.

    - Visit GitHub + Visit GitHub
    @@ -154,7 +155,7 @@ commands.Submit().Wai
    p`^AD(vd<^Uc#?Zwymz_TH7|`=9=6-~RfQ8@0o& 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

    )p8w4I=bR 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

    `BnE3Wzy)*#rbi>80Ictf2g* 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ξdFpYcagAby|Bx(Ss+P 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­1q$F$yR_T)3T{ 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

    u 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%0DN`w|A8+Fk9{rE|A4RU`$J_-)=b+s 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

    2?_uAlJ*tHJ8* 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 z

    +Zh{WFuFi{o=${txWuLvY5%iFrxMMV7AXP35(V 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%0A*{#^6U=b>##;t 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%0Do&p>}7Wh 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

  • b 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

    p`^AD(vd<^Uc#?Zwymz_TH7|`=9=6-~RfQ8@0o& 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

    )p8w4I=bR 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

    `BnE3Wzy)*#rbi>80Ictf2g* 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ξdFpYcagAby|Bx(Ss+P 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­1q$F$yR_T)3T{ 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

    u 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%0DN`w|A8+Fk9{rE|A4RU`$J_-)=b+s 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

    2?_uAlJ*tHJ8* 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 z

    +Zh{WFuFi{o=${txWuLvY5%iFrxMMV7AXP35(V z0e zrudCnI6$_nV)Gxrnx2tXbJn*Vg*lP-pBiIZ5%e`X^zT_2Xx-uY?lQZBT?`bLl8PAts{$=XP;_|R^4hMq zl_yogeVua>G*^7$%c2z6B;sHKATInJ=MD zv!Ay%<&+##Ou359fLty51Gr5{5plR7br~_oU8(+zHMr?2x9Ktz(~NaM^E$&q<{7rA z_usWGo&y;e1u{|;mT_{cdPx~ED961R#C40tv@>K7u>N#He_uHe* zdyVp55?3&j);uaV_(u`GuJ)!^FvOWl|82x80qz!K-w$kB>l>WT>M&qZxSvQ}LZ%;^ zsy(=~ToC1U)hMlGzb}jPUH77Ko760)E4q5jsELPPs6#a^UqP;Zt2y0P=bm&+UITTW zcBA=wPt1XP6nzu^$7n-?YlCRATDd@4{*qN(7hl_cugZBaZTzS+Idb<;JX}J#-dfr2 z?Nj@C>=tsl!j$2g`PW-=<6z`t_%Rij4*Ll&>X2^+;1rokBko{FN<5IfT|Hk zoA|`K03Wm<#4vIOqaZ-ck6F!M>GYOY%A6z{Eh%!eK5K4PN!6w4Tp}>BQUs`CU)`kO zu;T~P4_0L_jh9s?*dhSf-<$iz2HN)G?c?@gRmB|V-NsNg6w1SyZQCyYiz{(C>*5=q zXg^|X6AJXD7Pv%2uC#kj43tdxNP?jbMGsM9>LBVIqhVR*Wum_7MZ)nBimzc|PO|d9 zu#a^hn3!v$2erHZCMo8JVVffr4Hm~ikFM5G!cH$}tYFy9l8*E(V4&|A+QvMef7Q9H z?HJnj^poo+K$Ihq2o+0Kf~1E>{k=&$PSzh9#abP$UMgBbxnj@%u^&}bgvUHBV_zpJ z5xB5&5zu4`;R(IF3a-cvI4albJH z^&9n%*_dDCHnG9LGy(h&SfjC1djP+xGw_KA*iK3&G^SH$^ko#aj|P4vp4wh~t;!tV zR6}5UTCZQbYo*058P~8k5%jPn#M`on9H`90CG5+gq`^xPmmvP+0@cw9Iz zWma}JdqP+OT5NGxH_Mceey7VeJzNZWiaB(=H9s(hk3whvNTiwhd^u zMAvy3Gla`MJmn$&L^Uy*;rQS^e$Jr(X>K}QtG^sevPi}|*5?X`oa$2>C5OXdY04Sg zVQdid2v&X3Q3-Ad;?SyiA_QN%Fd9X8ooowkEF6x5Jk+vbX}GxY%GP?$pmVw|jmI)Wl|@np7BSh3bxNf)ohQIzfr=BpJGz{h ziA=vGD6Mr-Z6 z@hv%6D34di+j6JUI&e;(j`$r4SE3bpMz@{Y`{;cR)y>cpQ8#5XgT@RGuNo-0-C|C zWxIhD;u0&)=w*k{mrvZx5Wd#j_DhFHQtSx2A5Lse&(((nGrNYX$NK9EJV25p`FNQ6 z-P#=ePH3DzV?)+obbY}H8GOwz1P=Y8rk`DwxxK*<^4mr_J$>&&;2NB z6QY1)@L@Y)99X+>4Y-y5o-0${{Te^=xF89}pEKI7s#rU%+YPM6@Ts|wH86&5=B=~# zaCfBKlpa-niaVQA7Hs+Bc5T{*{JBWt?L6nd#JE%+>HOme(VfI9Nd+6}_%>A6k6yo` zcb0^U07^4PgyjcRyZ;2JVXJ@<5cg3|To&*8g}bk*j$L%-KAllB&$$P1H!u`BxNsFR z!}DDQb^u7L>_R97IO{h_cSzmwBvpskI8iw)8^kTUMD{qJUwr zBduU0qDj6Nv(vE6EZqUWiH@O;Da*p*YZswEu=eiYLUCp$87HuhHlHsP4&>Q@QN#!9;Ra1V}7qz|PSGi3S~x?%~{p4{x7_ z_GDHRXI53U8}Fp}KiggJm)^B1YB+!G<<;}MxB>YWK`MXoSHN?w4-|FYxayeEOJae& zxr!R5i3ZQgaD-_!{o{uxKfXUsj-pVy`abY}!Ve;dP$ zHoo*#aJ)+9P6_ImWQ{fMm3!BU@TP2DY7htcbvSm*=pXS3)EgQ!;~~ zMDm1lQxL;U6cd+{oQAb6RziAOypknlW?S{H zl5zRJUy&uhgC5}B9Fj?c%QkEThykavuupJNC%^d;W=J9or2lzJ`BY zU^SKd6uL~~bC)~&J>3DN-y=*pAAv7QjqIvi80Wczha;#?&ML6AgPu*kNI<@Wv9gkl zphZbWt17`EU}^$ER0Su3+il%zJmQ32*4(sGIt>)K+*&3suYaPFr?ZDS0LX9xb|L0j ze!#-ujcAYdrQxh`<3&G%9oc4_Y5`WG{5c=_9ERd$6&eE}e{>-$%@c~m4~t7QznB|> zyFjqf`nyobKfZxXt@v}N`(NYUh+sWUB<#B8Dezp!+dhc2LZu4JGx58U{I3${#nl_l z&Kd&cRMP|*x%@!+bA5T_nzgGH^Hy7bo6Csi%d?Xf+wH%HJ$&jH8VLn&x%wo=e;G|A zgxFH^ywbZKcOz*P*H7!FvC&eS4hOB>kA7{jkxyfPGNasnf$`BmhYUqM$@|WHPV9TH z+y>eX=wKuF^?(h3h3_Npb1fplVCV*V{_Cp;o}AYa=JU1JJk(n~FAr&}b9M}OuMr84 z0A#RbS}E3t$W=}W`6JoRmIoQ|HYLirkuq+jZ3c~{T6X46z_j?^*RPYy0VMB z4|*VjJzj-69w9!bPn4eBZ2b0mS&Tp!lkZ3MvKZAvi%)R7p^H(}w}Jc+8koG0AOeZ& zgk~8P*v>u53PUXGf8y4NnJYhw#TfNLnB=5b6r-t(B8UXn+R#|!C%vPh?F>b-pG_O% zOZ|>Zo2^mWza|rV4 zHqm(pbW1E_VeBB=>G%7+%`3WZM}H?5$C5F5T7Cl76xm3v^;N|GSf6b^-JxxVg_`lC>&jmmsMt6>L-4V;U^4{ zy|pPs_*OuacP6z#l&ucI4t%`nH_2n3po( zpP8vl0co6vQN_bdeGb3gLYN?eht73@K};{hitIcBm>M!8QlYRi9=O8u7qH-*N|R-= z&ARh=kw7r&YRMBhUSpu!&jBo_WDn_J5f)=8t}u6YFo2Fge{DTP2~sZ!=}UYqz^Ve*N#K^Ej`+U>{*m3D`s+vOA& zrxJ9Ir`JsLcVuLapChg?Z{+2l?+NRobWY#=grW=+pe6l#Tpx!Z)HX^Nd>@ZT;=g&k zmV#_br|-Y+Pp3C8Q=%G6k-2JvDFh+#SlL{Ct{<<~=+Y+ge!iX@Rw^FKD#m90e6au$ zj#_4a-o>5eIj_`Az2CcaSS7ZokvINXh!J-^9-dsh-%MaSq1LBU%nQVHx_@6(Mx^>O zNZ`c1o&ovYl=`{r2}m$W)+u`e?|@5S(afRselBKEegsi@xeRk8DrsISnD_b?b32C| zD1Q)j_Bgb${`(xAx?}+>wNzl@D!OPcBB%XW!z|99|8>bxs#NaFs!WxqQSP%t}M zkQ9#nx}!}ii85Dka8$G+ywUaO3S7Da#XR*7r=FK0hL=K_mL!Xqm)Z~K+oiP|A;e;ZOtmx75+O|Wo1}jkLWS+A!HgcLI`MMFTT95dXt(wO0uTm} zSL8R023i;d-k7`Qm`#$X>2CC;;PDnZua@m^)vT^c;waXnPsX`Q=}Hx5qtji!8B8$+)~U zK^}NbP4gWoR6<%}T`1>5wRtyPFyRP7 zOqVc@HyGv+lqK>r4U}xHCO|}FaG8Brnw1yf&s=SQzs>pVD!~dL2iK1!y2jRCsS}x_?ujFN{jArs;BgoY zbNgOI%863Oz7%5n$sTVA41Zp4g`Bb({8i_QSyB`P&%x+6$T5j`DUN*b&efxKdp(A{ z-ef!Kvq}_8UKe&m2H}r|`*sXM0Fjgod2@b+>TH5NC^sx^0PVyVSEc{%^0l_;AkK0R zX&=$JvTD1@@S!{0HGzp+uzpf=-eUXO^S(e^SN@=bSdad_|-Bic>LNA6#+dg|^>%}i8Dc%oa2Ub_ks*{#mdi;l=;}|qDbEP09 zhgxVBVND9skc6`473q?o=9pPq-0QfNKD*|TP9#Z5O+nXR+irJxajyISJ)@~M?+@ED z;u?r(HGFGyWiC6;PhH=;P?N_ceYuaS9*y3m`;)v6HR1-}B`v;Zryiw0#%Eb$Blj>L zO-?o@0Crea#1clinUuPoN{!uvU^?~d+jU@liFL1hEnk}vBgY+|H>>RdQcW1%58^y_PoK#O&<3a1Wx&{oB-+6k*Ui|k@slUTQ5>x z{I`0B&XahNhLbK<{&+0#cG~{fa}3-{_3`r=xc0?=YR>m&lI}~l-KOu4ld@oD$)|#K z@E1cBj--i!!0ON9u9ITebBu=hikn}>KujNX)NyGnhwfpDfj${5whV!yUx#KnAq)}Z zDS4oo-c}lBo+!l>zZP?^4rbXOKuiQlQ=W(?9rzU@;@ds}rR)FV}hIkBn zX)9xijNUas>byM)en~1Zz8!!jeM_Q>sZvy6NfWQ;4>&&v>~}kwt2_rAQc}e~J5e(Q z*r22N89mQ86mXYj2F4*kr08##_8UhPy*~|PeIk8EIDMc|H zudKPJ^&lC@*ACYd(}uo0fDJeJB1X@rqQE=YhW1-%9$6swlWu%-`|W6Ijs&~9-7E04QhQwU#?8jT|NY$;c)!t}WuSNhz0Wr}6+6Es*ExjMqE@iusyOsLL8n2(mc!{il-#)(e=#0KX0N5Y4- z+*T6eR)!Day6eKK7-GIxsRI*SJD(qeO)^wS%8jpb(IES+c!F?RC{5vA8n;^Qd-2xI zBsgsghLk&h3U3A@c!rFc!WyN;{E@vl8_X=&n{rzjmcRefZ9S1oSbU`NMdCacU5sGP zw5?4eC6`#g+gHPHVYzgrs{WOkv3mPuP}a|)olRuhJBTDmlHZXEDaH~eAtHO?W`a6A zVgML4vs9Q4k6L$&!aM27b?B@fhrKs1fLwsbpWT(3Z@_^TBk4pk!=C41QiUX+G@;>7 z)YG$9jmYaHWFl7g3vV6_So)h3>n?pHFmsi`6DQwAXm=T+SnhcE_G>O4qQ)k3CA$)` zM7VZgyis!DYCy1jpseP@;vYG*Gs>*C1la1jADmcH%=uSIi6qke;`3tB(go#k4Xq`h zU@mCpmDcU2H5gsDwu7lBF^xB3AQO_dNos^13r>}vD@9%y6VaWPsg-1kvA=9nDL1e& zL*r7L>DaDu@BuKnX~}mi_ud)+17%P3Eo~0m?TzfbE+f`{P6M? zY4n$*itDu5?csjj5KN^UZG;ENP0|UDm0?XYZ zlT92o!YJ5+1WFnghZT;F7CcV>H5Zy}0ru&P|k=CA^jn4;(C? zL*!OUf>hRAt23A;_`mume?1$GECDXU+APAjMmZUdG{HBFriFxvoF)44d97{jSr7s9 z?&kK_x3m%cC(BVacDN`K1cwY+M2c95tPRhRx&*hX`G#B$y~?FK9#f49ucGl-%rP|i zRgOfmCc@-7)>*hDFA5{#)biT0Wj5%!jNlo15?n8NXSUgHGN57*b8%=T-LM6`2 zQN*jk!CFuj2cnuzp?#pkxsuddi0@A|HKgg=s@Z#7vJVlAr>Px++N(&%D8!y&*@FHG zg`Sn~Wl+oqe*ywQeaw;Ek2sD87=?*S0C2Zp2kPvn`h2qU^0N$+`8dx_Q$ofWzOYb_ zG==N>s;m)itZf{EBG>~QWlHw>5LiG+!u(f;WJ4|JLc|On(0{Of$qhFuR>v0OJ8i%3 zt{ehYr>c~URs8;9{VG_8RjQWc#%jR7ns>GOFz2w|#o{k%{)qTNZj#cfr*zmeb}daI z-w&0eNyOEl*#6-kqU%}o{12vOA6)0@Me73d z$>o?m?MWO*t^|7XiTm-m|7!`z=%(qeP7o@cq&`y3n*vcWTVat6o71%EFVjDp9WGAK ztfbhth!(hSHkT9^>}4ZHk9$51ud`O@ZuBbI3X>Imc|PYt$J?!^#HUh{^e1z@nO?D8 z2G?!9hq=1yG+tb-!w%jJuZGiPvcmH88aJ7#V{Y0RFh(Z6{@JU8Bi`(|iijw^Twc0Z zj_nnDy56yEz0s~A!RdK|5*}iOGF-OT>OJrjkpb%R+W2l(`M&MCH5{M)n7|6@dV=c5 zVyWPTGXm{StuDT!Ub#{;iNBNq+JIN*CL_BJW*P3 z*dmS4*0Y~)b^bb%CDZ1oYp)_b-w93-0udx%us9mYTQNHeCJQzNNI(=;8<9`IKCz+b zx-A|dLfMqNo0AwER9TZMkYmF{1wCRYD=;Kt1hf^C(7J_)$(k0Z!xXxT|65$8Ah34k z(s)pA?LdcZK6vCdO`mJ#?;1r~gkz{+(G3=cTPS2i0&XlxSYsRnA?Jn)+o&hj5`Ds! zojB{r(Y_ZHAu4w_roYb`@dH{!$(?aWC7Ne(lx-jj`7}+UXQdKxV1ncUVw(%IW|?fh zxm}$$d{Nx(yJ_{f+4y=}q;-<_Si za8%lC-Jp(^^|iL@QJ$!Ot2Z{~Qhj6YmjjxkfOI;Phkg=7n&D_vvtlGUsKnyNE>}Er z5KW$a7lwXd6l)XE#;^b(Edqgt`jJylQ;>sKd5E zeZu6ICv>tc7ff(R+{5y;u(1op`{Kr-`hH%TLbp?zTM8UAPC3v|7VJ_J@NIaTYHe)Y z&U*cOb%})k-O=Fv`a#joM}hC5DSElb!QrZB96KuWTTWUbz-^m!0u8Thl~mLuIw*#F zNWG)aMiFs_B7tZ+5Yc6W1}<6(F_WVu-0LjCC7MJY!%)mf#>8HJh9b!?x|$K*)N@QV zXsdYh+w$ltIA|+3Ar&Hcq|##?+_IJvzTXLsa^kV3xX^1IMJ9MV0`AF-jD3}=BUJ&B-C*yxevax7o&41v->Vo8QT@h4a$fHc*s7NSH0gKAEs!1~kuV$@E zU+-|NN{p}^@dt2u%|-Q$Kmkl-!z!TgcDVeO`ldMKhHc^3#oR*^pn_-k1Ee-B!-Vj5 z^4dLFvF|=d!H2i%TgO|eCi18qZqh#0KknUtkNSJBcKS4Vf^bcu<~uZ>M;x$|)NO84 zb6y5P_Uq3iHvaeQ00^*^)xXC(iXsGH+g~vCj__ANVUXqNC1U)S2DtOb%tA^QcIDJ} zUsh>@XHI!tsRbM`ahLOb)|P?3s7j5rKdPOwq1d3og6?ajoEA=XlDtpsEQMM=Q)i$( z`P$9?^fo1ZW4Eq}WKPNIefGsOg^^noM-IrOXk z3y^DzeO*6lk}$VbsF>d~zE%ZGZ!Dc84J zPjyVou^;clIkWg?IFwTFY$X1PvY?ehftB*in?gUv7`bl4nTO)?F9ux@4S!Qk!mO^;3g!7{HYD$*97ZxmCss%eXGtOlfi ziEL$f`B%NOqeQA4Rf(2^3PDC@(BK|ZT+r6kE zC=Lz}a+gA_03+QT{84f*_0kR@x z3!P1M;m1aoTe0#9c&c0EimIS4#F{?8H-{nDGplr{yrVzuh+hSPU^2AmdG;4vu1y-^ zJBB#HJI$`6wfS8B0hl~bi5m|iCJ^$~h|Jy%mA4!l&7W2?@QXRNaPl6CS&-<9$KSM9 zb5JWi#%UVgt_Jj`JvIFELSBUrJjp$@uIQC0CaplAkNu{6BEoJ!6HTxOwb~nKj+(>m z1==q+#NQ|aPg_@{w}#4^&N z9Rdk{CuTU|^!k}qmDf9&eDfeSy(&W)#h{>x(sPsv@t~Iv>a*segi|SwZ!tUdc2sB# zTNar^{sHq7m?z5${$}Z^2G$D)*(ZZCI=s%RZxhH~E7)`@I<<)Ho}`)YrwN-7Exn6o zNeiAdFAa!69fhiiUR-8r4q=NyRN)Merdu_o3N~E(6&f~ll?+}u4SS|(C6oQulOuwon-03*pQG|rFkYYlEZHo0$8-R#PvEa5^ z+F3L)+zk5`5m$!_{pAR@kZ4^DS0d5)tjmE>yTU0AQAI$=GD|C2JhB@wWK6FZ|j+?dJyu3^l%J9vNNdGH6235TuUb|fd^Cf zjh1QqoJNC5NMa zv?;J%!ZVIZgY``t#+~1@wa4e30)?25MGwRh{Ej+AETO8ZUH5Qra_o`#7SquyYfTi# zG^-_L558twh_VhrDY)qOaM%2D?$Ep+GRn-FV?Ho0FuG)qtPTw^#1tlD6-Kc}9>>FD zO6eKvi=Zv!d;`+cd90npg`wKVJf!nP1$`Oe8$yz{r!<=85pL(?F68InOA_8-2+h&6a^ZHNR*NDQ66dL`fdS4 z-_2L#>IC6prLWe6*pLBbnJrznd##D-!-t?+FOS?e=9U?`oGPc*w78!jc+?K>x=iwo z)bVlrik&$Bb~|Q#s3^YVW%~$(ID!fPj?0_r_3OOd5)b(Fzsb7Q|4h8zNGZAzhQ631 zcg=i>?!3b5(I?z2+LX;YGM?$KbF5YMJ{F8_eX@h_p(iQF#UOb1>INaGeWa(U9}L`l>y$eU#i%R$-R9@_X_Nl z>mqD_R$WX+Ncz9pqeF6#|4GsKurO<9y#*>(5# z$FAV}oE04sJ8)6^o_@11;F)HkP1!`!4D%F=oOVm295Th!ub$3?&|jqE^R2uFhj=RH zoQ0q7@>iw((q-7;nOPSpo_H9FS$q;1Nn|^u)D!HNOKwwkSn||xu3KR;-o*-YUOM{M zt{rZ6{MdF_B67LDek`6?_D59K@!|T$Nhj?8nP{O!9sRWo$r05L;9mnEi$mZj3S^kn zgND|ff~&9oBP?eEdnayfBpx(PB6>D8^Z&I}#;T`%6l6eaGZz?{AYv(jkte83lM;qw zD0~y`8Yi<^_Oh5_kMzx*?Hui7^QC~KbuvRiRU?ax5tsNDSfwUoMH#FWnMMUd^1uEA z^Vk|Y2zYMPXkI@O?*4bFfngJw@Q9i`CXG%SblN zgX@S|KQGJYL8?!~D_Z>$S_VNAPO}VaeT-;>zs=Z_@v{s>x5NcqJFWMM!o7T0 ze)bI9eAW%zOlLo)sh?;m0CgAJsZwdgF`O2^i{+$AL06)nCre>wlVzLCIaF|DW=Oj} zmQQ!mv`-U=X)DQ7N<^~;mjzcd!3O(?l`qSwU@|1|ju#-nG^$tqtGjZ0$5c($@2Ybc}qj9ul+8O_3t z$a4j>r%I1gq0_xfW>q3qq;TKmX6X2lVnsWxWC>|609|n+Ov2Cym^F~IXFRWOA5afy z`|E0V4JBIwA3q85o~@$>r{oBk{?R&i(v|+S*mm^vKu^rm-&w4tXt_6vbl7%F6=@~T z$~D^hY2FmrM~TLck3yTL!w=qEHd}1B^CkO!Jcd+y9&Q`4%w5AAn}(FSn+trcI($Y$ zf4jY1F$p{02Hai4gMd*xrA6_Btd(l#NA4A*lV&}47=o0G;C zlwMBZDM76?V-uz^X{1nbVkl_JcHrD|L*Xpd?Z^taEr2w-u@r0Fl8oig+iyotXz0tZ zX;ZuY$Md(?rJ1gnT)1g@F|A%=g10X4w9gnki<_;Ag77gzJ1>7|46p!U)1+UJaW1$gR0?dfCqasEKyNZ5 zA(JkuinI^els{2K^AM2*+&GRhE$3-}^Ts-MT97Xp{KZ4u4%De}xyCjB{#_m#2udfh z;lUzr{|qd3T&=tKJR6qf%X0!jLi}RG8?IKpI=|bglkZ&%!Vl~2bG04JzFIjutE3vF zS9!7UCNItAThKwnE(94tJ*NULmXDLIR|xiFx$7pR6#2G|*uA;0f1xi~q_NjmW`66r zwBVZAeX4PxDCCuO`SS;QW?fql_A!0_(VZH%7^+-b%hvwxeZ1k7;Si%zqeqS)qNrh& zd7(r6s{)u%^ALub$Ov4ktw|N+O~fO;I_vns;v%yada)GP=2Uol-JI-MqweibHHDk` z`VljJe|DRNeQdYG=zn{)*{oP;U-KU%l*y2pXP@Kyyw@SEYO~5d^oux*3-27ZqplHC z%E0{APkZS7&%ifYp(xx%nox8V8C04EEIETpuo`8p5L;LRoW(jVGKNHCR^EV()lAlJ zkw^CHF)@nJMNJ1p->hH@=b=o)0uU(CapPIh`Zp9Un46nj8ECJ@wwl4!Ej0cU!}8 z8I}X60TPtJQpO2|X&Z1HOHz;&)-~OTUBY0_%Gp{-L{&ic14)2;CM@<=05Rsy1N z#rx{eSTMBuv|?RhL;fd94L@0iVQuKer#&p{Or%7cVV3*0u%rvW0h~FfQj6?06bL|X zQ3d!~45GFsa|M11xZ8c~$AG@;9N?J|a1l8LzElNK>&F$bVz6h@B;D+bhW!-bd&Ap_ zhIw3$)X6Fc3A;UdN*a5_qG0s%wlF(q&Bj9)YEsKvA_`bb-)IMpxeBScbvo_Af|YK) zKu{Ukx{0j)29DjHC8e;fnAiFWxWA*=sO{udeEdPqOHq9HecVDe7wppYI}tm&2d-KB zGvuW!l$s+hoT(M+#iiMQ;?gY6V=MM1(EjChTDWX?r%hu_{d6LbYsG+dd1)M%BsLdZ zwe|3OlGQ1)MQ4h)SXp4I-e748jyat|zqt8%jS*3(Pc^`*^QhK?B0=;8W~->zmxq|> zg-fuj@Wsf`$D<9-aTamU(Ddp25n<`n6UP5!Yg|1VHjOFB1Vkk)DbhVS_?6Yw96n)z zZpH|zBc09l@At(M>GD(%HaUQ3g!d+=S|a>$Z0IP+vtR$XhR8o)8uM{)eOFpj#Lj zd6+Kb8G7*DWHK)HI(wK{{2g5N|JC^*IhD@XCb>6%1AX~N3iW3j^7+eEcMo%qn4*&K zO{HModuY_2VIEjZ$qbc8H66^n;Q3C{uTO@Tfw8SHDA^7j^aKVDa$|b7 z?_BTu!kXWZi}N(9=udsmLW8zjjZAgl&YEBH=+vniq!ZJg8nTz%4YV6s+5ngr_g=~vn{>s;Ca2yxPi|ka94}l6LJ48q zF@}B1*_9*VbSSnC?ut*(2rtH*aItRq`i<~ZwVqMdnOWIcl{UE+6>8IUp6$&<`i6tG-QI2FM#}E&(o(#DiY|DivT{wMYgVmLb@ODHwHybD< zM+K$EuA|@{ET(eBu%GeS)R0tm<>$de5@ZV~Pi$zq(HTcEQ0)ukbMhZy;1hZ#sL1t_ z>?K&11Gu+TX4^xnJR{yo{;7dil_-XepKFBU5?wcfoi~E7y?cU{o-^04(|+PRYlMo8 zD}moDz@k-Pf@xS-60^SpjH|w=PRrV{oxLoJN|ne1W`4#qE}Nh5(hS*9#E9lfh0^R1 zWoW`<%&le!vByM@&uT3}mnZ#)O-H4y;1V!s+GcRVq9f2yXOdlY1$)Vw^TZnEDrM2H zX>H}19d4YNQ2$ zssSyeQV;iUa7l!XVo7xblx3@EvqU50zs{r#SgV8K9zxJX^`h|vap~qic}0@r9I2yw zDGJ&sx%olp!ubE!Bs6{nmAv(jVq|amDd0mhl|h^L;tjmVZ@ls2W^*MFif|i_>@#lE zmz(kPcm+QsCEitnL7odD<|yrGC^{I!jJ&8Z>c9ZH^-i9Mo$h!7`XWz z_CiXOKP1^?Qr@&2VXe=-<jYE!|Cx=#u;5o<4x(_`(>ar%SVtFTgP5Nwi3SnD(&E1 zRvvxKwTI1G_AGtJ3U>jE=~~X2)5P8D6z@RT&?Dsc$7@{e_f^$6I=*CmH>JRB=gn2k zSnI*%#i)3?T~mM+@RI)yx81~)k?TdAdWiB zWBd=M%bmI-%Z|-Z;qtB(C{!kyv~5hSB2g&HLu#LTN;kBycQHGC7B2Qw_pbtGuik%# zc>`K28WMu~^Pse%-9!aix%7YjIW_*m@3Tw7mrgWr?wCO_ne^t@ov=jxcpgF9B+2jA zXnKJz|5iS<%twM~1 z%IB2xat?zWQSB%UShggu^l9zK;2+j-ni+Nm(0`s%LHdkPiW$YXV6I@5XuM^na^@c(KbR z{c-$oJuhc!C3Z>(r5IbzM--``Z@vH67Xp^{y+ExtJd1=W6;8 zfhsy>sFu86ZL-*F4tfe37p=d4Yh)(BYuWAD+VOk>XwcL6NWj_)angzTOt)%}4=)2O zaO67LdODH=^|*3NU|aUZQchQElufmrl26t z(^{pHVgpFHwKsv)@9}?sG1NxT6nOB6$&zvu?vD&(k9-Ylxo;Z;KjousE(k< z^y}~i2VpE?k!LZ-gx3Wz5Jxfp2r(Z1`jsRJPyWSX8B)4rHMa?GZ)b(-lU-L~=4EWk zBYEOL*=UbTwn+!^Pd46ej9Zl7hP;2E`cBH89fj-4 z@LO-xYnT(d_b>P%K*SPR+577g@?{*1#Bdl<;AWg;BA1>|{xN|xQH+x^BNg9>s^#K^ z;;r|B`Gcljk)iF|pE~zrAzm8bd|jUb~69`E^tg&1Q|Dv~5ToPGkzV7x^wJ463JPuVHJ!STAM`Os)oOT2E?$<^nWXXq8 z+8O*T22Vf@#jF~Op=7i#^*$DS`jf=>Q~ZjI*Y@}7V#-d0+959e*Lb+HH8)e=HF9>& zSnts(E&Q*04MVqd`IpNk+qja~dNXM+q!Zq)*I_=Lmiuc92VsZfhT5v_tx`0bHFsWA zj4Fp{)!+BMl_L1{9DeBd;LhsCIA~~p#>-lfX-8&Z4Hxl-96z4OcA`DU&->LrxkSpW zIUX$FW|H{=MaCfER)0$-t^PbHce%IPt1O{v`qClPVCz^PLS8FMfvjf{xyImA`K@i0 zfL~Uxg8c6anWF%dy-tzCmu@D(WP(vV@y$QpF@E&nEX%p_baP2NxbsD`MHL>IkiTLe z=^f4&x9h#SPO*b3DZxjBWTA&Teb$Gkh`hjBM|>Pz07{cLl`>qONYKq0`a;Z@Rggr= zH-XS3SW+w!34P!?re$WU?(TMj!BFl}=VON)T5joesMZ7XUPpYw^k90hNi3|;AM7Hk zgup())qjO8(R60J^=O-xxF^5$-ut$F+`J>T#4TYu0X z0lsv#c1wgUf7f$%-0o%Q4PEJbJ)2gB-{Bya95?_)OEwp7jV@W2n`V_=W(QPQb}+9k zR=px8+V5}=I#D?L@K=A1nyRe=nTl+CwK{A+^SbTrlPcO?-f7>Rrajw3@T@hKwtsMZ zT({9pqEdz`^kvzBa>Q7eJb3Ej)H@RjHnetnA@Y;IpW@8yVL0T?YUgj zBfn#&*qVP#IfI^kz1`_{_2WmSdi8@&??a_mFVW0xznQ0~9|t~fbH3^;hRtZl6+)`> zfE{$@|KsT$gClLfuJ0HV+s4GUZF^!n6WcaAwryJz+nm@F+j{!?-*3HDXIFpf>QCpf z_g=?ut z!ve8v#37nmmo?-hLMD-9?QUQ9LuFn0bM`REqVe-ZYU$o&|e7rloP(ym1>EUirDciFa`m zZ66<+yz~y9IgUFqL`ZJk=ET-Ca>A+5(5}M-*ak{QXd4OBGb3{Uq%g(=i3PBaQG~pV zIm}q{F^VDZrMX|1Np9b@8KfZ*z)nr7Hx97*@Os~GO@y}*&-r}#=HqmG(u7!@Fzit8 zfNE?^0Fa3PWoi7L>1n)O$7dSx_b6`swk|*5^ZuNe>gm&%@!{lmMLlEb`{tC&w?dkgWlxDD|Z0_@d$ zyqTu_Y~k@Qy#`YviQ$(E*534fPsg>hW|O0DFOq?4mB`TBsIu-}HRQ&tB9fu96GVDnuo3){vrDnvbBk4N^iczZDR3 zp!K0k0{hk6R=Rh0BR2BaI+p35EaoVobQHJKHgk;0DCwfXC~#UsvPZD-uxWRX-235J zPCSO1PgIx;#FDOZq9Rxv4fKHcif}hO|B?(Gw5aCtorZh^9nRN!RSITFBRTY>bFb(b zw8~FejnawYZ9~zN;s+@Nag{P{F!n}ME_KpabjLYj*WL0#30?*ehZ*E(%Mdo!fTScj zI3ugyId|Pbq@fmN*qCNJ$VF=Yt?p ze>GA-=f=)|5gIwbQ#^li^m-~=P-}2muSWE|pHPO`a}h}(?571>HT?V;rlo(Dct5^oH6d zh6+hne!t!^1@ixjCg9+0HSrqY*M?O{+;uGAoAz{*V-#2VDwG9JU~uxL0Dl$j-FjY_5zlt4GuD|e?zOqShpU*(~j<9aN zzhx%3j#Am#$I+MtI{19Gy}VNlDWA_beGs!pFs$;dQB*v#x+gzJ$9_lnR{B(I$5R3m z7i}0VB$lsk%|c=oYp3Q(SQ^~T7>sTnAb%@HztEpuz2OapC;M3VeOLL$?;i2)u*Iv@ zKOT|PYkO0tbHKS5&@6jihQN+@KIXeA0{5HO`Rz{Sfb^-S@OGI53vZt8>z0rIwS~R- z%db7So>jSE|7>mR2WS7ygdKi%PqKy}ipXn6HnSb11tVCzs*a=WUNsKb@^<6gf62<( zN+SJ{KO0-MknSTTL8$I|a4d|ay{>;!gv7xs$RgOnWd|>_f-X@_3so#GKUHwrQdFrF z3Z1NG)J5CjwSVo#z73`T6Rc&|$_6F9?BMu0;RVfuyj41@z*;N%=AzU~Ek+`^7gd5j zK9{|f{T78s-%_Hz3W`fW$Z?F9@ppIS90FZhn5ZS$=Em^6Vf>iQf{bkBPaGM#$aV+` z4h0yu?5`khcL>G=)H>v%gwsNkpzdXDnemju z%DOn9wS?&Be*3oT3DuJrS_t#fh7*qnE^0;SC{H1wEPh6BClDY4pFeWrWOyH7#j3u;q^#c_8Uv zz%&{C>C%MN#s6lvmh8O|1(zAMPB;7qemL-ADDyR;*SZO?HZ5V>Rj@2HWlPj5*!dgZ ztr?+9UvP(M${fqh&bC)MeVOutG9^qneVp)OTW%2h;x`<}m0y>QnxaWnNfRz}2}l7J zH@O|1axs~;18RoUKDl3T}Bm!bs?KbB2NhlG|C*lPjQ9}BS(3>Ff# z^w7Jqb*4d4F6$z+anfOdO~VCm*_wz14t-+EauHGzbczWlTjrF8#CtgNQg2}N-uMWi z*XhPxkE+u6?LZCiz;N|nn@Ss<`1z&m`F(bAQ|2{^-+QEW}l<8=XWHvuEpj&Ma9cJexLLA zPv}gW6v&g+hpnXkl~kvmqe|X4Ko)Xfx08VSuk{=Qr%NcKJfqN9pZnfrv?TGzedn&+ z&Bl{)0q{V#>SXtlYywt4mQ{z2vb=CN%+WjU5bG3DO{~>2GgAKyn;O`4?i;rkvDd4d z493~%Zs#ta_bd%TcRi)$zd`Z^^Te~r)t8uFRr1kuhUFD+Foh0};Q082KPyQ(aFf)G zZ0+YUoXB4H+hy^_`^i&OOU{brkQ_}s1;Gwl`8g|}VlP3X4AnPP9Zu_~>IfIgf}Z$%DB;xq!zYZn4+-1Y1`AqyXf8m7P%;H*4CxC;?=-<-6|ka zBa>l9VWpXtv$g(e{#Tsh!4($r^PR}|6n=@Jt43JS0;HH5V{U#ef`2hiDoS^%pVAP2qQ#~-(nUW}%yZAX&z4Va2>GPOqXMXz z4Xzogd76}j@X$aoGm@jCI>2e+m{TmWw9+FNhOHxm+3#HYTX>yeqlXII*&SU>XMQ4| z^$*_i9R(v$h=bpPr{jeyPIWtbAMZCXjrQ*N8$K-!-$f!7oOx?EqL9i5+8JQE=?fJRgh_?CS@1|;YETvrtRv8|AQ zA7nM~h3-IltggNbR6VkR$eHa`-n@Uvd?Y*g=2(^krS6!B zt$l03`|R8Lk-BnD_OXW~PoaRNy4R$9-7vAR)3x2dp!DEaU=2o#=L&J0f+9sL=H{*B z{24&=>6<2-r4S-pRsFJsTeGqPQTRbPiy+Rf3Dqa=h%tBK?l-txR5Y2|>K)~0CrO#g z@a@|}W6?5232)RhWnNTo{^qJ`G~FC?tW#i2_kkW|g$ZZQg<;rWsBx@rr6?R5W?XmL zHoddtSDWf<_tm5E!is(^msN*(Ef?)tYA{91mEgzPw=49fuKFi%&Yy}4^9#(`tLbFX(L7%0KL#`7d@NC%I08PmI-M z+UimL(SgAQ*(W3LBhq{=nbcH3*C;`7p>O2V)=Fuey9|=B)3G$?VuNZ(reUJt^XF=w z|KRtK8a&L_?Pcjfz=hu_CaTi2_sR0p?QLv@wWEoVztTC8khS(GqPfIY>1?-M7c-KI zPmwX{ns28;-%2Vnscf#Uk#n*1he_C}x1cj%MvpwQT(>OE66MtXt-q~H(s`LWepJI= z#H-h+7b{v4pAt$y=0J+CFVrCX7ZS$h^lk?UW7Lf|9W7~^RKLY~d11P5z24_#jvRp(yqK)0aQ21olyXBw|QN_8ebSg%a0O zCbn!rf-6MBYdyx14ZY>o+nhxL%a_9r|AtRv1ebsed>AVI2s=`Xf=WfqW}?|y+MfW( zNej%BcJCqeORzA|IcebyMNmj}Nf8&J4Y#uoX>Dk(vW}bP@9MadJ?s>bubGfNG@!h# z!T8#S2yloH;T3$F#LR{3W6h4PF&$tJECtb*^P#R7g)o<44^4b&{YVF z_HNnb&SW5^%brz;ub@&@fSS!PFVP&+G=^~#MUoFrQg+AMZ~>w9n*ko`lBfo;1{3%Y zm{1>f(E%r#qzKxT0e0QqFRP=xQ4yh588)Biy9>^5sXlHl=RL18Gk$|!2czSwKgV2j zN>Jg|;Y+U>m}604`64TQR|JYl_`RQAZ&%|9Nvx0RuWz?^1vVtmVEr2MMjOZ%Io$Wp z6XBc|Ge9HT^U_p}>|?*(Z@eMfg8AJUdZG@T%?^aVD?nC-f&10gdrG|hVJm2_310FM z&z&&4mhY|BvK9UUP25L~&t6votF|8!Umq~uu&l33+gF9bBdmZ>Yr>oFeRtqt>1#sM zAX$+xSm-WPhqbNN{Q0r={Zclm>q6mfgk0yR{(4;ABVz+l-!YLLT-9NNJh>YtFezCz z0=P*P!r1Wh^IS;>nqeBUF<9t1YFgQLXDrYdTELn35^=-LaL!Zgx#YFfQuQAT%t%pTc(qexly$y^Q2=x_F?B;8j{Y}%|Uex>!$(o;>IYz4j0hJ#^ z@`W(TK|lXM7D4sc`;5SBZ{*TMMx5rD!bxWLSe02@OS+kf{fz5k_lTD zI;fQ4e@31A$#y2g-oABLs}+jKCUo@}2Q}xyd(#n(98*49oEMebJ%7!6FAB zAiQKnrH(2o)t?|VW6bN?5^yKV@Q@YPuN8>D>R&O+HwhTkdjk4G2}-OMrhKP zXy>8@k)isjBQ?_F8O2;vdtmQ) zCC|g}l__{6Pp(tRD;eZCcso)tqbhG>v)?ru-LPb>sJzScO5p1s%`mhbAZ7VG+_U>6{sTdELg7FiG@4$ zNvipJgE>d!^ENXTs`L0JaRh#{C-=3J1`c2CbKrI~lEF554$NTFIU2m4*6qz@q1&w0 z6;KiIWvN$H-xk#9@PJP2nDJ+O|5`nw{Ymr9*BYVn7w;4EZE2|Vk&%!4E9=txS)j+r zo^(gc;$7U%EZVrFLiMxAON0!V7Z9YA(z*o#E z%6J4uTKJTa5r1WHVmkkzVr23(#$^U6W<=o7H%YlWky3PXePeicA_nUaNB#z2fl|?ug=kj82#o*h% zESr*H@MH~!x>6qy3!%?eo_u%xnh-DCAH@@K82zt_3Q3z%gotPk-Ju$l2&qBE$Info zqqq!}zW0PGea+k9$xOtD-Q{ZD$3H64Xb5JZl((*!`B}89=t0tS)Ynqtgp-mYn3INv zWYN*AQIh0Hr9EQM?@1LxNKhGf$@d3Qh@uKL+GQwptmhR9!^$XHW4Oer?-sO&+T#=U z8zi%F6^+eO;#>{>S1+^08Y69Id1E0Z(1rNqMubjFs92^(HDpUn@gJ8T z%NQf(OrlR3hVR$*Ups#BArYt`+gKYH5kwVA5u2mN7B_?a+G|)PRT&r2e^3)nEJhH` z5Wh#%4(;lSyZGnAq)T0rF3}J&=8uj-UJNnY%Z6Eb10&hkU2Q3UF+o1;huYlzPzPoj2)x2W!{$W15mB;-e?~KW5`fJO-?ryJU zeR6d2nt;Y2952fU+8~9foA>!>bkThqju@N;y(}OAE|92U-9V}LbNb@(+Wg93*6w^v zrV*hWYjI4FkCF}HD?s*n!XO8c2wVwNg;bHq3JPT^x7VQe>+~Ync~`i@!RGkp6zW zJuZYZ4{z~co-eWa_|6oaHWrYyKyq9o&eDJnE6!)a0?=XvSvnbH!-*2B^5#m?(0#Zq zFbYPiMjgixCO5}qMje*&7D0IqIhW$HWxx7gofy3(8Hi=g`l*}=vD>U3F+N0EgBBP7 z5o#F~`-`fkNOp%6)-2&(6p4Nnsc&^~#Ys>{rOuqAR-?2|{JbI8y1ESWd<4~$R1sFo zFp4`Y{T?57&_s+cI!R2$0yWhLtf?G>0$^nZZ@KfBK1guMI~-cvkP>k#MJizu>=MMy z!nPlYoC;gm)J%Q;4eU=VWewUgoNB!+$9KIt*}W~QM+ zByRI+3Oqn!%l+(2&*jm0J&azr7qm2jqS^VPoKt~zi_jgyhT_QuPk>MU5~d*k0MB|~ z+5uB#2eRXQR75<=C4Xv}O$WigDkseKxcy3e?zE)dfm05z_h(&76DN3`!Ry411%qGe z76Yx_=4Vz~n}E8{9;c4OSBCuTq8>#~TisY5&4J?4#ook`;Hoq)n6M`@>c4*CVDo`E zPfR_yS;foWbNPXb2hfzS6{5?9Vn0z7l?utlr*MOkpYVrcP$YM?_AnnJm<2+LG(^(m zqV4C+#TNCewGMFr_!$rrU|3?(1ZfE(PE@#Q(W&2X)4w6^<3vQVqp^`XQ}A&XR3uRa4BR zZI~IoM}?Evh;avu&->)k!d_J<5kes(IW7@J#cdl9pJK93PS0zaAIL!aKs(J{TjMM% zi%UhyMoMW1LTa<0moI%+Id!@Mh!;HS8l`Bgc{|k~w~KFIa3!kdJ3R)ye%{A3g8k5U zSM24#9rt*YPK)OvGZ^jFGO|k*)UJH=1>n29dJrX|RVmb}%l&+Aq>o+#ajgtiraclb z)~-IH6))-dQw}*5YBxGxQ_AwZ+@{0AC6mB0$H}IYYN2?dtNd1DlgtST&K8bnu{s%$ z=Ulq11)k+rTdliUdk8VLEc^ryB_(8Q>usmk*2-01ad|*h>Y}Tx>oi5P>Ac~>>^z*t zVk(VMnx7_;xy+q}qw?F*R<*;LJCxb=byEb4%ei=rHybTIYltQ(HRI@z{C=y-tV=Ca z#t!eqehB!aD4?S+X9ly%X^Q0kF3(IsqrNFbvXmR$@G3OXfJzt*+Rb>N*IX%9rI3aN zgbDp~CN_cq0PeSR*anQM19skkYRDU{gWV`1Y$e4gJN-qxKICD(1cacER$UaGyzj9;&I zGq^ugPdJNPNKTxIL%aafQI~?0;npXef**T=lqZ)BLda=5Uu5@YuDWxMR(BX9ZT3s= zxxWm2%<1*MQT>)H<{x7rd$v;a-76ULeF@$H%5BmqZvD>w&wmpo2|hwJzYFqkpxnO1 zl|UZ={MGt-5Ay;I`ezR<6n)p1!=@t4Dag@Oqu32YzsUJh#>BH>h&)BH1|B1gB*IWP z&(y~c|7dk%HSNKgPQc>*L{%xpHipH3SupzZY?)4p5(3Hk*v(#;E=~UF!7NRdC>#5% zLRBm-*FSs?h=LUcnwTNe0L;Em{LxBKx5iIO%<}qEzpamJ!V-V6d~{JDF@Ln19`)C( zYOMM!-zwqORz-%ddg)Qnz4~sI;8II8V_=Ib2=TLEkwp@SD_18cYB^c6(0{c~UCZ5j z3#D1dXRL8Itl6BSPA9}ALk78oL4aCjKx^QLlVXk2F-~fusk2HuCa|v#hQ1_7Arf^V z10Vm~(VS6+A)?{Xmm8do>Qxo0*k*{L81G27Rp$NF+YlRLvN%>t``l)2xs29O(6&m# zAKeEU7}V#s#%Re*_+;iLYf`Yn+S-irhwYJ=`DJK>F^J;D7=AziEZA0^zq<{|rpoK0 z=aP2qan1Xk$%DGd!_M21`@H2gSLi~w|E(A{kp}E9xp^pQOhPD=xY6F?N@$*R5q$ZP zSM}$NDa}%P2yUD)8I>`vO)*U1gSa^JJfP4|I0n2(G`lTVfJNF7nSOQ9@hhQu(`57& z`1GY6d=%)!z;cXGPLL9DL$gmpD$&3uw}%+ppx?4ZGaMDhO3_7c<0_>+%`p+UQlmjc z0U@^>vjndJfWtyj1oc&<8cP(G+wXeB-F9I24$g?lT$ND-=D1H+oXu5El}9YvUPLF2 zty)S!g5+S7(qtf9(0mVFm0vG-UFY*OyxH{(9(Q5zJOj<;hB#5I4r&BSt?~NibhQ5- z@SD%|)ZjS|I>zm&NH)dBpEmC+9}-$)cCX5OvE;CSQPth4Jy5OdhFa-qh0uN1M~G)e z-`D=>>>a^rdt)brL1CvupZi-GgDc|>B1JI|qIRcQC*^$9T!0VjY}9->#f~7>EiU%j zQz1%k)(+ruA|SU?vrhf|0quKQjz|6kn*=03<&SgF&%*Byb7u!$}jv%Wb3j3qu6K_09Uz){SwsJj&$5kM*Sh%B-b*CF7$8h z(wpJ9%25f&-ycm%xh?o{YYD`5(vrWPqK73f#{ZeQGz&PK5Y!rocf|P(Y zT(>$j7ALld?e|~Pik%Jt;NZk6*;jEP2qK+=b(LI~2faNN{XSWy7-Z*jX4_NlMi%;S zu4TP0UK36mpBsOT-h#m(uLszJ9JsyCWEk?ze%%`$&77Wg{iHGY^gO;Joxr%yDUm;4 zspc4--fvS}pYAhL6nMTk>_ljurGW@s@0A$e6hZy^cg6;DcXKh(pU0x! z7&Oqfha=lCz?Y({cSxpwG=~0)#IX2Bl@(cIk7?j%MWk0|Frs5?bgx?SsK{?-fcps= zrxnfp3<9bDJ(}y_KG_QI&(E!~hKqjJ2y;O6tOl`t3dvYgaq#^yI~>C59}Ax8GdKMx z$xpakV_la~s6l+$BQ*7s8I}34b2ND&gju9p%knSH@X6Fm}LLQXX(q?M4_y!jCTm?Hm_ z{_m{a7r5v>eH?ttbzSscf@E(l5j``-LC;pwmO79 z(mxlF(EDM^BvFO5|)K473_Nf2!Wp zIZW(vt`AHj^5x=1N0|eYgiQluCNLpC7$d33kS0AwSN0N!6?a}^M0i~G!7Sjg)Q(}J z=T?blfGXuImrlHs3RNkY)-5~|B(%5i0p}naBVae_6j?ns;@J7=5;%Cf*yaP#u8C?d`+wGw2iUOTH&R>QKyWdG!uXkEU7k2-SiQ>y3{U>&|6lF8bUB|2A zc04F?@-~~@Yqk>n&4S`DH`|?Cw4cw&1Ps2J9ET0A2*x)Wdeo85R~pRhf$~B4t<0rc zc_g4=;AR2f!vmhy`>AA{u-8#%F5vo|ezG37ow_5TGe+w%ifzWo_dZg*)$seB^7F2? zIakA#K@5yX6=2FEh_J ztQUOusqa#)A2a01aQMj4 z+3tAX;^;i@_=hzS4F(vkwb|MEgoQti!OPYA=e1gzr?w}TuU1se%fE%qlRV&hv>e|X ztD{3KR(C?5zHbm>;#%Fj4kmR0C+soG_|IqHz2X@y(VOPGH&9^C@b<6CenkvG#s4N! z(xPrJ#Cd1pcP;e!PM3{!;%Fd zHlTo8fGFWk|8*?Q`F$sY*QM|{y^r_#b(Gz4Rs2(|6HHR~7FJ5!dw=?2EcC9Ht$CwV zQ$##|6^;gJ*gf1|@>Yh%Z%2_N{Q*J~X{8PN?nE=N!WpV_Eq8HJEp)I+xuhIu*vI30ph zup}*seLn++6T>@kT^bSbPYj-Z>%RXhAcDe)6*FxSZdk=p=;xOcIFqhEb?j9w%Dac66`o7hDjV*#d|@S+<#xx=mK|c8%W*`unT21R;K5e z$kovvZWBK8p)5NWR8gK;1b{zd;m1q0;ID~tkRTOKNaPZkqQsXrXCL4g5YsO4P-HR| zy>HG0$x!Nu%F9wj z#e$;+IOvjwMT&@+gtyOYq2c2Ew2Pj-?6bHwU^HlFa({Ir#$fPw!cLH|3e+XGRE*yL zI^TYU>ip-jT*1!oTAE~k`SP&z9=q;!U3ZjKW{&htW*L8`hQL$IV8EsuEO!aS3+G2@%|GfCNSVo)Il?6)$bs`Tx%-JKeOo`j6m7TvJGP?I;^q?v2 zKLJ5WKB%1e)ZC@Fr`fD-UCAfYmM{a^-sySC{AqJ6OR;Wy!=Wu%%~A6n-R`y%{rO_M z=Eu9pX>R5X9+=|ryO=D0G$x9F-?xzHJ5*rmHHF^MqDP00PvV`+CFsv|$R1)J@O||o;B4C$9PG{>TBXwtg);RVa zet8Y&64ogMWk?E@Od4Kd$!b4cH9>Y!a~8M+Ca+0r!7w*1h*y@IbVzFm-A(FsjRXUS z5=}Hcv9hk$0(C;D;rwc{WE|q6!F0Ql9=ja$K1qi`KG0@d>jFa<>JAhe1;Q!_Cn%j2 zA|3WbU=o?4W<#>Hfr!mv%Qu;aNV+^S8Gwm{po2S4y449-UUD4$h5rn3z@gtt?~o%N z<$IB$;!wI*Ucp^o3403Q$R#O|4~ldKvC3`JTq*ktl*p!ekAbtlKRptKheoxf+kPQpta}##m0{#^H+m^!2WwW*!>!++I zc7NQFX0l(sVOW>l4=`xCz62dkZzn2Ln|8LsPW&FlJ@QVw3?TfTCYA~&Izm^<4?=$7{ zwrz<+%r~P3vzkC*aJhX`^mnyE`cIbVDQvwG;5ncI4*p}H6f|J zOD=o1)3#z|4MuCZsJPSkzYN(x6(cDQZ_#l z`A#&e`#ZENqBqgFv*DJUX*K!@Y}#x+!NTmGM&>8h>-y*Zu4qGmgR#?UK*(o2m2U-A zZXl@8SgGNYSm&$Zwn0T|sZy6&H-kIqRN$ut-txtmc05K5GdF-|m$K`6_8DAY0? zm@1a=%^dPfaujVx;lB zgUG?5_(<6o;NhjiP3Y(fxa8Aqc>WN)8oq76-UZ$-s%lPXBBmns7<`>ErpTtz8hm!V zE@8GNpXEB=YAfALOTh}!b^8}Oow3QKRur*nUWXxDy>6+pwlRF3Z-?ez+b|FbaUHgX zzBbcy`aU0C0M|KP`6;Q7K`TsezBwI#{F!L;&*OGr<+sa$2mNc;k)dNC!uu>34t~6| zQ}OY9bozcR3<`+vqxFAQe*LVo`2Ii~Qb!`l=yAll(^)WXAVKnZI`ckK_0P@afx`W~ zyPY_CF1^l6@9^h8>=eRiJULvkg2H_H!u9Q_A(h;+xo)*=`Q+DZzG5;2K;i8mVh$zNDa4QNHh% zX6fQH>wTu=yAN6^k(nK7;c#(>*&O22ia!3?bDN246Xm~fxE8zbL92N#?fWt=eBExg zb!&HTe%+p5@m@6WXa4JRjifaBe%Y30fmHEv2-D==-S~!+G|Jdr;L>Own_y4=(stXq zOeSwLM*Yt7a>jeee{|UdDmnlnree}6|Nix_qh!&y$YHrl^kEwRfc?* z{`$L1^|nu>t&bT1H6LEk1;>;G**+Dlq(KYfB_9#RQpWTj82{SEhg9aI0lU14Q~(Yh zq*p+<-6orW27aILU%=L>MzsTpM66bX`&mnXxab$yFRxfs=AYR(kKc$=*4NevD~UZiqYa9A zNdOX(L3b2mTnY(LDV=Ecl1+qJVnA&s!^ha#gJXn|5IV}4rPL3RxPSDl&t^%GsE4>h zibY~!_6vtu3PF)e@tPqIPdO`Km`yyg0~1v}5(57j-8?~td)nRzaCi^}{&}Ah7Hb%h z6Hg|SEbyalk7wgVq(Hfajb;;5X07?x8eos9&YVBmJ6d@4ui;tEDM>4mO&-p-3mY~J zHgN(6OOnG}#N6}O!doNozI1o;X>EqLx7siY8?S2#HX^+YR;6J+!f*;F9^qhJBDIyY z6e!H}r(HJKyD@l9c9>65i|LZ0n4ptc<-0@{qqwp#dyvrSKAP#AvjPcO!67Ow+?jew zKn%Ar3YydqAXG0Ng#rC%_RO=_=lvS6v0V-jo-n}9-cPAxoXz2eMl1uqGv;%jf+@h} z`F)q;Ws|5oizh+=0Z};uq`0=}6gqFp$Dw@{R*Zw&hPkq>>baAcAO~K|@n;=Ag|ID5N z@`3a9LOZ!B*VN`)Yz;ds$*;^dyeN-u;OY319I6N=9#+)JmL(kp550Rf`_z@UL16q=pq=L))Y zyh`<_1A|~|TmH!6&UTppUvJjhJESs3d%-A_f0rc{6GX!9{wYrI3bkIBEvu;xu;0f| zB}CZLf-ivi`G@;&QZ{YHm2k$Mx0l;aIFoJx`>Lwn z(t=-kkl8%aVSJiN+XL?49z|HPob?M6H?Z6rBkWaiaf-9rXWswOsQ@E7k%^6h;&=sv1U&Ef;f4Qyy(i9}Om#$3jx{S_gEu7(1 z4vQ@x&4tlX6IeA9{Rrf}eG*NuN6Iryxc=}(Y6ZBU1=1I{1}?dGrB0JAt}xpl1lSds zElRb;tRNJbd7m#-os~ZQ^J2HhvN60b##oq?B9=1=)Vjb~kfAj#X$o;5kjA4gPh3Ne z+aSjO<+2rU@`q5n;=of$^{CvKP#T|ms5(KaevE`jb)UST;pmq>2dN!n7O)@7hR!hy z1I3Q#*+(|v5nhmlO-%hoVjS1%wHcI zg#?Nt!216{I=u$?Z~;kx@!9`#Jg~|YHVTRdmwI&o*XwScV1Y_Fzc)P;Jrds{aWbJ*=L{B+oxUMQU|Li;+k&=3?P@@7i(?>YR@6 z;y>uBSp5GJq9Aq4!;jRR=V=`p*qUBoHvHFraClM-g!yU_f<)Q1xFS!JQKugRSTbOfWRqW|OQ4bjs&s)1#GzRGVB9v!v=E z2B3?PjObp_sqfP3TJE!QkXzvq{7%Jau{5cRyytCjgr|rXBL^8UM^G8^7HeT=#!3Xh zJg?r;p`H$|+03Dhgbg(`;Ziws8UJ{JutOy)2~FOtNH^K}A1j7zQa7q%8yV%I7dI^( zB{-#`+t@f9hHr|)@GFBs#InQ=HVAgT8RVQTu>OPi$jPAByeK{}XWb!lQVVylLR16H zQaKu%nv8!x#u1^6bSr7GL15Qz>{^c1s=uarG~K=SrzP5`Yaud93^;qT(8LA=b6uj? z8r8mA;5vO6*>+?M1CSvL>~M}oz+@(6Hhqwp0rX=_eZg|_rKYQJ?6lx6;8=4j89~>G z+%6E*LUr&80@a*};n_;V$E9g)*MV)W#eix-56+7UZVrKU2nXmL8pF|G`^OLAeDMy) zuFl2MU9tYs+&8Xw37*@td&aobr&#+b!KUZs9LD{gE{?^Xt~SAY*7E&3se!Tr6o=tz zZQ^YXr0{3e(H}G5+cnNb6zts>;dEfBU_Vxy17K8(cQY|v484w4-W(mfXln>I_ghN_= z5lZBQ5TX6mYQskxc0|Vuj&Q?eo`OtV`ZZ)Sa{zNb_72Ix4$45gEzy={1^%n~XpN~4 z;t~7jQo#@|f*K7f^%*L8h!kl!&b$jKr%~Gfp!8IeVBAqh8=rkh7L@+g<_9U|EaECL z^4NG%O3VLwPy*W(yRP{KAB~ygpL0jOXAgQT97`gh#c-@92^`5qCS)MF1yErJ5eD?R zNgRgwqFMfNucu;VO9vuO=xpdMyGr16Xx))hQbl5j$1K2DWg{1J-2)PJe{}2yMmCb* zw&fzSN;kxVqur}%TQeto%dDl+tFT2O%RBv&s@xZeac?AtN_?#Ww-8O;1bbQQqC<=tT%xRNa8W!htcvL&?e;+vBn5-C! zJ*s2`auN)px0f~bci-Nv zOMdoP@;zCRtdk#+q{bZat8^c^;9^FxZ*#VmKxfCtZHtw$T{AZ;!obICP@|Xin{BJU zizNH9_Q7k6{&$)z!Wu&A+qGZzVVY#N%TnliQ>`du+nQ4#%-|0>`(Sb-A?^H>dbtsN zMr1*(K{_0nzd`C5$@hgwWC+pQ+S5Hln9NBqR2~w5C=i2ueiJQRRFf5h(I%) zJg8`?1eo+mf;beiIFghhM5_{f3N1^*#s8TPg7GE1=z2>BrP?50G)!LUalW=hXR1BY z*IDJW&DJOtqyoudvXn7N?9hd!6NUCPMq&njg(bFS5@#Hb5sR*ypk0+s$+pVB0G2$g zw%xL@ox4*aTTv-QP==~0rL3y}ZJ9%v+7~W~R>glY^-`&s^366)wp`7pB2A)dViV#6 zKl`;J$=!gvv6&eU#9-5;xhFJ67UWd$Z_K=>re=^K=$g}lNQp*K0;aeYHl2oY;^|3v zQnk!He*Mug_)iB?DceHm@mn!;6TKcSZ%K|28=;FrhWaL493dfz6rLQ1Lz#gSt#lWQDnpB-f|21)#~c7YDay;xC_P{Bk47j{czS_Q(ybwofq%1jGpwt^U+k()2KU#Fm@$g1AA6D;0a3sGvI5oY#B zcGxAnfQ)0fD~4bb;2YYwB)}!GhEf#U2tX98u#va{QS|(b9A*mZ%uzAm=7<|( zYP?mOCePV1>t1ishr8pSMVf{5?;{-PnjDYv>mK(#8ip24yOj;IK$BD)A%aLHC{LAC zp#m5vqtKZe6;UV0^Hs|mEyIr}y*ltYC8Z92YORL!!pg-7doue{%@9p6wa3yRJWa?K z)S(2-Fg(`^<4S|2{I!B188d`^WDWCN9dh|X9z>o>q{il;oR6cQaK8laI1bm0t@r_+>(m!{cGQ~qbhZv_f) z{kz)eXh^ML4pJgK5^}mhp|RqLcQ#WdZngzZ7cFGDUxr?1|1N^zn$qG=G^!2z*)}11 zeiTRDJ42tpoAqaheKrcOwlURU-q5;jvT9bBUuT{M|Jl-!#guzqvhLjlf`e#f=Jvfh zB}~;kRo7n?cc;T|yy2P%_WG{B@&vU`a3QoVek$#iXMSs4`VA^>Dd? z@~*&BHklol1Y2n`t96JT49MRSAl=%C9rb4&nn+WiR~dL8OU!(!Ko(&|DjtM(C}oQr z9V#R<0>v%Ty`X_}=^gAy#wRzfv;YM^O4L7lL6V=VUwv z#ul_(+r$A(*Jn8ug~UChR%MpZ2i*xs#FU18Z$A>DnFw*M5w4a!ik6%RzZTNIZ(E&w z-mdWRDe^h8em$mMQ&sfEJLCN5`^3t^vH9NlQapY#sQk}4@kJccwU9SZke?asd#^^Y zkA+B}F1OorAAtIST&TJDtSI7fJ_g<7&#lDmj5k9~J!_}_xK!b2?~kU^0=~;+d-F(s zfqSqQdRIW!ukG=ou#1qL+X?o#rt+!R@u4qTzj=)|q$J&hslYPjI{5Ai^jL1)|xIfHGZVU*nJFR=qvw&B|*c z`g*HVVLgpqDX&{S@XJ)V+;A5PK33Rm?l04ORkwdZ?D6&DgBsPYn(gSGaCjOLdo9er zKHSe8F%VP*HWJuDRcox6FgS@UMy7k>7(31UCyl+@03L|W!TuC%kPsoGbU!Gu!FZUm za}Ubts(D}gnaMkZx@|R=-^E&+2MbC6$|p{|4RoBW8XXq_$)np=xH{wnOQ?>iBm|z} zsG=pQiIjY&m$orC4s_vMzu9M5FTuf?25}TZWsL((<*1CaWegSmP}f8hO5otE_wwXQ zlY%o33fBj}j*+H<@FnnsHn$ND>voNeaug0c{|{k2l=>?Q_@4UX9gp$amqqITZ>GS=@u0G1JqH$bq^8`_A(XbhVOR-moFHS;dUP?kjG=pe zJaw{mqr22Pf9=WD-Tt*B1 zGGpm=uBG^tS=;n79v*&!y6iW*noBS2#iU2pO*IM{Y}czJIiJp}{oNqmO&xoc zBg4MaDtA&`_hI zsBJY%4EVpgngjd@zW zl|#jeh#T>oiHJZehJ<1nkpf?>mq-=rvB)%(|NpGUa?6BXHHATg@lkU?EOL_|x+Lx&5ZE1bv-=QY$ijrHWqZS~#1H;v@&kS z0_^?Yh`qk#UEPxhdMP#Px;nwS(0%-NjAIn`K1PYclE^!xyAN;yh>ci|WHgUxC7Fdt z;~|nvF5x(!T`-U3EFP0D=^|r0B0`9c=#axGDAD~If^`JDhvX0jgUKttGy}e6c?*j( zK*>cy!!Zh5%~dsBSV6y#dy!HB-5}kfSY}$iigzof7X}TXp8G@*RCpE8m!gao6k7f zb(^i`I})l6jJUwr$%+r(@f;)v;}L?4)Dcwsq?L{xe36x~i*PYtJ?3qlxB3+RaWG8SR)VD*v)& z`iX)ayP`Pf3OZV(21T?Tjtb$8ipX`^H#v((MMoBiN(WG=Tjc+rCYKrcRooZ=XyOQG zJVs7R2JBV~&XsE_15oRK6gBDmXH`OeFAsb3#q0H|>iHfizYZe+6v-=2+XkXKU>ip$ zNe*q~WAR{1NW&4cl*6t(WQF+wK}QKJ$_m@o&RXuM)e7kA+*tu zbJ#P!o%$%m?QCDIm?fHi*7%$!df%SrLGcZMn!+r2BwF4uE8Qe09mOxj5)sal-bItS z2Q=iPV3T`?x8Bc~&w_)+leyDpkf{SqD2pzQBb^gDNplKUV2w>^-p_zt8@y<$VX|R; zAv}r^pYa;bXuvTvoN;(86`Ab+T@!=u7xn1A+NLlF++ZfERWW}hxfwtv(un%NLr!A; z{l%!vo*=m>a(M>^{02dHhIw95q!3c5gEy0uqo5Pu% zFG5x=vgKHj!&@>uY7*rnRkWwT8}H~kMW+W<$qkUs$?u1k1tW(xcG==q3XxMGzBKTW zTOOyGEx2O_%(IZ$4TDNqVSyf5W0UnuOq;rE%?{Hh0`<8EA?~(lD-e{ShM~!dChEV7 zP!-r>RE<-v4e>&W$PV)KKC12(^FW3cVGw>KPhIDv_k8~dOe=WPM~lwy3v)fRW&~t4 ziX3SrE{ZmKn44lCq?Y(ESl=|G@7O4CzWaU$Jblz}0DZ~i_YH9mx0e88>8U9()1OH+L3f0Ccx?l$&fM442ns!$9-ka{Mb{5fh&av-_xss(C{D z!8uq{8(BtIz`%8?{1)RclK!SbP_Cb3;kScCeHnuE>IuorE&8-4_$JPj_9_S&sk=Wi z_BON!!ka0T6yvnS;bHnfSc%)Eu?n|V{_nOoi$og+!p>{HWR3#!0da>((W}^f`eGYGqX5_G8K$ zIl&n7Ni61cu?S~jF{^QjF#66bi}LI?eT7MT$4=InK~@az zO$rfEmwR=t-WohzC@*uq*^Z8PARYBOE|GRLot@R~SlC;184I&!BypLLL1$&k>E45? z0Bu}PqPQtVRAq`6jL zMf)qij=UQAMA5PGp&j_fEfUuSwbp`VWHkd5_CQZCAS5EmQ6e>kWBQb-<=)T!i-uQu z(yk0PnNdVtg=WHIk<^8HlPiUpWtKwaCFFEJ8-CdI2klbhdL5cv>~uP=QRVMadp)=V zhGhIe-~~2U0cr9=PeYHr)Bh)R0doCzM>`yiNQfFG^l5P$Rl1qf0`6{4o9~B(nBybY z!NMy*J`LC?vASK2Zx~EZ>wQuDL*3>|@(e->goI;jK=NVn`PMB<=w3Oa9qZVx@^flj z&pEN!_VzGdrs1L3!t%L&ZMJ!z9EElucw77Wdi43CNKiphesW!ZRf~#JY;mQ{V=1iA zls&&;=W!~KuceNrL>I)kX^)_+IoeraYVaAMG)3umO8w|qa$W1e&#<)iQY|)wQQg=T zmvFL!*|2L}&U|^S1?}2jPX9WKILqq=XUxLEVWMo@!NJN-!FMo@GmGX*aS5ZN zcmKn|_fgcvb~j5mt?voZx`wmNF5^oTH=qYi<1AvX*#cwZsvd?d^FE}0vwaOO*fhm6 zQl?``Sj?P_+jwBsUiA`pUB!A@F=3K>8U)e8HWX>(FIRa}y2kXF-QaXDf@d5uXOsD<{m=zHPOIrb|23A z*24|4tltj{;R}rBhEUnBwfvg>a#PErwbCBhD{T7Em#5$HBiv%c`h@B8pjjn zcPaIX31yiO)cBrmWA09S!Qp(L?lQ*57_@2cuAd2^7X^Dh2)UMueESRFNEu4UNRUFI zLMN$5DufkH0fe0MrKJ$;4PLE{fV%bQI1HK5)dc0uew+!84D}Vu+LTyD@DK9Iz!55T zJSuJoPu|?i=mui08v&!;zUxRxiPHM!Ng!#;W1%a6utjV zN@U;Cf6y?5RB`9Ar1gIlC211VM;PiG;*^*DuXYlZsKteRBu${5nV_^D!cQ6e^(jRl z8)SxTb}$jaH-Kz8{>cQV zyQ^$`U_4@Jit+p#45i+OGIE zUbpfrS1#g8xH_zru^$<%rEGuQx$fsZXI;+ZVb(>GZSTTv%0MGw=x%j@21a=uI+?oR{avdFef%uqYua=Gt626bKz^>~`+XDkot~|+ z-}=-q5To3=F%}M;O=QI=A!`_k-pAB1jw5Vx`~u2*ffB?F&uK+do-2lp7*9u6O_VES zUWX3K1WC4DEvaZ5GaO7hjynH;ubi!wSkpgg9Z}Oi#N}Fm5Xa~;3+YTwSJkKG|4e#= zuxADOen+ePaf|1@g`PeqtDL$NlQ4$~=%|umB(Y5pvXljrN;dUc(7R}<4VI4`rgJD@ zYps0hP7?2_l$9~_;8c|+gQ%DBzqd$mWcqTXqovE$e|l>DYz}b;uBXgva<^*#r%F_t zBNS~CP0E>7G)%iRC{B+c%(~k8&CBNXDEYe445VAl3A3pTUYhP^)&5FMx0)08cmf%#qlF+~aU$mece?$b#1&H^IZnSu7QZ6fn^6CJD{tI@jwF0? z>FRN4?6TOR-e;OiHnKQE;{~oV3-z%TbK~V)tQ0mUa1`Ld(lOG3uzqg>VL^UknT6-zN?blx*HIuAsL|6x+RPQCQQk|ML%G)MM6>=p zm&G8^YqOk*>Gm3dkGpN?{3ytvGevUtT5}4`VW$^&ljCt)ytlG)u%)r=;9m1^HsSi4 zZL{mi<8Bh^m+N?o-1j!}mng(BTuqyaTC&l$KKo0biGq@}rdzFQe4C>BNE z>zDE1@5h8+@!KriYu@8yu94?kmMc*rodR6Bw=Mu{FJD^pUg~nCG#l&^$g!8DzeKq# z;}^dquW>bojCbs04-+Ti4r6TY)|Jl?OcZCOuy@qav8+Io*OZZ^Bb1DZYtH|ZOmf>m;f)SL;nxQLUdP(z@L=^hC02j+JPT`;OmmQRaidNBxdmq|rk9j=T1$Of7b>iRGG{p!BA5oz6#UQ`s>b&_P5 zBqlhLED}Zy)COM+E+k<-EynabfEMso(=U~|Azfr^PFN$zrz zq$B6-n!(ultO5~yQ@h8yMM#%H-j+pzfa<3kD`C`QfIMvj%D=NyQQ`nH2~D(ptQbBs zttwJs9UjfZ<<`uH2^&*(>t04tYE5w?nl1gtX#c4k*dxRLzVx zQJT8xa9PLft=i+o{E`SzG*Cai<^G07+cLO zjaO;YHK0SUyYT{N5wh^OXPmD__<7F)jF&7wlA3gg|{QiS$@DV@_)7#()jA0~fiq+o2(#hCf!pDK;h5BwaemPytAwe+a^- zgLt+>TS@tlCbP|P(Kiby1RKD!Fdy6>Y2VA{+7q&B^6nHUc*p$lhb2l}l4LrKhd`t$ z2v6cX0mgRH&JiM{5*nUm3-t@-SYjy_VMNhnLZ`@DvgGAzhLu<}NC||!(l?&BmiASE zrm;=aKy$tKw@OlCId5neiQp6{hHeBH#>ct*oOqjhrOxQ6nrSq}1jazgz>3Ex8~u3r zjXLVfraXwGNC{fa*%zH9tD@0dS8i5sEHF?uaeKreJMH1z^$PHC@0Cl9E-5HT71GB+ zHdys1!ca6USPI5AY9rM;N-m+pNYNs<$%B(jvsxq&O4C*bVEjh;vB~|A0IL?PO9Ysv zQ|F!kSs?~jmOT{tNlTS(4mR1y2s*yx#iy}QUEo5eIpqL!d&3=>tO3;F&9(2$6G^D9&o z{(vAF%RK$bqE(;gQaw@7gLyPx74`-tUvt(QmDjDI4}4XE~?l;$|f;P30i&?Ye)+yJ(i0ybe2pw zDo-1Wpnn=PBV$1} zXMoq5@%p5VQy<}R)hJ;L`;oF-h(Gb$yFcV(YG^jHKkKW5?0n-|eR}bS9K%l8J!SI! zd!EbJ0Cii(pG-&Q;xuvLyVSCu(7~0 zM3y9YeyiJ`vB1Nn30cHqG7%ocXuYXJ z4T;1tfCyyFIy54)3+-aos)w@JV6;n5L?PvY3g1^GE{qYK#u@j<6BTCV-ly_VTs-Z0 z1$$}D+bCm3CX2N{-3O?(b~|4gRz%?}N2dt16C@F-`aFa-m0O3RcNu&=2=lEgdfY8s z#Jx|nDipzJI%cfYy3I5tLIkb*-QGPc^d#R}1`NJkV5q_0p0vo>EZa@&bG)`3w;Zak zR@-=zEnQC&rri{&Ye{RzB;x6!CX&}l8}IIiozv-lT}{j_sAAH8T5E=dYC87C-vuO7 zfMOH;i!6=SB4-;kJmO-ImP9dad+kcMx_It$zi_%@2|~ET*w}XCpTp593iPMJ3GfG8 z{jYumLG3TA(s4CsO2BHNp%EJesbXQ73L7;s1@9?70GAT7pUTjb-pigue0{ZM!>|cT zo++*f@}Mi-D3uS48d{@}`3A$>hYT`DeOj>?rn%CA9XB@H~6MOqi`Ng9gmj=!eFg+3w~q=Qn^0>lq17bxnWH7F`kr~$s6LmiXv zR!avJ>T?Za#(K=6GP%09*A=f1?QSZv)TkTlEK&5ED0^t`D!>{em}_(8YKb@9W=VMK z8{cl1?5drQjx$7{&Rd%w=HADH^8V?#{ONCHR$bzbEsFBT)(aR;h#6bYZjP?>fYU!< zR1(o;<+09yJP70nT|0}E5~W-GDYo&9G6G-tcNr49s{ikJyflfYwL5tfZ7{6g+g8Y` z4TrhkYnSttiXgs%AGg;@*y=eEg@Dg>&}lgm2uYVaMlgeeK@Q)8NR>af#z-C?tIs=p zQH!5T`@`&3&B7}>i@~pbfCS6}rd25DE(tpFBy?Co0Ar+d^l*p704n>6FybiqXp5D|X%+thY_Ec`W8G*iQ)hhOw9_wpHg zS#j^#oq9>>gE85i2!j(jRS43N^#wj&T`Xx|EcoI3r6Ix?KlkTNYns2&12>uqeF|(| z=3JZsuM7oijal~X8uxRoe;I!eaYN0Ad7qFIrHlQO#yv`NBv-?j zd63Sa-DJwEfz?cplvhXIi;cn2K{xHWI5AHjY?H=)bG193&{bb2y|_6E*5o?5PzjDB zLaH`SG?5I>3ML3rs$}VcZiTl(nSP9Zh-AWDq$;y&`IE2|sT&5>k=FYV*#%TNB1TL) z7+g%)I8tkeM*4s6)k85yf#^;X#ONqY0O#@FHO#koLI>zuw$#9<|5YF;^3w(!e#PH0 zu{K6v%Od^gN(zsHXYB-Qwf>vS0-A@J%RMj6I46P4Y@|Dgufo)WRFwIz!^E~=62kZ$ z{%!Ex<&IHJ+Nnl!3J$JXapwcBm&*ryDHvRO@s;PdCId*S8D4cQS0xknYRG-R=s6)= z5#s;AQHg7F#-=-fZd=+?Qt-KmmJnQ(0y^B^zGv!f`R^XSw{IT^-4)fr(OD{CBBMhy zqVSo2T56e63J*0a-`|QB$p}Xsh@>``C|sZe0sW7u3UOCH@5~Wg6~0dg6cM z4+l7o>LEp1?-$P-i}c~Q#hm%$1HPth5abDuAR?LW`V%6RCpafM{g3*bqM66aPsn&# z^^1F>&Z=iS!c-Ze<1{=m{*YO6GUY%p3u?Cl2GFV-UaR9TMstw`hpScKZ^d4=q-C>f zl?46MwBOJnrlALucZksJO;;!%lTL0^d*2%nk9oRx9RqKbNjTiC{_Y-qr;^ut;Sv~p zUEB>#r1@)pZdk>)rhgtb-mQvkK?YKtEKp>V!aP74ZfD>Yqa`_cZD(CK{lQry_!!(%Lc=-v=>&MIJb@p~K#9$Fe0IcVJM_JsX*FlAi zz1dXwZMytEu%bad?+xB(hG6UGMw0mu5kU&}$+cgcKIh)A2Xv!}F-D4pzdsM}??STX z-C|2J^O#Vzxqa=P)^hH)Xo}-hLV{NZu2bk4h+^1#osBcR0O79H+FE!z)EYfq_KR8j z&hM!qxlK&IC-Q5ma4Gn|HJd%h?s@cmse0G8t8RxeTe_Q;?2Kum)_+<;lM!7&k-wB0Ad$waCT8o#IN8 zz%bmW`cvlu2#Qk?MsFq{@Xl5d54pw4Tj6?XYgXspeTDUn2pknHkl@T&;kq%sZ!OYa zI^Rc|;l(z%GhNK72(n@E;Beg1UJN+DOIUN`_qH0M`5gjtlCU9}O=!DEasT&=V6mrc zAg*X%ekWZ4Rc5ddx`o_UQ8a=qR55myHzoIraaN>>3^vVb=3GQ|Ld!@5TloO0N0c`l zoSCixb0d;@b#s6;qw6auY`d^yAMM*{Xg)vCb-oTBd*%kw_YXX7$$H&M&@tiv^ zWZ*>b3{Bqjki*>H%S-Yp$;beBV${$=BDnyt#JdEOXl8LNC0-E;#yQYW)6-pF?mrVf zZTYeOF}H4~P%B>*ij1YK;Gx1b-eGNoFhz_gltt=NLe+KmW|PI6|CGzpi~Zht;hZy2 zFVA|egy;tZ5YJ6fb>k}~giK~q0 z7S|+(4Wl?Qh|}hb*z>#%@9O@L70^Xtyk{8d@z&%LbxRN|`#iT1@WMeRLl_;#xBS?l z$s@woB&0G*b`VSqp2o!{@(iCpa$b?0|1(p*X7-~sry^33HpEa88cH!RaTJ`B-c1c& z2NODXC~vPzY@a#`!|N|4bdoBzeEiXz#D-&R@-=4zEUuI#mhmOW5$lkY2#bY4cJ3Mv z-y<>JCh+|7xfFh!D!`B^a=$-LuHF|{eoL-uBN1e9b@zUZnPw-!{Gl5&lS);2ZOoGq z@YDAEez{14I-)mVkn@3v=t&}!+vdwL2Dh-E%>G7XjKYdYf4>kv-FjZko}eCNbhqz~q~5tq+Wv_WVfHE8(g`U>`%4aD#5OU}tE=j{&$Fx+5h4{&s9uMV z?(@q&Zf6PrZbA~#?*QJvuB#E^VW`C5ZNx)Z*kO-D7KJ7dI2^PxAjR_DozkvsIt3)s ztKtM4I=J+ait=1ZjGPh;~wrVu3wOh?Qb%CBIJI)owqdlj~c6*mC zpR>C7UbcYpDi)n#-GYCP3ATCH%jQh@A#9)o%L~fi{j)21;ewGpON^RU^M!Gk1Dhd; z5rI~&hSE-|zt%4}r7>TQ%!g#$IoY{)K@5^K@Sa_5@#s22E#xvmUYIsG1+4Wh`u?JM z`Uuc=cGi~OlaGX~f?BN}54 z{WAIBs~$^c`=fRAzCeL!1!m>45-e#`sF+_q3ok==!5be1y2LP^Nt`BY^P3RVRQy$0 zG7P0;ocIbf473JcIdg)DggpF6M(+IIjqJ};8Gl;X<|iLRK@;vkaJT^0!W}TNwbn^V zGrRvklUC429e^^~KXw{zG(gr+A30sw`g6j84Fd9rOrV9tb&BcONbWvq@pm~I6HU){ z`E!cseP)-WU;(=<jAKl~@Y-VoiaM!55>vx* zh2mkcCB%OPfsN5}_Q?@J62XTdQAmOwh{)!KgTy!hN0E_9{2zGZ$MTa~FF5hRfYSW3 zhCQ&UjZfM~uUH?=%&y!$R5yN5&Odu^$%yz6rD4e2_4717zntHu>sgcbmcCQ3y%)|v z)RYPSLS(i79szh8Tj&D4!T-E(<1&!G+3qjHA_&38@L~{EtnThl4W?7#F2JOL#^#-% z`Dfn@u2+}HnZXcaH3zD~!bbaYPo?hHKkHBZa1$>jn=`#9`npr<7usr0&Xa?KD>beA zmxVEvjZVgs2YWGGuJ$FR_RE+(z+g_9EVX9si#9TRMFYju^v)izMB(xDZz6i4jFnU( zx~QLYceyTrLM{i4CaX`+6|!zNv?cT$D37IqEr(0vYG9ND!};=Y*V~7@D)fD!P8Z*K zLGbc@SI0@mGTzp*qw{tUDUnQ3SYFxZe}&Hf21K+G-LqT#6mCEi z8lJ9=xOPyY^RTdhM9^F~dtQPwk-f@=JE_X1$yr2zK&?UE`#j<4>KX3rmDlb*evdkW zdostW|NJ22R;MBK%Y5Py%Cnzs;R1u$&~1cK95PouOVaDyWSsUqRQE`EK{{33TtrUL z)_x{sIY5EDm+LScjt{|uSf33Oe5tIcvqJ8ZhPHu{l9Y^nrploAi|IyVce~d^FSoF# z-m0*IBQ&Co)?z+l6|M=I-+7U$#z!qlM)&^Yd?K>K*Dfmv*82=0jWEWQEgNj-Z~PTj zE;cUyn40_#ww89RD%xPfB5saOI~s0phsAq^;J`!?iL9__z2~7@jfM92X8N*h+x*#P z1vYPyoS`-R$TMD_U1%51c*(O-Vgl$`^oVjHz&Q_rA+CZatGDA@A9O(?AP%{fB8EWN3u+expV8PLKM2TOU%>GG z1~(QUdM-uO7qp<*;dlZuEREG!z4$@8?iV)IJF!K$U1xM$JA^N6CnDz^P(%*0r_N%U z4IQ6Ua03_?nbr-(P$)lI3z3zv%9A*wkJ4WUG~d!awL_yIVuyIbMuCb$BilI{{g}lh z9~iLSXec(XH&*Zf8RzH;1V!dlmH{NxYXi*CVZdy%BZV5*61e&1w?2%X2D|50&-2SY z4I2{1Ph^E5-G(`gTNZ!ISYRT8Y$lNj zr@Hl=krUWWfJ&fNTc&nWX@`+0E{*@vEiNCdUAk_sMY3S`xsVEAo9FlX8`76gXY^T$ zRlJAo_+@ACd0{8RFz%Y(=o(o)x^tDti+@Wc(i^L&Pfr2%&1u`Ue#gzk_SqRH6<@&4 zs8hJ;CH}}?x*dV-{B7hD`S>;xiW#r>dC@Xk4_-z%<5j5B!pQ@h^~=kM3zb`75a%@^ zpo-T&oQ@5;GhD@pq~?~lJ;pQrj(8}GW`%ibfA*!agSOc zlr8^8oyY)-RuKb|7l_(>o)O`>${u)*0PQgo1Wg>ige}PQ+P88a2u>jEM4%bQ%iO z@*+PW!hc%{t?L5h9D}it!i{+c-Ft4_=Z<}y@+@#2(E5!=^n8^< zo~WAT!u$etriF`gc+_^}&11I{o3-;e8(W$8{e3gWIrw?-H;;!UqH}N0m`MP~mZqaL z*Y?+f4WZw2oUAlsTRlRCruOjmQI54WFek{46>6jixC+4HT{38M@5F7R<{%~A8?cjk{jE+v3Kfm zW6{%ID3L1H+xA z_?>4(cp3z&BOZ|aQ${o<6y2^Ofmo9$FPO7|H~NAX<{{^QjiHi}lR#u$N`3~!X_mN= zspVh-sk%5wz4&}t>Hk;{e$%wg6C?6Mk0xBcwHSg+aioAJ1~lrw$V|rEKi-_l6OJ^A za2Ux%O@+@aLsMonrnp!$QWy9Yl=Xi0wop(BV^e54nFg%K^S~G*_XyH5(>E+J<79Xc zs>3U7z#p177&B-s2JC5v68|1V9Sm{(mB=jlrV7lLI_e6^Cd zs<}O2S_@7VzM1b2!yBhFXuj8T1%*~$Gm~T2o#fwmR0f>T9=vX`WOcn7+>Y?jIkn!J ze2r7tpNzA@!6r)w#dqbbcWPf3+NXmWjZnQAK-4Wi&jKmMuT7KRtIYWtt}G`~C4ZOo zX?5lYxvWphC#(3>`0{f1(Czs|YJvvvW{gZ@A6K&(c8()cbnLK6+~ht=^vs*pb>yXk z^9+y5slcF5$3BHZ6;np}#fha15-RB`2&l0<3EK?djSRykxYPOZA?s9%)bpYEj?zMe zA*?%jfKEI`5}1u3Jb~_ZuO#a7A3XZ7!&jj_Up$|{LTrqeY5yaeOq$5B1Qwtum`9Pl z{eQLodByDML0PRSfAwBx8A*v2wAd#k8?po5439ZkF_>;wS;`=|zW?02yGUEvgvG@# zk4G;p)_R6Vka0eMd31!h$U8{5c?cY$A4d+azNR93^U{dT%6H{IsKZ{2I}AfD9F9Q7 z8W;VSfUu|yiReM+Xk7{C*72beX3{0usyFKAX1h;sG9q41&!>=DFHr#ij4fd79GIh; z#rH4(?BLUEeGkXtfIXG%Q>AJ`rSs#brXuE#zPV?w<1HxSpt*=z>1#r$%z7tG83};^ z_~rTcdDwz+bBHrA6~fdRJ$e73)jWqj{MZfp904Tl&Sm?Q+MKGHkPz9m+O44R(igF`Rv@GD%Pq61|j!@19SXWqy%1 zDMVJBN@A5I$L1R*kK+9GM+nW{mlHP2-Rc`6`DNLE3RMGhmD)3Mi*Mp?@v zd65NFRJVpZ?3tH^`>(qc5>B^{Dvh0?upP@)c%+ z=-76NIuAZDU5E+$fB2Wo)M$Kj9!DDZ3)vCGrga=!tpbsF)=yf|K4xSwtbcTcPUI+y z%8e)!nd5tAr#ZT2w7P6$dz(F;UudSUJX5>s9v59^666A-7wXYs+xFHYUNp`Rs#PWX zp1&U5C`mtco(AKFLIP>f_cl%{z&5A^I-~&ohn=a>OBhA$TtiabW?RMJ=*@$u6h8K} zVrzku0j%Jsly^$u7)h-E0#xqtm_`|gMH)f`Ig$$7c1-jxZHt-+MOQ*3p?B<<_$9`7 zk+sIata2IyA^F&Nlf*nZmrl9&_-m*v@T~Skd~;E6lElZkD|{?OuAa^Bf$Ye!=CWDj z5}bf@dWn~H(nI}jFaJN`;a)Wbi)>W;`lV4WTIA;taqeUkW9?t&5r}PO*wL5s<|#3< z-7CkZRZ-pRXvK~geXsapi9&*KVWY^X#B}bXcBAPGz)2Ki3P1w{Jx~iyywOiMHXcqW zZw%u=k$98+9VsazKuJkfq@9c6jcEl_M+G&9;7g1`l{R7POf%^}=bqgNRxXnoyr)6s zMbKgXd06m^!8TE}|H^(1xKS&a5<30)nniMmZxzT4?e$f;M}WVc^!1!Ug%;c%HF-&Jfb&6eMX2U;4aFxKRfaQ8wbC z3UIZ?z;+o4w`U4ABdmM;>Xgtig5zSSG!Tx5b)_v9%+dn%DrsEv zGU?7=T=zO7;y#y>rg)|oDuJAe&ydLyl8!cv`~17prl&ZIlTjJvGw&N79S`#BIbjfM z7O^Ttv=PW|p#}a}LCJl|Xl6MLTEhj_#GccpLjL4zAHt8TrEGjv;g9q;Y+bPP3x>uwD6^d4sKqqsA%(Yyyk zxIPP3Kk_%cGQ{t!ES;1?Kzar4e*_0ZV*V1YAaLCica7cr+la0UJi=Xwn21};&}xU` zGxUr%5BcLieYcr&1zHg{Tah-FQ$8*Nk}}2|2qrs)QAk7gV{xt?D7Cyi^F4c)_nw@) zj4}xsZ>|Q4j~DoE(61v3$<6R<;mB6gCrZVo_4luTYbu>#!%0i3wZqFA>XDR|O?p4J}ZNe_Fe z%SFw?*3G7xAaDipk}zODjnRK!Ncf7naQa!++pV);7ZbL2O_+Z%F8A=RO%mE2#j)Q* zwPgt0NT%&;QVD~iQ;P*G2m|&R#bX=d4UO@g=m!$ZP48it=e0ME9or`=R`sXTp>SlX zl?K7Wsc2Swgk$kQLJ*Be{0LTg-8C0O*SUZgLIHSxDz`l=Y)Is@EFr{XS~x02y~+Ya z1yf2}ZsH&3`!Lq$mVmbE0CkR8jP%@&%?OoN;j;CDSaK<)8fXfR3fQK9`{c*ZZ(z*X zZ6=oO(uAz{m(4&mLSjYk-9h=?C4)4S!w6yREY7Fbb6H3a%sA@VE&0o8bLUt;jRT9uipilaikp5P==qAu<$7X;L>p ziMm8YAQg~|jwQy-9B_-E*2GhcR6LN5RR6PoK=$5xSKex>K1tgO51O!E&;-1kc$D%2 zK*6|_E=?YCQGS>qI~S#*J&$E9j0@GZPVEn&4GD=2RY4Z5j1nmcCA6aQzrU-rX`-vl zNxE|~hA$-pH8x*a;+AsKt2FVJ4G+S52YkU89!gNTV^U$X(rT`R$7-nyd z3jHOW1oODnvCnE0*R?YvN<&8D8>qQ#dccsJms8lx__W^MDAhRbd4Xqj?deqI2Isxm zf;5!Sl|F!Epvsqes+>D;aTwU2$kXd_e@#TF^FCTSt~lgqT;7g@@U;K-d}i$m=<*cH zZSSTSSE0xh^{|fgaO0132sQu!qvCiD&q^Uz6A5K)63L66|gl^2dkg6%tOv2a9nh$UUBa|fBX5>vV?HZU!2c}05*E$C!v77mF99eWQ zHh23C;W$A)9!{*@>%R4oo3XQ z6049(p)hsD$LMt_es?=J8^scaShAd*p1uCA&#Wn1Z`mJn3N#tejIWJq_9G((2aan) zftSEzm$UY1Gjc01X1)#t!k?zfajR%N?HDc${mI#hG@`5^gA|&kW!@MGzYz>kXBi8b zUU>Kk6xp$#8f93}&P4}ou^_?hB_Jdq!i$5n%>AMR2uveX;F^xq%54XT)p#EbKP+sm z25}gc<#ed^aKdN-QpWo;8jmNoeYm{tKQ>PH7t8cIK74%You6Me`hpeSd!pDK~e zr?aVjyiJeBbLMtyEqc1yz0CZ8Fl2Iy%l4^z{&h}!quEKg#F}Yt(HOj5IDuK$+qWZCK!nd^bZ$2ed+zQJ z4q)V&Txv;!SEIO30t-6rVC-skReGCC0{doR?WsPwo9XSjD|Xu&v%|%$RDw~zTF2S? z_Q`ncsAwMCjH$}S^;bjYiZo@QYY`h^j21Hl_GOx z=)`VTQ~(tZic=JpT9FIzlgb<>lBBB0F#Xq=g}Y`zmWf41)ry0@-SJ}aVH^qZUC6SuYQ8u+Wy^@!CT2A{hhByeAy<)gFKTMI#}x6QM#E zN@w1AZ6#(#xlM@q_{VG|l4W-mWsXTbpE-BUz3lPS%J7K*sB*N7|AEd~;=mY*Mj8@r zi4KDzu0^2U%4km=HBlhoLF_soeMP`mSqj z(2K>{GNCiSKo*<_4^IHiz!!JF}L~ zw(;&d$Md~3@+Cfw9+!YnLs4GmP{Z`fi_n#h7qSBVZB`-u{7UxP*|{3sX5HwT%_c>W zy0Cx9(cuJQzjN^EmPAN?l##wQ_~%!RVvrkR$}k|x61V?>Em`Oz4{*pq9VXe1g9p8I zMSuA_iX9qVFNo@w8_Zi0nt^lY(s-U>Rc+wD&@g;{fR+a(OA=5os#^w4RVf^tGGj{$ zB<(9o|I=9_)k)E9oq-Vt}1L%>9c|}J+<0twP zU4n7_7gsSg?QGUO;z+`bHkMKKg~^=*3wu0sXL`eM8b@JRS0zmQ3Nd4RL1TwQpi#UK})*@aC*gz zL>FL<{L5Zo>hF$hR3w?702`7Ra3_M9#J6O&HS$X7D86@kq+*^8G4~YC)%gsq+OXZ8 zP5MoiK^8yddHA7?h@iR7N`;=N9IQ*`n| zuMS4g_Yx7%m%7x%Uh+w&>Kzs9;Ka70^fhSMPaWUJMNq2nV5Kw4R;JaQD0H+-JZG+3 zkgid;xZ@awf&IXV`#>47E_2}j|9E=G=t{b-Z8RNsY};1Hwv&$S?AW$#+qUhFla6iM z>R8{t-{*`oM*ZA-)sL#0bIo;0!l0$Xjxl3(ZA93@Z`ds$tOP4J2euvl%0BH&LM0O} zwN#^dnn5Zg@ybknT?CZ7>>jh8Wu>O%;MILY_IhcWKY1t3OfoC((!JsH&|L=!M?-I= zF{iwKZuJg-kRbMZl)|;uhPdFlB?N-Q@V=BY_hxol0g^rXTDvdw0JV#VyxwIAj>@di zGOln#vl@qtBiHG<2Vxhjg}ta&`+77@$WbASnJ)P8?(-$l*-W&*)yRPt%6HOzI`gA; z`38^;spI~=bBZAe#niF?nI&A$`3EG0J6Gu0%-hwoaWBTd-|f78JNNfP2pzJ{b{drR z7>bn-LEgllKE|S6evX<)FHTc_L<|GEicTDvdm%(fa4h(zh!INjgGx)ebDgD^*5^Ug z@zWS*d+tR+4!``UQQ}iU3A3U>x4D23AC=ohuX4`yhS%N2W*|~~aB93(q?h9quACDm z7NrK_3qD%5u6hUI&_@w0)8L0)Mj>(uo1QVhd4Q9Zh+XBtEu>T9S`?anXpUAZ8)%zi zpUJ^&I?xVrSK_gnMAOiQ#eFM_yuQnu7WFGTZBS0!xU=l=6Ki=b!<*R~M*i1$iCz1L zd2`j%)U7QGOi;s}eG{fFYe&LgkrHQRDLwXJ5Q(C>St~Be<;!|beteJdLU79<{M4FG z_X6~UMRC+(`qpsR>Ub1(VIlvP8yPb&w%^?fh)&$1=1wR^6l1picLQ3p@JWAIkS);+ z3xQ*ILTt8hszjm@I?{09|DnBnL$I21#(V&FGFf7BDSu9QJSA}DAySFphZ2L-{}IY5 zx#uAatD<6^Mc1Adjja_jp5KYBT{V2`F-0!( zgfC-=SOv^#AvM3-RWk(C4*A*?G)_yd*})~d^lO3?OXnI8#M5<@Ly%mA$Gd=!(Y$DU zO!;-*>}t4KU;Yp@zWe3uI)>s_4NtX90HhbI`gyjsF;*EFehL}(`1uV#c!LM9i5T#$ zu@3jH3+W@W-7h_WSO#>jlj^~8*DylgoEII%2&!XdS5c;4@UvDE?3{zzJ#Kig=~VGm zFCi~=>g#6@XkN`#`}UEAG^`hC_-uB=db2Tly7-lf;9j2p;Qfu>oQc__g#>6J8mf#N zwG6FXCslpkgH;Nz(y3-kMWLn{ zOdoD1N+Qfm8>1qQnx}vR$uek|6>$taGmw7 z9RjihCO5hGz4rCaE+2z&%W{8KOXEUbawHKZQ}pHZI&pR2+D8w2{4N>K`Xh-!DIT!7 zc*pp|Su)ZO#og%M;Pn*qr=8C0Va@#A%(-#{3N{T@2A7A`+Y{7J{Mk)RGc&}`c)o~= zc83$~tjTkZ)zznxy69@1F4aqxncik<>qCC*{{Ze+dVk{91HQG!;X@SApFDb#9ffA2 zgXU%GRoZcWgzG9;Gak^YV{;gosl$`zeox}!0k!;*yukx844Zf-q--Y=Q~11EolafO z7iB}u5g~uyXJ)edvY%$|2B%&P9VApyvc*THG#twgA(;`9y;*@Un}zgZ0@x2zJiqFC zTFm7n{c1mJ_aEJuRc+)*_QE(&?T#NqcMr=yiZi83LYl|Q<4LB0tf65N(c~M6X))o@ zhyCb4c;va#e50?9P6PkuP!_F;2-3;IaN~T!*lAOKO)v+6wL zSJokxmIF8O#M78D${;lQS)!u3@qcp%p?~wlrA=ktHD}PX%CVg+tAl{)jd74%yOqGN z=Z1dw?EzgaQyW=N#jGY(Q26u)BG^`1^@2)#sKXXDwuu&}Xkg%vy~54-PbC$|F=-km zMgYfv(VAQHvWQ+NiYn65&XAex zZ2@41pIka;?_gY;Gehj-cpyN__k7v$dTvqrq|l1{iD;1{yUdY~I3H3^R+El*h^9jI(h&=C;fK)h>T5RPh(q}J=m zFs{QeWNFiMwfS?5 z1pGg9Hraxykeu5vnsIHb3tBWg3vlc4msXvIpxgKTh6eU*>k8?+AVp3Y+)eSOt2mO) zk1pgH)hGVIh>>Y(QRs&7u+0L0&>g>B(V=ocZ?@YFsx$NfhUGq8)_GvhS*A=SIH`On zF$^@-Z=?8ACLL0P*Ep&&HQY7j{|F9Hc3O+_OzFxt#p3hg7!EsPk`gdQvmq*t{@^-I z|6|>{FX*-&gk{8>&bfY=^Y}L73#`YUY{e3C;;It4pQaO!b%Hpj%lSv&k;9mm$JkY{ z%mXyuq@qv+OZiG>oLthD`mQ>(kZSCa6o!N`h--zTWew;P4TnUoutryBZtFMg#NYmV zrcP|7WQWlf#c~G7l)=R4MltoMLzF)bgjd;j-3Dm$@G6s(v<0mx>Q)woxBH+to#Bir zL;6b|+GN@Uewe;tB|-jqA1mmHtCQd1*6(~3vAEy;ry{07TPqSui+h(B3g+Qin8)>Q z_?m>9&zDntHavQB8Fr=a*qgQb!pm4a^tCLP^xBPOL zkuB15sG+cA2|>PcXCualgZmrY=z=TK>&g3pp|WH8s^$1X;UaR~`z_&h6F@Cu3wkKH zpDY1RYBLCpe2o>CK2H|jiD&}4&J)ZQ#RAAAg>_A`>NQ15RGP`>|-MM+(EP>5cLd zK!_Fm)4WBLSD3~Cl(`a}T^5`dgV=Iyb6<|^=TYgrf_hxCKm@tA(NPLcj}cuza8NM+ ztL*S(B?MhvcNHo$sah zD;^VBX`|y#pZb@2rd@8hq^ImbM2?8oMBBPm+us#=;o`SRpuv}4M;&Vih|Rd_ zg&yMeDzD*6&2*jSXXkpa>2T>5j~a&C4wRrLaXu4S(!bcBlzCqVf&q zpJ76N>Whu^0Y~>Kw0hnbHAfVbr=3&Ur>n3tbsBBfP-=(_qC?K;iAveeF_Jdeh~lH0 zgjB-K@vCrT`L4aKnMLb%Xq36lRmEmOqcHpS4?X;H?S!;HEjRqpRdYA^$^E^JiQ+hlwcZM` zDw4{B3S=fkKC>yT-7`m5ub5M`A?g@*I9qQc%SO#ckFboj8XXnwt4rgZ9YAC{(Db5G zZI37T=JVoaaO{2e-{jXaSjWxN+yMJ8wVLO<)~p6a+_F8^Wa80%Dbq>ZxbC}rCIsUV zE4*a|>g;6FxY!ekD5(}^PBn*Pt;SN0%^uHMh*WJM(8%mXBHSS|7~u<2(w`&K5d7>9 z1W8!9DD5Q*>=OxqehuL(K?ZLWaei22wsnb3&ZuN~EJxOG;R_H`!2eL|KPVqs3mu6{ zR8e>c-m~2aR7gssLLQPKGdf7&|5sytB3#{azaNFA@t6JiTF`g@!>tXb7v4)}9@=P% zcP9kP{okNPBaq5i1xJv>u%ZH~R7eIM2GwWny0MkUSc%@yU#>S@u9AjT^NoEVD5}Nr z!b@80K*>DDe2U7+am;{X6{RWQe#0!@l?(kD7t`0726%lLXGapc-_BZCk^V(vVP_2w zTQ2+)%-<->BH&1Yv}vM2=)Fm2nPahc=EQv2DB7$G7lAC!azP6DddYjw4AZPA38f`E z&vK!hcw}hdbPFWPzH7pDfuJB#NgX2--9g8@gG+v#fTBFy&}sUIGw{H!$TtWIuVSyx ziO9r{V2}oS5Ou)lq>oQMSvYk_l&W^m_ zX<5Ff2f{e5l&_il(7lkQ9y2H zWQkoP?{8#HW_PLfet3UFK`_XQ8#H;;q$i4g8XN{{XpT1YlM%KVe&=R>I{3XlMa-An znAz!sns2T3v`EJln?D?oK0aGiuF4M0nAUkSc^x4g4$J1Izdj9W4UhFQN~gu9GXBRF z6KkO}x!#Xk19PsU_i-AJOw-$W8laU?@4G(HTkW)XUst`@Pu$l_)Bu@#H9o0t7DHd5 zH<`gqvv2w5!6kpWj&6B0)~K$Iv{{s*)L!LOC_MA$3~cfYS;ulbTX;H-Z2zx1Rk&a= zNBn@Ie!ApLQj|n!hrp1wN+An96YR*Ir9mq52r?L3t!inhe*36wZqkN@K9P!IC`7o<-hY9T41y zqb{XSlzufl)F2a1Ju=k3ZTs(~vv<{;_QKA7U^wAx&-H;Hgq{dL5G0c06x%cmh>k~b zFiSTIfJS-Def1P?J#{lXzvmA#Oq;gIW1lJ#9AgsJkV2S43XF~dZ;XY;H#F2KH9T!Q ziF(&fEq?=0qG)^DU;Mp9)MtS#-5P*)*Y&{_4r_^HJqy z=6W9fx?g)xohkupqm-CSqzg4GBa+Di*^d0!loC>7^@6i6D`c9y5P?bo4&cAuM4QMg zaLC*cy#;*Uwa2pfpT+hAfP$;83fSo`Rv7f2$_RCy)** z0yyCI#>bgg;N#GofuQAgZx(skQ3c<|z473zefpdT?gImkjzO;Gd}14cw1MX?@x<~v z4y5;ZR8{Kvi8>>0iHt<9eha7PSxgMARgrJh221YFQjTbu!en2L8#BH8CIPf#FZ@4` zmQkE3be-N8_IRxy@N!ovBNL9cP)MY9*4`xwvav{Gv_Sl*_r=6hhtJ*q_;6)AbVcxk z!)}B(+`#tb@%jwl1Y`4_M992C_?-2g>bUz+RneUJLD{!6F z3k2U5-%Dl6ea)+NiMvry`T&=0T)F&4?5OQYF-f@`j6U!AagJ&F>;zT(E*uQLS!~#@oJ`P znaRF$dO5L>R%K#idp@i<)475Fn1XzScyA(t1C{%HpfbRr+(fj-IQePuKlR0r{@JC{ z34e&cW6)d|Wc$1z`TdB%G$2DupNNi~{~M9=M2s-Q9t;__{SNKF=J2MWa-orNB1MEn zf?E)f+cZdv&?F`~rJP(bE`-ED#?L_iD#6UpZ)ZYs;qx_&yX zuH$}b({QSs5IH1R6~vp3dkKCMS<1_@Z8!b2&n=+v&jJF=d&fW z&WeL6U9cMXWQ%oRpWmA8H=mrl_uss*!l1Xmjw10G z$)pVJVl;~wVzFFR)Q0`u%p}~hkMWjLSW~Pm7~WX5IaGs^!3p(vdqn%;?>{7@wIYOf zpaa=lqQ(9{G9MhOjt$R&&d&opN9oXTV{wID*dS1b>^kWFffv3Oz9hvr2l=kaQ0|lF ziRe}348;7b=d77fZoUvCZIKkgRL)4wM|`FF+E8)P%$H3YE0+vnn%-LfDbNK|eC|*SY;>)C%r_x$Ayz4(ln#v7aM(8A@Eydw<^cp_TJ(!rw`FKzTOw zJWp>c$jBg&|E1bjxKvYa+nRu!YK}>CQ7)JbWEZk3v`XQ*Zr+B^B=JauabXwI2ZCf! zyaaM=bmG&_vJdqxj0JkaaKuN;Hi-TpR)IF8|7r4@eg=@(nQBMd?wvJU)QU3c}vk?p#%}(4}!yP8IY?u)AQ|OaUZl+_awKIncd-IbNt}-z1>y!cCncg zXYkkCl|DtVejA{MAk%XSZT>k`}`>4Zlt13y}j zq)2t#fPhQ!g=AOJHVJql@J`SgeG^4SR|9-fSOmoQfvL+@p(Cr zei~vEU$2m$K#Ca$ggo3Jn|rtj#BAthy#`SGrL&g?peU1y`SO3Av=|G7pOKG<`De-AzWb{W!T zo`GZS14CE8&{kz&nDw&pB-E=snUUVH5y?;K{kJa8!M=Rfn8_gggTc$ufC;OuYrbl4 zloE=7f4)l2YV^*^9YS9w7d112BX=U|2rxdqa;{-`Z@YgL`0Lel%1y&f+-Yx>{X#AJ zaO7b+P}aSmHj>20Erl9Gmp=icN(x^UGodUA{qQXG$hf(FJ*GVe^&-g_H#{gK2N<(~ zi&pJ$XCMdH=KJ}U`h`W{OX=HV%cP=L>5R7O>Hd<#U9vN9YWs{3-7WgihN`xhf>T;g zPZit2{V6tc#Ga{xHOiT^89i_+`B~C{!F=eY!isvqk5}eH*P7LezEAcgdcvGTb7w~M z#BJ)zRvLyT=R1V1n@9qxPVZw*n`-`NiJxBtP~jP~j+ zXN#O--bTpAc+P2{5ugn8`J=epY!>`AA-rtMH_bgBLIm z@`)!cxwF6d0wiB**?AR&Bqs@ahbVFmBfF ztZG4xfpw!(=eTLaml-0Da_ux-pi|vVH=UEtOFf$vF6)+*Px~Uu4u&6_|ojg^P6IT+ZFJzlS|gZ-a%&X3zbZ|shq(TO~K+O03kx9J~< zh{*nr2zu$gF4o7FVGGIq^!IWR>JtD$n9NV*);@+$57spLicyE-C-;NlSpb8MTrGDHTUDi^A}j=}ef*Zi89x@#ULs@U)_+rshV>-ZZ>v6Bk)I`MOVgyl~pK_x0}P z%*JKpe_VkCzsY#KK=nj=dA0-71Cx6ocp>{pQ`q*3Qx?Z_@zX=xU=_L#OS#i1g-(*Wf~~LE0$hw3z(gV13#t~gXEirpCJF?{;ixx)kI0B_5XbYx@XW7MlPU5JI-h6FhcsSRGu#JkJf7hj7;2 z2+MyyB8JYpq0W@I9LzmL^dzb8yXI1F&VlHJ9y=RiK%nu>Z*eV6YVbeg<5Rb7lOt$Eh5PFk4~JvRs(P_ zEsqf{WH6h&9ur3Cx5R(55~5|H&O%Q7mxRp%B8A1_8~;KTi&6u;5mN91#eyMGP*?sB zfe3>}t6}FOkp`bOBx?vqT9GFb8-%8=#!mv5P>f>-TCMf6BJ<>FU-%!ynyv6XQm(Y) z96ockjj)BGZX}Msd^bH`|5idf`?~E@GJE3tQIsR1+h`l)h(JO^O9iT@wr1Z{*=VZe z*|~8|U?565o08@D5DHURPfJ^+f9I$YA0^s-<9EMRk(-hCfw}F@QdN1Ox3mS;NXcrn zmU~@UT}_$ieLX{Y6ZKlX#&Cfz9m_0GH8!>HNj%6r^&|tkIj^Mrtmi7-zfG8{(p!*x z6urIBYd&8;@lW)|MxuGMw2RjE6#r2eTX|dmG*H}~Ww27ahyJzeLGzFE(J`KelQsFn zG9$Uq#N?gV#!>ZGZvZrXlH{(%_C@URR@NNz3vKl!Q=qxI>(^F?4!$AU10B>6 z8%2SJA}g3?sT9(Ph6KeDbGC9RS7kMJB#F8`o_*zhaiRpv_1flZuZSr{s)8J`25W+; z5~iV8Ljw6Zj8)?J*pgVI z$k+p|23fk_W{z#c0$K03quT{C)iGB-+cYOU{2aD)+i&@P$J%PrFf~UB1W?->6{KO3C2a(*M65crIHel^>! zuuJW8-xBzIKe>4y?S(t>YDK&B}aS{Po**>-%=%?kUjo zVVK}*aw)4=F`DSC*;8&EH9Pg{8WYmQ@$#l3t+S@oUt#ulTSHd{+d>Uy;*}#^REBIt zEgsk2R{Qnm2dW*iY%HU25$u<%XOhQGGf?X`Ms2ti#MQFA- z7OxT|p|Y}I>}bMPK7!i&R{Ddrl!j5`)tbq9i6uP929QM}w%Q5+WTcZUI0K~-iGZfB z*TA%;QZeJdi%Ia`Ddn8f>Sbbh5>0Z4nOLOOk|ZRh6~uGFQvl0gbI0zgBrqOuxs~mL zqS+1XfTq2-rLOz4zoTK0aR2(-XeH!@GLR42kNoiTxZO-&U-WyN1nBg5&O|br2%O5J zxV??elG?Z?JzZkDrm~r}HnOw{5^s;WWZ7?an)BM@dX2UY;C;TAa&Fq|>k(|8O$p%? z2K}^W@BOkU1YDQBsu$vXP&)!2pN4A7-f`h${*MRp%6>ej@m@*Muu;{KjWi5z9rinTEF}k#eq}uwr+F zPjBC=980bUvJPq&ZBez(aR1+PJH{g*g2j#Jb!JwAmlM`-+WkkaDcpD#F3Li_R8S<> zgCaM=d;-Hd+_Q;x-&OjetNhVr70I(*SBM>1DxC_(%z$dBz8r4>nw?#eu2NR1CP!o8 z$zrb|wHb3}g9cqHYd1K6W>qPka4vWS*6Jc!47bi8vn$I1LY|FQN`_4~-_GD~zfYSE9Zxn$mK)>~pDFIgSS`pcLot!am$S{CbsRW*-`IWss050ehCzYuY0Bi69_^BCbLcG?XGfG@*n{h3P zS|^%-Jjub9%}w5VElna;3r>l19vT|H#~H4)gcMOI8OguCNIM6>d#EvZj&}6iRp>gg zB{Z+VW*G^UO@P(?(qNVaV7EqC*IzMHBx<_?VD!n!GjJ-KKa4Az0z>{nhOnz>E2952 z&=(7~85Bfi3wx)4ci-;_8Cd-UH{fkrGMNDihbkbR)1h9lD=p<8A zj&vh?b71lH-%U6Gp&nt4%R@EBrV0)8NDtx@Hm9!!2h24hY0mRJDwYRo80Xi8O8)w0Zb(six)O~gsDD)bKo)Sw`k;5iZd zkLxbX0N5anXB`!rF(>#N1ti;46lhubGRZWNlhwfxO^d0+Lo{Hp1Y0>!jS2kf{_q7M zU;p{R`HZKgVKu7!UVTFU{?qHJK%$}1&J;>>mE?Si2Xjum>d&wEm)AEWF%~Z z@*t4rBs1V2%uc)cem~pU_;=TkL#0eK&52DMHS^cM%ZC|WS^E=*w@F#24F-=HjEjP@ zeAxNzC_F=MdMZ$7CiiRDz8{fiNMT&I$2==gr?B$&(Lhc?|0fC*SE!AWeT<)x zXB=IFfuZ-&G~8Uo?2BhHci&hHd z4JE)hfyH@>IZG_1Uj1F+sXbTL%^!mcIoUpYiGo+iGP;UJ<4zUT`07lS0g{FU>PQq6 zv8k5w0_?~=6=HUIkqlLhVP^buP){@UqI3<_a#LdE1^8fU17n;;;Zo}f3TDUK{74fXes-uNu;}Z?sOF!_oD+B0 zs50CiC~y9ae_i-J@C)=K;vZBn`kHDzM#~$YEN?qag&jX#Z_I6WnAORAwVU<7E;#*; z(WL${7w=aj95GqGve}fyzTC#w@?28OK#gd8wKjTQ*DXvqKSCBmh$C$}D1mUX)!&GW zYArtAh@4sCoNN`lKq1HRO+ zn1oWj2#SOBA&5dic4Gof;i0X1OuI&djxtBQ5L`4<3p0z#S}27mys=Dcxwc7yEVD)yu=WN7|h(yfm-7~hC4L#0EuOt2}L;Fiu=Zja6RC@6aDaS+U|ip zAQqgkzZS2~SxN;SqKieT0U_t9Fqm$FMAA)kBau}iKf~fAZ9p9NHK^62FY0S{V|y-d0QLGcBjhu#_ss7$$n8rcRs~}} z?g2D^2*Ni335xsuurW^f>osFozZVOQrXlphWqV0RCT&OW>-b_gFD_)1?Eb#|OWW_D z{z#$!-*`W$aWbJZeENlb_+9@IBLbtJ!{qzr+x+9kgnDlXDiPxs(6<-HyVu7(JqY9C zS6nLxk1pLsPK}Y-O_S?^Vdqxw=mxUiLuSb1`?*|M%M7C5wPg+8tW`_daHg2q__k-& zub$Fv09@~J3(X_E72>2D&H|Ie>W6X@{`qofHSPYd*M&YYTi<&E+T)78kN75$Yd*U% zCock<%#BuoWwnNGhXVu5$+-?SvjVKo!i|gW)q*R?2I?%tLwNnmxzA0fyN1`U<^?Zg zdruH9qg%O;Mv5&c`%*00vrtlYb^JV)_UzM6!V%YK(Vs^ld2nfo}NnUE5F9yL+{`jVkO=s71PxoE0f5M_# zq>x5wd6vimuL1hqAKS0H`@0`du5fnl;@A0c`y(a&{b&`&RPDv`V`fxOhjwEK$Ix+4 zM?~qLMP>sB%ddB>dj%J>W?$PEGNZ9xC%$L;oCA{Ha0uDox1a*A-mgz(pGj${wL4!c z{VSMtK36`zCTE*$H>b$u#y;=gbUu$y^XdwBo$eQW8Y#OE^D!Ji&2ZEA7o)ve2g4U7 zxWKB#2_E+kONBi{O81F~`hy1gz~_mYuMpCFmfnsI?|(A0ztwF$c5?Q+8~+VrZYQ|C z3H0^?SG!r)@3}#MJf*4$P1@yoF_r8+nCO}o`1>`&vu(E0b3ynApcX^?L)MHp^j{p| z9>joR>*Y5{Hx&7xdY|^&9xXut4qgym$F^VpN*&$S8>PvnH2lF^fxY%%>>nv78)&n%hf-Rw>>&wekST-B; z9jAwA6#>DLG0dkkrHPe;#8TYMXq_JOTX0tDTi9{ES|QHQ8uFNxj+ zxIl0^7io48$rnsJt)g9sK9W04P+un>bp7Xb(Z_XBb>|Y7A}k-;v|X>8QqGR=v)tG7 zJB2LFF9hm)8463}I>2snFi=9{3O~q(oKPN9Z+SYF?D8`L1!UBcV%W1ojj2qOD?(FI zYAd<|6c!B_*j50vw2?x~s5~dKM#W}jVHH9eT*jMXt5-fUSvs5mHaTdEAJ$qVstrNq zSK-j8m?Bw_LXF1fo$?LTx+7JCmX1L&_3D zVUH@@c5C@ z1^gh|m;4F0aSjG;L>2#fT=SJtC!#SFDFtdZBNNK{@Uie4JC5ENvv~^Ei{MJ~saH4X z);!MnS=8efGWpf?%v8cOC@hA%dLLq3#-0$~&7w9Ks69>NKe*joZ(nv)*yK9OU=v^N z&ilKQxgwJRcU|{CaKCeIm!7tH_H_Mrx;!F9!WtFC9bQArQD~xC1MsFoQi;ZeQ^CSW zvI;FHA`dc~i+TFfQ&Xr#WJnZL)?GxPARu#Ftj1B8@xjJ{;SA9~@%RD8#R?)#Cc15C znChACjq}wgV&c#+=|O){A!tdKp;5%jS(Vw~HEB}fMOY_DTBSo>c-~ZtNVZiNrNBT>psV5@Xe}jPm}4cQDFw*Ts0}r~;j{{trt9QO(+ZS& z(rFY~BPz1v538XAut4~$q}W+(l8Cm{BB-od!_m9uCn%PTfqgNbz$R=xQVyN0vo^Ti zN_#B1JQ5(H9A0~tT71U*IEEc#ri9_=n?n;D8$`lSV&%Cja6%kS(o## zNjTxoeSoOa<7s=*sT{~)J`>Dd~^8KiQ1()ECux)<8^)BU_t{8~{4F{WM{-$uz-cS2w4^A83&1_Gr~+dy!YG>c zP8uyj*1K1R2fL8Wh~3Z0oXrK=xWzcMpOXuB50cv?10_*6b>cgj05o z7uOL%ho?hsBD*w~H@1*YO|0;j25y`TkfDBJ$+Xsk_w&iQB85hSIfy}wx0bKR9!VoO z(8%#&i&l#!A*%#eT8Yk!VQ1g~Bv^AuL>A>l;pghW2Rv9Y1m)~kX%<-)9d+a)To@U(cUG@9Ky;*2eXZULWIM$3M z>vO&?D&g>r?OmCVd49|5{i)3T81wuZQS!`K*ZnN>eMo+@bBESoA=}I#QX#g=@D(Q zl<8?yPwIkagW}E8JOj>0bIW!JNirtFWp25MR1|p0HQ#(zSR@XWs?e3ZRjJ4i#K5JE ze>mOeQOdy!fnZ0BeIBW(CLGC|F9+jycbPQ=MqaR%7sdw;(|2;)gJIpY0zs*~--gai zCFcg)DZdMl4;OJKpuYc_pb1EkaAVc-{wg)-AUTRm(kAcfH>UeXO0GC!4-Sf=D`=YH z5)C)nKeh-sAEQZ#P~C*gk6b8ZCC`!)XiU7s*3yLsWEEurT|luqXn;Y_qR1JG4af;} z+U%-yG^xMl|Leuz4ZNI5s{^9yc<7xj=(vW1v&hJDKhI#;O{C#vlB54OC%hy;Xme+7 zP;V|n4aiL$LMn(>zFPA$<8MtzL-s(zY1XG`-r+@)NQg&Rr;Jf~;Bw*#Q{$7^ZG?*K z-fJFOtF5LO=BrZzvyheM(N+B&D90A9VaG$uky0XC%hlIXBU6LfDu%p_JsTl(63@o} zCemubuqRXrzlcz>u;`RWFzE!H7aFaTJEZKIB$-P8cdQ+pMSGEHy*^gh7*k)gKDHy}!Ntj^~2yB#-liY|~N!LU#XFO!L5NpDkq%!PRZiI;5Fchylk1 z=kiqNk6bh!D;ubsg7=LsuA~8GD}X?qxj*F?R{4Y=&1ya4%*qvCGHKdK7GHsqKw zIQH@QS2>}wQ@3g!j|;*RSUc-#x^8-1o|%~ytN}EIG#T8%4A$!6tIl))n$SpV<@1HUI=VHW3aqc%~KEp9s7yI@p?xG$hK*st~cPNaQ*e`%-K= z5hSEo89_}5x>1>-k?5jiib7blvs0B+W2xvOEOM1&|yy1n@=lnYLN0kkdT`dWn_O(VRNnu%On#Bp@=U1I9*I!@{J18dSdylN6*VKW zn6z3rquRJVBGl+f}4wn%SJP^1RL&UNobXs6>y#!PDNf> zDB>$pd*7&Z?w<_Y8$X!9wpzP>fTD)1W=31X{d5xPA8Xcy#m>Am&$KWv)fT&xQKC6g z4~aDHwGNOU^VXCr#c>cDfC&VlfuIvu6QxOsxLU6bw^2GXN$Su1--2*CmuW7tWAdGn zN;;?rWS9wJbhuKr#hF9PE?JSPdTMfw4!|$_wGl#z5%*Rtjcp~^zQL3Wx~en^ZYd?+ z6ua=!vJxPvl#(Vlj7O{J5X_W)jFV{*2@@C#8BEV<9#c-jqB2O5Xsv}3OBjwJ7we(k z7iJkMqF<*2hu{RX{@ah~W7Ku`IeWN>>g$Hjs8`;h+iq`mzJ`*_YyiiJ!+9_=xp^YK z8C#M0?(TKG9oTOP+AcvfhU%@KBood4-8=EOWcx<{)dAS`6@4hD&C? zI_~G<6y*DxLg+l8BY09BC*=@@rT$&aB@@L(J@BiX3ikN;o(09dYCrHV)|-P%i<2H@ zGCdUCxQE5wBX!&k2sT03&g89>_GO_FC5@Y-^d`Ig14VPr`DpzfZ}|4QO&Ye!duGgz zH023-hq!rQ43_I+|Jn&UAtv{`cKsgO$#)KtSTc0fFZ)%dNHa8FbVH!e541x$y4;@` zg}j+T&8cQB_WOWFbJ}sA>G~qb#vOtN?pBcf>-*OP6!H;=sR+HhonNnrrwcajl$^x3 zPxsT-e_yV(`dGbVKOTkj#EHkQ6J5C)|9As!BwVrk{ZJo zjDqG`INVhUFSQNYUiEC^o56j%>AKNXI6jC>2%;}&oD*gs!yp^h6f>~`IHy2dH*;CAp6aLMUS+WH4s zm{U5&39#P(t{(4>`dufNiT!+4vEgMNw$*wzj-M4G0W4pS|6-?j2sNNM{wPqfEmn6M zIyseY7sGju*|_ABzWk_a3g82XBwgS9{M9VQ6D=2hfJPdZ*oq6=9DbPzrx77NlL$xHBD}HaY@cG02mKlfXRjPJr_u%@|6_+x@1+;(WrtYlZzQuRb6%3XQOPp( zHeA*NerR1_xae(n=awbRoWLyb%t*+~x*F;{fv39DU};Xa$V3tQ3q!I@e%5-+!8}P9 zgdMEUM%QN$>>~SZTNdFks?OTIQ3^s_4E(Ef<9t#Gb_(pmH05Js9oIEs8t&>R;>sRf z<_Wk|?avMKI96~SWB7-eNsg!ZQX)CS0*(!fshLR_a83|9sP)T|)YJkV>&;$rjqb}6 zlpjpCtJ;AAZ4Bvm+gmIh>G@dj#sBmkLIK4e>;MV)JKk3GqyF*z}Hr zshJimzO-_AV*P(iy()^6LjZQHiHZ=Z9%dvlZeQCV53 zRY|?;t(iHVF{b7qW`ZM-nz0VZETu74eQ!=r#OLoTgPqG$a-n2;mvi0S=%Y46eoP?p$K z1J99vv+Sn0AI4^CPS;G(p1X=uOr3#D;2F}mdh1_@b4<@Dd~PI7y8hG(Ht#X}JDjpP z0vwuy$aBaE!4E6YcTD#>uGuZQ1bo=Q zYMaT%!06SY#exM51cKb*=hf_g@qs7X8e8Z;T^qK}lEjO=gDnJE5Du8?w_TGL%m5N8 zBj-FPu)$@|IV$l-NgU8o&})XgI_DcU7SE?L;6SojMT>}<4_(h*vCLn?T}5-2%aXFI z&39+Ctb1!75AmvI)f0Sm^v(SJsn=(w(yQ0p!o*xI56*`!qd1^@%di+*Q=yFkB=!;@OJL| za|wAn!~~}sgWJG4k~N_Bc!mZ~B?2I+lS|mn^N4I;{ubH--x*X;4>lP#P3_7Af^Jk8 ztz@gXP%uJZV(`+zmp6Aq+sK39+>IN7|JH=8x{Vbo?w(pM6sE= zVwSyXRxF|J_!T;o%qU=Mr}|}a$ktb;X7K>X!JMBj)^W!TqqaO^L$E!_KS#G&TOFF% zoZmKrpG5B139dZS#P;Hg{#fay22@Zx0fhItwBtu%{TZ38wH<4S6Mc=DK#d8XI7g%(5$=<>xISw+;nmv|4UT??ps1nj;C}8^1qUcJ*c~0O z6R=^h_m~wJH5)wUb{YU?u#FJ9H1Is_?@qD1oCFRj|3NXhy?kQ0{>LtN=SPNZ^y+~3 zG;~BpM+;m=$%D~pxgSs_D>Hj_xeVYcQ-o|o@{deXINEw;LXW3pd_eoV77Y@M>jsK6LPd|6+N;Dv?|m6#kG3Op!rqdSD~CkZ?AOv=Fuq`sN*g!#XvnZm;>2=tbGYC&c}77 z!n-mwO9V7%!-n~;j4W><<4Uoi;fP&oXA zM@rPvX$(;K4*~IR0c_%mAVt&yZoLtjUX%_Ajqa79;7P>-D+`s1c%VDhN$z;d2#8Uv zNlLp@T})dI@W^5KYcj6P_%A_Rcxrh3s_>NYg0@whE)EgMWz#n0C#lx7%uz3^{2Ovu zmtic>=|d3as4C2wf)lA4HWia}K%s3>DHZj>L*&Y=p|{VHv9=3Dl4+$_N<=yTu^s4yNeD_Whv==W)>T$3(@grTVlMHt>Ulasv|-gNS01 zHj!Fo-N=AAb_axY;ZiAN)TAM9{PMO&|OZQu2JrJ{fcHqrE)^Jpd-;33er-&dvs1Ru8;_(2Y{E?u&k&k@M#`vgYK znx_Sho?v%77XE}FN}_O3^81=PR9{YN!;W{tWN%uB24n`k7_2r5t?tw5+ju9FIww_3 zoMBZoRH9(}qpwM*b&HNYXN1SXecaKzbkI;)PeRXy9ceFAKylc9jT_ku$M}ND5kbP| z%JHv=yUTV!hkdZ@p?Bgfftd|gILjq@Fq5FYug^emLl0>Hv>_)-zfAl@pNI9w5JfS-%a5O)4q z4G165f8zF%5gh1gnx9n;Cih0;Ql>Z9R4Y^{5p{6(a(*j?%NA?~qb{1j_OX`+t9-cZ z^crq?BYEgARSGzQrw~2J1=HpzS**HChRccCaiUO)ZmltPGkQqAmi=kECnvZI6Y`mlxdl^{?JR5pWSg}h4Xi<0Ko6o66T zj-|aE-rB&MQ*xSG!7>Va$w+b)cU0-pC!{9NWc6UmF#Ewf)}!o06o!!F%%|=Cum~p= z%%L1o-bHWs9H-Or+wnx3br@D>4N#`nW}&VP6N6=s2I!N{pihO+GzW_80@jgB89j<} z&&F*}>+SMU0-b+cX6&1^22AbLQ%$^~hoiGwpP_r5U9Vlqjw|6CU>Y->_Aepe=Z?%m zEk=$;QMj>ad%I=Vx~9AkMRP`K_7TIK7`8p)oeyF3iMfJXwF+rfGm`}pcUpt6@Ew^# zzC4=XqYKwI5Sos*6S1dem=l1q;K!G<9qk10=uGdercNbiS(0;xWVKq+Z*7hF>DH3w z&iA4MRHnEMDQy1h5c}_-_iUwT%n((fE}rEfZR2T2&z@@#oVHulyB8g;cpGhWRgHK9 z>9ms^|AeB)-)+ce$^{k;n}&5pl3}oYpg&qIiCa#vVfk*K-@sS~Zplt9tupVX5STVC zpFFBK_51>|K7D^E0r~}4a)_)q>ZMmTTPzwT?#T@Byapt!20#%zweR43T0!4znMP*^ zakajIWL|>#1^J(jOAcPt99GZI6A{TDHbuc zuP$_4_HPFkk8GG_+wJT@ah!XXgItV^c$=*@RQ-px(wPiUAVLmK&>bxRS4s@E4IZuV z0I+uNqi27njfNQz@YC4MmauDYlJR@LtA439VsE{%AguzsuNHD7jEvjv`OeDHE|BGi4043X> zE33h%ENdJ0y34I5&=_Zs2~s)WE-{g)7@s7=^8W{Q1fMMf@FWB}O^WQXCy3|_uTp3QnTUiLQ$iQ%{4S`<3uSdTCvB#2RUXE8^M-kIqR0 ztvUUmToL5&+3xrU@rFAXJx5K zYli)0fa2egmgy6KWlAGLp4G<*PZeN#o$`mFx)a5V5X1}VE0=2FISudxXxq&(G{CPV zNDP089c!{k`u(U#pJQ2;2<31exsvJutsiNV`5(v&3jPCF5e!=lAYv40U;`qA4>Ul) z-ewWXy8z&GFO^oBdmL~N=6){%PiFnHp`J*gPCDzRhCFcZVY z8ibDtF?1zWZLeJzwfv@cIvDC`kv>nn4x$`^geANZt406`!#EnPXF8yl0LPv>SqlIM zLB}W)HY>nsMtUPPC`{N^dOLSCDgr$$#So+ftll@oa&e}+o=3+K`>AQm8XEfvoU`0N z^gvo2gLZd60C#(U!sxLI!}~aZzt{`q2LOXwZBQQbNK@yiH_#h%887gxLO#$M$=yGM z>)(a6Mq?q2Y$uyyHTmyf54!xPljX~)Ds6K;e{+4C*=E!V=kR{K?&8mHW|o^dfkTCW zzdRwMp%!G>@6DR-;JJ}5_F`Rb)mDJINu7S;Qsh;4zR#Ple&|ov@x?UWX+Pe|>pir2 zvL(Zw_0Tm0Ule1%?Tts_%Bzt`eL9*yr9>zu9;?t$ul@ggdU=9Zb$100H@Mb&wu9qt zaR$MAwuQR;_%|un?f3+_dJeov>-`-q+_TDh~!tLy2y zWHM@g^~YxLviaIr&2@DV_vS3ejn=$qi6&dU-ilB*6*Y)a)vcw@YOVZSJ%Vb&G+0cN zMk-uvT?)7TExNHe_ZFuqwVB&)qj4iU4Xh8##j>K^vb;V>d#Da^m`$+c-`VK0h*lG= z7AtMx_=MD0^`4aSq_ta4q0T91Y-h<2EdGkBlPD>cBI$<6Dx;IJB$i-%p$3?Wh+0Zf z_&P}B1$(}Ds_XYX@KsmE4*eDfDjUSdRv-D!Q#H;FKJVAX)WKZhajM+TeT+|s)BVOb z`N(VA{Vd02F7?m*@RioL^R4qWklPL3%hE;$4-vs+x_c3drggY z=d}+1)9;nO{@cE!)AKgFTkre+Y5P?Zf2T6a?(L;~jXDBNUJpO%+2QH6yP3R=pG>sy zU*mtIzudm`^sN3IuHNr-BQ?iQl=^hJ+aB$%9c_sJ$7V1< z-Z_Bpx$}9cyNj>f4q^mYIvJ$ zuHEm>(!YM5I{2tpL;k`R$_ z3<&Iu5SoJXQN}tN%4RF-cAo#~edeGzf3{drhyEI)8dR1;Q2$y8C@e>dQ_4B&C19N! z|Es6WLVJ0;#X4sHv(HMT&^27eEoU(-CQvKovI|ca-ZIY_aOdRCQ~;Rwtb>)q%3GTMM45eP)u6YuIDyeIJB>3KJt&c77Z6b-u?N2vj7$ z!5ZZBw7Q-hzTflrSB}3%7wx%fuScby@z~!Ee#d9*fb_}qBmbx2*=43Q`;1#@wTBu# zt&Wf9S%dHI_X=OG9saN54mF|Id4~J-x{^84MV(8R{mEvSh@;2#Vft%U+FM6PPmRF^ z95^^1D#q{LCm`8w@&#}ja}7oNh0D4et?q_Tz8=u`GPQoR`JTIC4gT&ESr=67(Du0I z45WQ}JY>1PyZsF0s!r$Ve%~h5CTBkMj}PIi*?Kj49XbA(1Xje>boy*?JN0w_&6b@8 z7cR4v>#TQOdEow8%})2VKB*2C&oOWLX+4Us@?CFn*O$A+`iJgGTl!>JxP4U&3-xMY zg{!EuK##M^3&@ylQP%7UBz1V)Ap;4X*>}gvSP_=B&0F_BIsG`Y&FzXL*E?@huL87i zY)LG#VAL%X-d)Y-IY#eg+7|;?DjZ%V^g8Ib;qB59-e-8>eO0sV3eE{F3O;bxmr|e=xRb z6jxqb!NqDHh-5h^UHU&l;RJ1C%mkW_k|2|%|6{Q+l?bGWOl9ggI1!+b$!h$F6sL9! zZ?p6dn4uoA81(%i{Ikc0TN-M#Es@$?5tD?Ty32^1`Nx#3lTqAnImB zWLaU#iG|r&f>4zN$>t#IamJEWWYwG#mG$AR$%FxuUu|WqO8tbHs&F+DwUvg5h%$-> zCNxJGY%t3T4y9tKsY5JOJv4~JQ&fW^h}F5vD-FkzNR6!`A{hwLm{=!E{;=pQgq6*u zH4V52JxuRUHBg_x(d&MAygk$2R6%c(r+(z(Xtz5gWm0Xs+QBq_?IC{|?RePfJjZ0K zbN;a7G3;$Nczr;mk%-pt8)j$kR5HWBF!-5_AHwE;;(r_ect&S1uw%r@7= z@nS`|DI9d2E?_g*q|F!BH0=VuRh$$muX6k*niIEa7DoNv&!<&*$-q@(2&w8or@)JoWXq z&ZhoIV?;J-Kn2Q57f^Jwwdsa!vM!uAEm!-u-afWFN2kbQ$zQVAgoT`ndzyJVM*l!9 z;DiBN@=;WU#|r0Hr#+soHzmT`a`b|&uUR>NhTELYwKZ9JG&sdnmsJ@Q>sgVlfIH$mc@3vHF4?rmk+r$!UnR?Ya?5}OL?zz+vz5zKb5=kVfRxsB!!a4vcVjvACU!7$ z@dl9%ezpj6#d{doXKlG?(a;jO-qn=YbREdt2wo-hzKTi}FVaR@XJP2b#SuN!7KTJNeG0N){(=z{z#|N4cTD_xHr| zR(#6R8E4)9W;Jx>`*Z#9@O_1BrQzxlz|r+zL!iqEb{8#A!MG1#%KcBX=$L(P>9R>E z5zQD`eQg23Pzr216&z_fBCMP(u$mBRC?cHLLEYE+a=J|io@aQYiets&W=SdjhUGf& z9>YTZ6?z9sD-A8mxoijH%`7eRlxirKU)Wmopu(44OQLS9i(92Df|3W~H~{g-7X0XS z9%^1rtsuWmF%VvF> zapeS4X;M#f^A?L;&#*xS<>U;Bl0|{lA*^~7Q9&XX)PgYO2IK$R-E0%l{^*nx6m%2> zIzibM%XmMiu%J?L{w@u)|J&O(l=Tlp3VYUEru}=z@1nD2httz?FwmFS4Cg?eW%Q@~ z`tVknx+#%qf~pmyN!ufxpaVm#!$$345fRtoX$=j_F+GxlXPZbK)HL=S&BKaW_~gRsB{f*)(sTrfnvq8 ziF2g_DssJMDAW}?b|2(B)#JM_^0PUhvPE49e_|V2f*#;@;?4Y<9kDm8hnPX%eT$< zcywM3-L0|d=b7>5v?ZV@!qBs)_I^qH&iHO5as`e~_v7!|e({i14gEIH#KiRbf6(Ih zIPhoRF87ob-dlpxaXq^#FR%5EzstXdiQCk&-qrxxTl79|uhZX>=od8I6;#Aa3sz*I z017F(e(Od zJb#Mp2{>Zy+7~RJN;0dq*U=5@Bz$%e&uWp?e|RWm?ot3*q`7Gqo4B9a`Y>+wM5v$v zN`>+q12_-_w5%fFvdbW|F(WIJRm#Xl%OQS7Sp~D0qQ^ROVT=@K7k?%{G?K~5t;#}C z`J{6y9bW&^xq+bmoE*OIvg!uH-_Cs9_Bg*l!dI{Re#guA2}+^}cEEAx6H8cvmomSp zRc{vte=LL67&m2sc8~kyasPcp(=s&>j>`&dGC7(SgitbX&OniH_Lay_LqRM_xI5{? z5TsCIXnOV$L?JQlHN{cK~$hG(b%@*vFE=AvmZN)5M`8y~mXE5>0@YO;P${eJpj$*s<7F;EP7Ccn1 zqskPX5nv|R0#MZ+`D`7;<%>+B;BuQ+A=0<2Y6;|yNdcLyDPv1D5Wv)>V(q*+T=rnV z<)UWRLNH=J#JeZ9R0~8HWdL6vo`@fwSR$~75(EhWW;#U=$8;)KgVRb@NirFQXw1;kf<$<^4$bFEt1up#SXpIquZ)Wnt3pg2j7ulEy$!uoa*_^r zmOj)fQaPC9@7vXm67sjckz~W<*sX12tfuEVFAE0fTC|5}#?^F`nei7^R3BTTmf0E= z+ftYiDGXa4#!j%nDY zH}@m~XQoFQVT7xffFXKEg0yO^{#Bh`Q+5tX9c1$(6<4~61~_o99t8vO*L3@J<7~c- zl!sMv27{X2bo`gqPzFI;_E_I`CgGKZV2X5-D~mF=!JCI;Z?tLBiEus@cD$Wl#?A9N z-EThRh_$9Ywi7?S#CQ@(_fTm-9r`EnA51qU(uCq?pZpEWMch4j6s73-K~#vKIWJeu zus`6_p#IDtnjgJvep{3W&pzMEqUDh4WsqDU;t}fEl`^?!7CoXyPJxlJ%+R-CfeHml zZ-Eov(%r=~VwbQmKd{Ce+frExM#_=htt2fk&=F}5$HTj9WRm$itLWvumcE*ihtJ=Z zg9ay{K{W>xQaC1=e(8pKWKsX()Tqk~`CE~kwbfo@-*E!v9+m`}h3I!v{>i{~*EB{= zZPV17UBH@E@nDjRRlQfaJ!<R#}uCP$02^v*{+xiL6K{+Kr;CiRE^w{s%uGwKSc{ z;i}Hi{g6_$RUNCvYRmd(%ze((_lNMs*xaf3{c~MZ%*I?Tt=)KcQezjW!_bK~y^4Y< z$U`=6C?k)7&`2amMmyn4P{9PdB0YhcRz^JAJijPebS$olNRrxLH^G>6Q9j~_??8!R zJgCBC^w2@UJP@Hkoue;I19NB7T!6Jj;Cbs8j=0zOoXMcKp%Fw9#Ty*U6x@7;e4_U1 z>K-J)!Vua|D*Jt$|14NY4O^J?#n3PWp763ShWml@_lk-O@yeJw`gr^&c4=ZP1iW0# znYB#AOJtNa_4kd-Tq(PP$7S)3mHrEk0^V2cS)nbEp>AudIU$v#93e-KlUZ>dsbwBXz9$X7K~-@AjJSG{ za%ne~h^Tbr)>$T@vC?pPM_7=6-Xuad4)bpa(h7M=qLe21BsJ=ej^I_2M)b~S8OCu` z?=LD5L*moL;GcHwfk7BA&=(0d*Ii}cr3(z9A1gh^L?2sqxdx+(WHB?&gEKDPcwFQN zrz>{AN&Ht_ZVK*|#sR#QOA1nUu#dCQxY_j)SVY}S7rQ&{&q_4Vh(Vso3OK>qH@LQ~ zouG}4bWAYYtM@HoST6V?zR-usX0&@)@U@0F%rJbY!&FwN=#6i!kP=dyxBcQQ(SfyB z|4>w2vD&Qb5EOI`sZuUW%1@c}cZ`MgEaBJb%=`?%Z;CV2EM#2vMBy1ct+|Ltzs_C; zBBwe@vf5@5syeY|p|PD?yup=B?Wkb!kN^FW#+5p=S2EMO_VVsgrfdl7P-8w8(D=sb zUn=H=q1d3Bn$rS<6z|AhYCj-&5K4;UBvBCh2(_?9DAe@ zyycxo>gMsqnVe2sj`OdR^+ta#qywApx^xMXl=IpgOre&O3d*@x0H-{h7SW?yv)+3V zaVH7xoe{SpO>{KUCS%#C#ipfGPSPFL)ns$w6y{157DXfzstwb*h7Op`_XzQKB??5H zR&SdNG)dth2ya5Y!pzM}Oc+`w8F0B@2=6{E@+EgOfu%|~^(zxN<0dapBp0odDMkCA zh)=E#5=iM!tW+o7$Qr*HWw?(xyd8Tg_{AOH4zTj9Md}L!!1vesKP?5%OyQY z4t4|T7IF}{B8qrPDO5q=<{!Fc(ry%(=Y22yVH5N)k$EKrR{=&mVh-{j5SXiIIiWu{bwo8kNI243iD&AFw$ZjFXoJjt<$8I(ZCzh#!o@ z_nc5t8dMc^NpVp_NMt7*DrKlkH`}l^l>`mTpo+z6$Ht$^bD_t)SyCxYI$8!-W2z*@ zaLtEcuV+Z*97cq|K{K@2YjX+~a@MU}-Q{EV*@Ok1};y1`^5 zwc?pdg#FP}4jI}g;uEEfMA73oQO^(aL3HRxga!)3C7^E~BmN{bKas4sjK2Vu%P4HT3aR36d4;Mk0Z)HqM`P&q(^S6M@0p(W6s zFGL=Am3ONzcir7c?`E2_OP40Kk?DeyKeCA=QK`YP^!pY%3({^}6)H>z^_4DTQ4>%^ zVH%KJg+$aREC?pr=^NzA-+w^W(8QUM>s8~g@4=#Nv#Dkx!PycGWa`dCBGEThaLL#K z@Z+6lmxij!^*aX30Wp||)kMh&C71vi88t7G1wgev-U1^j z18qrGi#mjEzC_efDlze7Ub$Zc3+vv=5BnUUa-#j1pa27@Kc$RNIzd!KR&ALzI4wj% zJj0DNA_~C0JXA@|BoZTnj97MIZxk<_2>K6I63aq7Y4~VKVbp))b-;&f07fW)4pJjc zoCq9Lm%2iXKmc<53rjwcIFsdOpW;}1jRe~j4-pa2pqzqGMq%?@}zP3 zhsaDmm0*m@$#GXu#ujFf$oM+{0JJHvwJ5q!(>F3AX;8W)a!jj{5G#FLw_Pede4qKM zJY4s{{yfJ-$)-PZ(6i}BkXXuxcf)+1DMbF7A~Q^89*kt$^aUX;MwwMn^8!MIpr}Bh z#%CsC!H|`MsSu;`6likYcug~K3_NToobu*+{i*bUle?nzQq%mQlR%-9EapI9G-Tb4 z4SW_X3TF0F{S;upDBS8u$$h0FHwJmWKc;OWM~NM&o?u zDsoTXs81gy!DLq`XQ^@T)XMaK7vbYGel9HK312kH2GN^I$F7ltM=aktctJr$5iP^Wbsx#h z&a51=2BJ8*!A0*dXOSw#Y%AT0@j1zvqAF0%?YHtuYMhc|)+=~I33!T)T((IQMtT{7 zG4Qrg@CH-wkvB5i9)}lN`{i_YcktjQr1nn@9aBQ=<>Z0`J(cfUs+ z&OV9^n@Xp1tnP+mgiN$j3i4hEHm22Yk^#t>ztzX`DLnhKIY~kWjc~|M3ae-q{-qmE4mWMNEkq(cv$E!@LsyVt2IZ zMQOf$M7({yzrEOPf)GX)VOIeYLY5a&aFe?R00-n}CXW$qv=4IuqN^G0O4#ibNGBz! zqG>Z7KnGfqM2E99yRdD8{vh7Ni@`c#R9H4j zt1MfyEGZdfq`Ej>=3a*}B8veusg_GQR+x_q!5avEG%5Tv8TM4k>9(eL$rK>R<+siHb5@qQ9z=cN$JrDuZugG}b_eR^btU z;?WamI~#D0X`WWSr5kfU-Gy|2Jg7~tu;qfHeg;qv?Liyy!2DrcyS#GpU|2c9yfQ%1 zSh|Lv7&97%?Di@k(v-RYi+iBBg848|{+OEpjjLKwjnNkjq>fExNcHq!4kna`Y1F4Z z*trbcsf?sNLw*u9KNE2%M#0n|o;=paSREB~LgX6<^U-g)Hx?epAI+$QW<9zx`bOm< zW1imlEv-CgIxuc*1c2f!96h@dDO_s?0!jUv-er*F{T5!mTBSJ=& zZ6D$k;gd>>4V9a4fO%x=2#2R+Gb@uz3lKwzNyN-`6F7{bKa9e74l(z})0j5?9`q7K zsw$%k>#gXAth8o;rC4Dfmxgo>V?(Gi5IW{NS)qhvbW>p&;dcN^ckR>m595xmG@}d)RvP?O zzwgUVAqpE2V`OVQD^|I*2hf{}vPtxOx*r`W{H{6OT91fOt*EygB}J+iNvhXhv-B$B zf>x4IgrI}~@)KqolwQ(AY^L{drt0xi+KE;w{=5|EZN=frHE%3T^y4Puq#l{?;NbW! znZ=Zjh8H64!cT=d3O%rKso*J;itch25#2OS{34U3W(9~@MJ~h6G+Okm;&HY}poUMj zcEW$qw8O@jAr|wW>4wE1I1?0=NuWasZG8nP_SeKrP$2j6>i6kv=^@i%`jmkBl*4+I zzHUT)TJad^ZNvKCm=5FCtH}utM&0hF!t!^+)cX4>=%5zRLUh~Yb2Q@Vml9>s);&z5 z8dO+tkg-JpJsQ`wAx>c>Zr*EXfvV|lw7s+P)|FaBDe=@BC|3IeWWAhDKj@Mz=3k&L5$WpWj*T0n%yTP`J$o2b86*lcniBQ6kOtY~qq8)q zYt4pf8Akugp^%@q=EKjzzrp$4Quz)62)>}zx-v^U|H~~L>2PlId%XX*tJu}^Ih46Q z7uzMAs`l;Sesr*~o!s!NYXw*FAr1UWr2o;|1>oKe>s?R(y}YjtS*5eqfwl{U5*9_YtKDJ)9@L?#{orPQQZi7xYS} zozF1<$?a$E&hwvK?QS=VU$;Bq{^;ge@_2n5uVM>s<%m^Iby~K{kM?2 zCAN7PyFB|Xo-!KAIcPVw;D7Efmg{;-b>H1FX~K8q2)ix!q&G z(BXcr2fm|UIMpZ-2zvnE3#f}B`$pAJalRGWpSE@#Zjs8;-q`GE7!wR#;wtBap260E zfYJFsJi%0mW~tu1%f!h0VVWF&$8!DY-4UMGu<0>-Lv(DNHSY=Ls!IY>Vcyu~0=7N- z(X^w3yhQI;X3nE0yp)FcWb*TY?9&6grFQ^G!vLAQ-g+t#evzBvynIsiyoKnW1TZ2C zU}C;IITuiwi4)r*-F~LAn4Ybc&oweNi)BM2%2OOCfCKTNLtlU|S!rPd_w`&R2rjEeq?K;$WcVzU5ICAUbz!!(}` zK0+fO6fl8e|1Ds100+z=`^LdyN+?E=NO&Ef$>o9Q(t5iSF5PZG%SHh zhk{`&Ayle$S__G)3QHi%kjQTjs-@xzljzkfr^N~}=Pb~cWH^Lcdch59WzwKR5mDK#O%~xW5&#CPod6O6w?1D zx?HCsN+=*!S_lu*NmUf4@zL1ua$okIsA^L8YWM0>%VUhloZCoSvpRsz@X1 zY&boNdcX%l*;T`LU-vUs~b_sWV}e9rN`a0!0se9y9dN`1fVw&O20t8esP+kE$>eoj-j z+w?kDW;|NpX@3r!88&*jTYveS$d;PNo^Ma*2Jel7jx7Ft8(hhqwY8Ud$C(A4l)8(x z?VQ#%IE7Gen#+445$Spv*PU9@4~edkTJF;J#uu6F>p<=M>33?UD?mK-cm^~ppD=MG z(YvOxC!xdYbk&+BmnrL-l^orc>bZJJqvoY6S?mJB>VpjHX7-iWnm-WwCxN45R{ICD zT}6y?9!^*5W8myK`oLG0O>#UU}X1~fTFj<4?UvGk@k^uNj?4+-);;!f} z-BR1Bm(z9Dx`e#Gt6L&mC>NP;o=eq+4P;aEn?BoDbZ-W)>xqZPhT257e_K}0%P#+) zN~@6h5IyYSBX=Um#VYIx@4s2jZpOx%*J=*lTnvJ3rY#dmuI5)nfLmD2+91 zt3L7>$JQBoNl8ilSZCc_*d}!T3TvWoP6qK+(+B=$Er0AdAt1TcT<<^r#7g~#jMwCN z_tJWoaB0G^EN4pVcGg5ze$!LE_)!QLpPfNg=Y1wU-GF79w0pdSYaf&wM6|&NGvwIIs`N_EMbd|8-AAI{+|6EkeS*U9OtKZ3<$kPluW)@= z&FJ9e@ZfuIy0>dh{ za;30wG1KZoH?Xv@5#sz>jpDW_Qp6Uh2MR~nO^*32!99nQ7)o`PZdd_YY8vtmPz>$& z(?{&P;d`m)R#c<}C*w|(NomxvwadtKc-}YFLcaUvZ*JkQw3w;EOE+X#>mf|8587k! z+t<9w#224|a5h_(S6jEK z)3U0fI!N_Pt~4Z|@;IfLB3$H!fuK6j`l`rmbQiZq3D2bkOCqIhPg0iak)VV2%LOG58?k+2;eo=rTND__F)_ z|Bsu3J7^?6gZDo&atyNj;6ddWoPh#6&DC~yVDD38?Sj^_LLqc*5K89+ffRJ}%BTU# z%|z!akC_E$l1tj64l$Tl)bbALb9@_wWQ6U}VHAi#v>T!+G@KHp_0l-uSTXymZW_XJ z-PXT`^ukH#&zLYKdX{o{5HeQtuLz$Jh^Xa!x|3OJ?sfJ;VQLRUt}6?eaxsWy+)7R` zCK)X;^%i5}v+@v@xg`~iRFOFrYML}kGNZX<4g;BM8ubR5YbTtIX4*<1{f@9&Iwj*X zFu}NHW@XWNlduYd(R^;+190I2|ZrWJ!Z}?+Dy#pXOjTu-+MmIrl#!`6{;#~RZkjL z-e}TH>b6T8LXt(R{tbf*lf%{GasBdol2Xwi$VeKCO|6l*@S($6OB#yr&l~-R-)A4} z#G&GMrG?C)`=cfM(Y|1bi9K7~$f;~txl}$r6;5BFyT_Qf%ke+1>r4SgsZybYLX>pw zEPSY+_bLo`!pa|~EuII-9^%1c1AVJ!JJ-?T+lWRQV%@J?gCerNZe*`VAERa=ru zKScGUcQDbd+WdGw zM#slJewdb!x(-wSj`|yPaRc$S=s)++H{!zJ3%gP0O0M&y8MA=uG&CO7WcCYPpBk$S z=fL*n_^RR50b1t9=ILsaHQ%hqYjl|%Ko5@fNM)(1MXho)JC4=Qe&Jxmx?BC1u zgr=DuJU+z7&(srV4sm%-t#@^(MQ)bJJ|?fQ_jHr3l%+OAXWH#`*UOpeWzEj>hep<% zhn1Oj)11DE;}>nwMn7+Md$BbkZ+g`Il-Nigz3}~opPE~~%|REtf0N;obR6)^OLu`w zf3f6|$Zq#WyN1?C{BT`)F^!vKOz5ke|LXYIQ1H@g+_HFl#Q71gaWzd`e4KI&nez)ZRK97ntFPuj^mN9B#oMfc9<_o~uO zyZ8LIy4y#Ix^#U2Z3?_TI6^_YrBMT`x|B8$osFhK*Tpm)Eh0Ek6G=Yp_r$vJVp4PO z&qqO=MkyG7KzR8V$RpV%TU3~YROL*&94lB?2@fw*G&qHQQ3G^beH$iECXVNlS5oYR zSh^^Jk-3^6B_;D2!<8dJgscBWR{gl;z~M;vTcQ$ZFA=ltYr9Es%@0-2wD#+Yd&`j| zRp0uzCD(`>`!~sV=R^^zmfEV=FwjA!&OgN;Ao~Av!MNu+BMy~MHIV+;Jb*NfW&x@E z&0gOM0CAMlR>Gd_@1Kt`Av}kMS1?TvdqaR5W_4{viKr#{2-u1fPn3w*qM1YEx*^sq zm~!4GZ{Wgi+&q6TQvTp0W1F8(4b#jJv@t|>psh_Af57D*dvDD4KsPLCnmu~AX0(!B zDXpb3klrJvw1HPxa2Dw2tM|4v^bg-alY;}FpeePj4KG@qdbc*PRugPNnob+KBpf4|BA6=OFcie9laXPSk1`jg znIfR9f7>dJuP{zI0>z!t=nG#(rmjOo>C(WWrS68?cAu;1CP&-J|1bDu6*p&aFo26p zeDF7B+!zh1TRd)e^&vkm;Oc;+1EA^!kZfJIee+(^8br2*XXP>Z%rPn~BndhMm!#;7 zC|6@WHHk5|ac8HBFERdIZMf4JGkDo9q2c)T?bWEo>^9Bq9iiL!zYkrU)R z5E&oM?wiH?s_@Jz)Ts6Qb?Gb=9~}8c?&gPp;{$*x;KtXOMyq2zEdL=qR(iy4q}6k8 zJAHnOo=U&1&L+;989;>pZ`bR<)9&uld{2q#pB&0A-%_*z`s}w_x4YTNqZw#dIuPWR zUV8g$E)p^P4wi?r!2P?5K5=kD3xjjbv%}!sQU{4NxSk*9pO3!2y8U?K1^6BcnQU6w ztq2diCRHByQVtixWJj-NG<(Gqd zsM+lDT*`Q=snla`U(-IPaC95GD5LGr-PRSw9=g4q&1nf8`1}!LX{OWTJa=`+BP*qv zhvfr28F>sfGaVz#iXK-+@x{x0+lP|*Lvj;N>}(h_pS&<&0C&G^=9jq(${HsnehH#Q z+#Db~&^=0hFtiwV&W@+mf4ltn^ZgqoX;Ju{dlEz#Dni%dcA@I0YbB|Ob(Xw`1>Agi zYx!!b@C2xp>)yQv+#ecbjV+r4{5ZImJyUGy^Q;(>Dx^!+KiY_WrZA^Ao`t5U<5F?NkH4zY8qqFWU5w~G*u9jCxi5hhrEOF#tP41m|W z`ld~Efc6l_`8b#%2ptL3Q4D!3!kUdaLM(*1K2?M%$1`q*7HP({>HlHst-{)F-sfS8 z6l-xS?(XjH?(R--cPMVf-HW@sm*8I9wFGx}{&{}i_uxHB4mQ`d$>y^=_srb0^`Z{v zt8|F0DK76ZSg~<&4`D)&7L8Cb;b9wJz9__4);vmepS(?4<5KP?R(ErdG0|L^h`(P1 z951#l2b|YLChoowr}RwFnvHd0?xKYVIHgxskGf<_ig&U$!)h81uO$B%!zH#h1+si?`LT-4W-9MeuG9Ele5OIwue1B+Q6y%}4p?;i>9IcdU{8&P&YDN`k zdmZAcYIvY?>eneH?M zQhk03m+JY)k(WKgaHH0R`*Mf>*HnvWBSo>I+g!P6RkzpIi#j>iw-nc^;^Id`Hk+Sx zNrNU+ytR7wwwi)diMKg?vplutC6C_CxF$w41+9fE$K8L+r#6sGi8W=WrMiwfGk8f1&>S|= z^bc{-+2N_A8b$+@KcfU1$te&7YADs<1#T)om?(Oj)Y$y%0etG!TXGe=*4)HX5-AN< zEC&c%G@esJA$0r$^f_`8n_XP9Gdoc29XN1!75y;t+(^{%bG#k%(@Vk<^L+r4NzN6K z=0;G@G~-X22cZN?O-q-UMdEK9o`#F#UT|tUFpItjC)kyz3^Zt5R9yuL^XV@+sl|o} z*0^vWM^1}-0qxRNVm~*pdD@y&q1(PdxobuZQBp~oiI zKMns4Vojo9h)Ve5kxHeKN7|Z_9Jgn=I&{T>JQi-nNskA=IBLGshFR!PVoBT1L${@g z{#qey>^OHK#V2ChRM|#{FTsoKg`ro4nw^11 zM&R1j3>WpiE3zY6ppl#5&ma6+-UI=-|J40cq{tGxFt>{E*_{w4ju6<>DMU4TP0kXh zNE~-?5@4G-&C{&>e(W_QUN31eMy}OIxad+z{KJ5u!V*O?!WO`Iw+A1Booj-vkN^%z z(b2e$4lh|mtMXE>s>CWh0+?_ZqZ8CtmevoVi&F4HQgp5s;l|aksOGLK=MKn+f&LPP zTH6**Tm0tiC(_j}`+jIccK7$;qO^S=r(CS+z)}Qh3L-+2i%SIWq#=BSKd4DKoRPBr zi;*?Q*>jGAhV||&oi777dr^8BP59q$qSg-1Ghc|=v(~3}LwNp%bo_y;clu}8R^7(Y zcAn#g!Gal>ZK6)=#_^48FPlsE+kkW&wi_%qyA&c&aI*iqBg+9z#FI4III6osRH&2v zKHDO_M}E+9~BjZo^XEV?=g9XkYN}7 ziP#wpJ4U{sLe}&0evSr(oO(R#u)f_iey>Byp0<^n*GoI08r|7SsUy%fQrcw9?S^0> zB8j4db`8y`br&J|2`O%Z&xH&NTPNjA(jWD2=#mL@h9J&bkggx!wW(uydb-HM)iL^} zhA7IZOJ6V>Qos?KGv0uO$D7h1q#bpy6LKA4b<%}+El*B(U5&M$jan7S4mE zPh#O5UG8GPZ*xK{!Er0F?wl!xKH0a9f{|-&;_m&>stq}njLzN^>V&DSale}Y21EU;r_=@YbQrAm$ea)oC~fQ#8wcvn_K4}3u6aGz zq*+rB`kk*5VI0JjajLwYU-qCUN>mD0{_G?>!`XCu5NB|6-Y%{Lg3LO+hw!GhX=;alM+>Vj^GFJmh z+S`QMJ(@{Vw8yBCiI+G9f5gjFi8;TKkRs~w*tco3lgt9SX9F@5b>(tXXDy7x8Zq&yF;$mPXMCCSqjmO1ap z#G==ufmwgBRko0&Fo^39>hHHT*s*FfZ%nqojQYe_ckCO6Jq=9k4ZHZvSa^uzWWKF7 z;xhC|Ed14TA3y+J(>iS6;kGRlY+WX|Gp0g6 zJw(C5vf%FPglC0YfCg>4mQAWbyD(D8ejH3s_|Nk#?G-cnfZm2EKwCs;S@^G^Cfu4+ zHseV5+?-8G9&|SkI_9~Y@yC8+Gs}beSj+SMCS@}Rq$`#^V(A!ziZ6*X=0P6zD-)5` z+gw=+AWZ8_#D9$Jmli#LffQfRpspG86UO^Pi&KO~h$ouRqARJAwCx+A>8q`Y^qCJK zcJpV|*Jge}&c6&F!5OZYdgwm#Re4x?RG1qmt-K4U9Aj;prBKkF)*#JMzL5q|$j%4`5hU) z0*f&?mrE3Xw=QAmlPSh07AB^!;a!E)%W~PcI;FvZz)nrfZ@V(h3m+M+7pjSHUMutH zM7DxhSNnOcc2atvsD05^J8jc80`=zX%kWLEI@ ztys_0DWex|ql_A(rDnD@!h&e;4Yv0UxeqI{-eek)5ej9Xmt0^{7dw*n0hqTu9&>1?Hiq;Go2!qw4SEFVN`a6UApKs)GXP zqA>V^xm$qII-`s7qyQfi^@fn<2Z8*o@njRh0L;B=1;03#9x{Bk@k*pq=}V>nU-G)Y;DNr#o0yy$6*CQ-))lCxureN2!43$>b}UNH*E>_ z0hUtlkaH8sctCgqnfin@LaB}yatEcugv@O!6D5(lJ2&_3VZ}3k=xX!-7%BpQ27eo0 z;A$QL6l;cuju%=AmXUkRCB&na(A6%m{4%A%XdSh31oRyWd?((diEgGB^MLIJkk>{s zoNGAhJ#eyWX~4*{VGGL)DJO#~oMT75g%er8oO|FO z8CjEIe&wt!OOtJ`?$keFz!JXIMhIrGK#L6}V=gQeL&aVQB_`5hR^;b02LO1q*Dso6 z-hFeWqX}?uf$8I_aiaLHU;EVs_u|MNU=+M8`bEl@>%-8X3;<%L7hQ`$9@vV={ z+;TIT!`yt)$Nu9O!OV6g`~zjEfed;S(fbfP;vFt1pVICYG%dLdiBIE6Z)arayDQl4 zk14D(=5-zm=axG}|9R1+Y{geRYJ~7x*CqT7;emjwzys;NFT{ZB|qBu|~ z{Ik@n-@|bkkLNVi<7vp%vLDNZXCt_zz-~!=WZ+T2QtPOv0ds*zgy$}wKZ$F zPH)SP6~v1viW}ng17&56)L<9Q;5u3^{#<+44EzK@xN10>2QanAw}CMDJiil<0LnrAgGEXqSVNW@I=?ga120b z)m$Ic%8v)q8Akcb`30J^!(nkw>uralGUF_2?CZOQtoh&y0(eJxX4+9x8PW>vr z-BqdC{qf~h+mVfiU zY?>PbUx%KR*owV*4V^?sTc08ON}Vr~Ac99DDJfXVrK;ycz5q{JGC<8mAFlc=&?1AA zXU1C#A~O=D;Ec4{1*VOvuQlS+;5@3^)@+@%X%h|K_a=!S0N;X|XrBfflo+H|q~T&n zPh5$dFX;BOYs*saC`vT{2abQTbx{Tl4pX}SFs=1K8ImU<@kh=iR2ejHJy^IXzl;i< z`)>CanDFF^`6Cb@&Cfxehl}f{^pBx$Y5)3dIem*yx>8N* zQbUsz+Zk`#UpT&ASO)}T=Pwc!cJ@4>dcoW>9^%-`x7_4wfUb=XJ-cXyw!p%a8vS{3 z?q;%LV5FWhdi-;3Ov|C3ek%d}Ws|f2o*kG|+&OFTmwH{niiBrGrPz=Hy2|gj$0_kw z%SSY=ruK=!%C{&Ea~TQW$61Rkdij7eW=p+Z_Uw3vpp^4o7tVKX(n|srB;dDF^DUm zvdaFT*Y(}Z&H*kTs&`r;fEM#pbzdKWQV6bhoyeWOBSyx4CFI`F_c@Qj*3)6N$*zS} z3#mJC#^GdPXrU*tWLP$|&v4RwqG2H|Hu=dYoG|vXV^?(8%q)w#rr!74gVbg?D^|U`2+)_ZaVrnsmUOFxUJuc267F9kzO)-o5 z_pKA}0c^`%Ox2UQHR?MTHSpT${)@;xDqdY@h@Y=pddeCM=D7~12*0Qb8}huSl5t=9 zy2=h#<#rRDVR7s1G~>IeE-aO1%<5-+$)+5K6p72S1>V%3400pdtLM94F9Odm`U)_+Mg$~npn5zKD8#WK36TdY=vJFY%iK2c2koX`u-HvJfoMA?<^Lz)}kq zt+lmM+I~Q3tHviKrME)opp+i{(ZN%BQHMn*#xvGEDvXc*3X1LsYgCy6CL-bup!<_JFGTZ&5y1^?cPn&o}{)fdGBX9pi|Sg~AvG!p&K; zfXmQLsvUQtm7BH7&078EtZcU1I_2AYt?RE1^Rn+WJr@Rj5lWMfy-wRiYmYkDyP_N3 zJZ{$ZOIoYm&qDd}muI=}_W0xKr*yU59s+^w^_>N06MHS`4`;ecM_CpVoOMPeutwT> z)7*<(=EPOu+tV~~*w%h*sy00AdsUcF$MTxbDDp@K5S!&~H~)AOXR7Q{?DW#HRhmxz z-L2dw&@$`Wo0Y8)M8U0f|6d3f| z7H3RLS;cY=z4Tmo6c;`I8f<&bx;(cubuW5p`@lQmXu3Ipuj(^(PaDC5F&m@b+x3DQ z;plvaHgL^1qt!l!s88{5-+aoZ)FHi^7E^A!D(%%Pyit;+R~r)*i!PrdcS3Lwr751y zX%*993vlIaO-Xr%?5I5_rTrO&?Th51a6)^myFeoIb>i%$pEAjmtExH7`R>q_MUW#d z4_7Ll6~05xDANjnvwsxp!O0qM z0Ged^GTzYJkpePtSVrzp(K(kgT&E<~A!xE6GQX2h{0GCZR)CD}x6k8wYbBpQJdVvC z;hBl}LgB;p5&zm5ArAWgW;dQ3{{8lnCVSJ%$xKeZ2G_Zfqubp$CH6;OfgnF)v{)IC z1tnHj$FQH(ducttpilXudec~fJjs;GeY51D#2i89I}Aa_DXC?4{gm7`8$-m)^x0Y? z3oQ}pb^qWpCbR%>`aiM~tbpEu+ODJ`SD`in?$+nHq(bNtoN#lv4Kq4*_+2}hy*&?Z z4*zps^u&OlX*Qybczi_EEhCUB!7DY+>(`yPCS-uX*M6#o&o84oj^QwA$OLp?anD>;{N(IbRH&Sj#1#uo`f2(D z=pbpICJ7F9WP?oH-bF=s^>zPMWtmzFR753gBh8)pbKM|Xr!uY;fEhV9^s3+`HQ(LG zlaNwckG{TF+mL>?ma|>8^sU;5EZd-M&j0z|uhTaO9PUdtS%N5mb?zrANH^D^zA#V4 z)DV6*@*GLm=Cpx5d~U3;{_X---7i(7{pO=|?g96Pu| zi7q!@|AsDS(O1h}o1_POsh9I5+MKD@+y6Xzs?Lonol=fEbt&^~Iw4Z@+YsW(BZ?;L zS>yPenQyC^O>spy>%Km*NhcQCPd(%H0qC}Ze5!ZKQ&-&f2Xq35+Y#g{oSdDs^EG_1 zIUtJK9n(~`BT`kV^Or0m|Axv8dQ>s`TY`p)Ub3^Cb2vRbL^P*AIlVo}NJ?6Y?$adN z|3~o2>EfGlSjtRj0gRr*AVZxDN3>xzoivZ7#q2-OQu{}4=9~NH)hg?9l57Qw=Gr7AO~FQd?2}aQ zZ{9$+gd2m}ihnB$HeJdLOCvG@+6mIXHt83zPQ=m?Iig;!%ryZaKxKo{UZ!FRgJ61E z1#tsxvUplVmWf7;djU|}1R#bgZb?Z$33C?2YR}?1m0rO~Fv01M&T`NmDLW#zQT7df zzBTnwO|FtN4da-qI)458M+Uj+nPq0H@;a|hscWS@!O9T<{iu&bjpe#}ntj)#Xs@kq zB!WHJj0?SZM{DulyuT}#*5POh{^_R;L*yJBXB{^goGoGeFdEYM5{ZYZM;TXkmS(c< zdW)e~f_BS?9u(mHVxl3ebz{gyzMMfQlY=+iR=Fd1RbP5PyHz*^5Da~eA;6Xr74z9*F4nCF+r7sMNi%;JHsKxIud6AxSpYUh z6l25>uas;^0HKKGoWAMA;(KRg1Fv}~YBOL2FiRac_k>WHbOxt2@%d2wyfK1>N2W25 ziDIq_ce>u$i1g!8ijVV=MV*cg+bThVGY0w3XC@dwc>Rw~cSi5`HrcK&=Y_B-AJeBl za2iV9eLp+q?uml4tv-4_-y9!@|NWi4nq{Nv%K+cl2B|wD$e7oymXQOg3nW`EI$Gea zzZ-AZ8ydsuqa9G|r}~FV4nuB7L=XZLH2G0QTcqD{q30a8)G|y!#WEM%~e6)xt)+G;&U_;a_Z8rv_9$Up{eUrX@Z#;;>7i9naC^nGh!_|K;JI3%e@m|m zAW5FF@@VbqN38R~M=k54npHc+{kG_e0Z5NlW%#6h6r z*#DM0Ci%t8Nz89f(ouMHSH(Me(!4Q%mf@yy!@+B$_9?9TSc0J-!h+R7HaL;YKDZp@ zJZPto$f+<=G)^_?;41nz;OwE}`*f7J)XZB@0c|@tC1y3lJ2JBVYYtr^bE;$-E6p{3 zG|UpgJ%6G!@0W0|mg*1%0vQhVoBS zk@@)|A0PgQ41B4qt0gsa^dZz#agu2YsSOj#k_B?jE^5QI_-qYVndW>q8{cNH+w3*M zTDN*LJv15+ns^5+re&yb@87kD3ai(4g%95Ik?t9nlY$Cy)w4ejSH;K1H1_6Vs0Ot7 z$nXv(;y6^jbUYeyY!eUV%bS7Eg4}$V8_=FEPM`BTPSr;d9X1I*Soqz9eq5;duOu1m z9IiViPFE|FR>&bCL@5E?I6rU_X_E9)ALp~Bj7Zk!-9~=)`A?n7ZO)eD(X}%94^AI6 zB3x(WmVnT-o8I)#@%1LjbTbp`rTvY!(GWBRCF6FmFnd9f9&fzO`3Zuur_L|p6P_P0 zavHG8y_Vv9f-dBD8$;h%Wbw_{=UNnM=n-$@eDb8p367YsYLhB51@Gm5=pLb|fOw_b zDJ6qtcl8MSTW#I{4_-(H+FXzG%Z_M1r+VWf1-B!Kd|%O!Xrs?pJJG}OF69i`5hJOs z5UxFR-fr`=&$Lf96@Aff`UsU_|7VcL%y3Zp3~wekfi)-*cexs=3ls$oW8nwqaDQ7D zr}BQxTr~bdo@h$d0*h~tIOB;*ktV+2sW2F2Lz2%TMPsk)F={mb7D}`AR^fCgN&$Ll z^R+_o{M706`h9nA0|)55H@m+52lhGcBwqvfclVyEti+1@Fj&wMXi(DC4naA}UrQ zJ~4My`pZUwE0X(rqK;ubW%Gw9s=ovx^5IKqgh60K=$H@$?F?R*9|>QNCT30$DH`F? z5w_!7CU3{ZAcn@rjeC;|K|NZY_mI``F#U?F^>#kQ8>nr00o}X3-z@WmH4%Q7R~xa+ z!}!fo6AGuW;2`>wveV1(Nqz`~v(0fRjMuT@yVm>;`xRjN>A)et1k;7}i|@`DVxv*V zLd^$RKS!fuSDrV0tz`T$3XrJ4R5cj4D^HtSN2OcC+6G`@hZa9;c^sSkJsuWciM8d{ zLXM#9F%O%(<&mIV|3d}fJMNjf=#U;lO6QMMvs{F3pMSgL1ZakyNQ|q&55pg4DHPO{ zw$3M$bG0`+e_{0C{Xa3Ia5rP|FTu>0_ZjhC8hP=SKch`QL^ux^{}uvfPUTO)q+D$H zys6k3T!D8diD1jpFAITycUS|XX{MwBpFA5oidd=OhL{X|Pgwy_9Xmmn6yZ1!A< z<%+2S*Wx#rUCKKIp18k}Xz?TkB+`i}b|T_2$K>9=KdK&m!9Wu>tH6vdy|XonSwpap za(L9$K0IT<4puK~9)-RF5S|#Vk-<+W)fBHJk%jXUrR`#JLkRWPW^DwKs66C2=i-7p zLaAW(^UllU^1q?vTL?nUD(sbskUI|8zFwA*>YypF%g(!(M1y!nYd))eHYI-jE;48! z5Xyru=o%q>5;0BsAFuFNX9G4z7VJKvsbaGFAA@Kz|l$|^^7Tmvg{G<FR!zc7JGlG^Y>wBkYi5<_6=2p-}#eZl+-hIt0{Ch$2TmgoK1oN?__+D_jZ>`=fXg z{zqYM!W_VNjm!|8nv|E36u(gQky&=7vRETX2G}NFr)KC{w<^XZxNi&`g>BM_LaL%(scUp{sxSeocqo#`t>sJ!RKVA=mEBoW!_@-VV*HR>*z;YtY`N`9P-%hd z4}#0l_V6Dd>*x=gdcj31*6ooTt2bWAaWDK}v2xBX|C0G*Tm(u+uISLmCkrz$x*^|d zj}R(XlP9ZpTrdC??(W&v*EREb3DRuVpwr`D( zpMoiem-peDN5|d|JuVzY)?oq;hD?#c8L$n@l({oZtBpRlqpb2vjx+7%0xC>qy&ey( z99FTn))NR4MChKNd0NM(9~GW>8YlTzuG^;OHU+&|Q5e8(eD&D(oD3H2&|E6Rvucu96q^vrA+ zONe%hmusv8a=W1y8-`OY=DBA1ZjN?2nQC9V#f+h=yXDkY!D7|2+}n$^hnb-V^+J&P z)|Hwr9^9A+g(o|vwp;KJgr8R_CZ(e=;J&X^)@TD(?`m!lxF57PK4sW?ist(GJS69#$LHC zpz)Yu^|JlV=+D!Aj8X4R6?UH&Xu0;Gq{ z3luhs%0J=Lq}wjf6S{k^|NRPh%(R4Rl0u{LaOH0mhB}EG;fdAa#vMrK-~Y%J!o8Ar ztg%6q$sr*AGIJL$$v`~s`Wu4e*=g~2jThaY92n$@Jhw~;bZ)5^+wn>$2@o^x!Un$dGG65~iK6Bbfzijpix!iz2X2M4wG0h7uD zk8be`GG6I-J_qoZ-Z2?i!={XI!#@Lv^E9Ba5fEjn(n#4}Ab5I6OV!v`IX$+EaB~JX ztbct94EMzA?>|jw66~^^0~Z}UZ9B^YBR?fs=$qJGp9l6j5W~7gLZX}_;gEE25R=t* z%JBH!*L#|pJQj4VbgpmpzH;upuW6eblddlFLx+!#BZu}RAd zy_#JwoM*~;%33)pZg&%Ge17Y8@eBxPp6zYz;_TU4y{PuHdVbg_Un%zJX>@LR{dx)M z0fPY`w<1o#n>4?j!R2^`cw4}as!1K_sEa|TLQvBp4T46O3JwuciVsA|d1o<&q2}*~ z7fS7ki%qaoFiWk+zjnZS(};Rc)=Pr-Bq)*pfHrbwht| zMfgK7gU-9x2*U2f-<1y4IX@qiX}`Y@fv<=)O4BTIM2>meIdgP*c6n2{T(V*n3)rN% znyQwg6=Fi@%2n-<_!&XJhF3^TU2|jMh28n)p;-+3Tgx%42W!ZY0y}#}VJ~cg>MRa1s&JumAAfGWFuSFMAsS z0>Mm$Uw-;J9QH$6$oc1AieIK|kDk?(3$?%xg!v@F>f%aiz4mM zp_%F&jF2M*zcS+@tflFGHwXhg@Xb31O;Cgwr1A9Uj;R#$&pFG#$lS#|cfnOMcbSP1 z$4?}x{vxhyi3(-%-`7L)&2a^?pO+s-iO)sF{9HoO;T=q05uzw8bWqm#4)) z=h;xty%&fAJCx9LslVmo!rNnnOLi6TSriQav@WG~vlEauHQk109s)wL&QagMslmA_ zq*D0DWZQoi+Pmv0E>O2g8k#G)|7)t7t&1$LX6Kt0r5_=zSba`B-7d*VrmDN~NTa~2 zy=6Q{m1L@QjPGh|!-3eIS*qFPV1n&m+E!U4A#4>J@byHe#skYhd10AiB??Btl?buP96-wI0Fo$31~5YBW7fP;l6Kg4%CXP8N@pnI9I4g~yeTf)u*2 zo0N2u0%i)!r8YEjkn2B_z`g|4@C+vk7X>*lb?NwQ`yI-%{C5A9YN;r6nTG6^RZ+|j zvgCe#jj+#ojolH$KjK|U)YIG;S0VBa=VS-e=sIf)8`t_W&FM$E_K}&=jX&HvL|VM; zv$2_HqkS>fN8LHZFtQ(i^lms_ zlWrdrC1kPRQx-|7ohU^>tpr4;NjIpKV=zg!=-XfKuITM4!!m1}7u!Eoa`ZXuWU zMgu2{WGVYD0{ajW1>VZNPLy{qW`^{rz=x-HyJwf%7uFlH=~1vE-#^7<)yoY>m@wm@ zwB3nFqfZql=AViJ2thkI?9J^BWCQ+{^%R2ncA0#=g6eYN>!f$j0_1=yk#oVQk(UlWz(1Xlr8tg)GBK0_Bs0x zhh?#aO#UlUQbza^p+ufCV`EF9s!H~YzWe{2bkMC;V`mxGkh8YEhbrRs2gIcWu&8n` zb5ynexN~LxbJESu>2}Y4s$fBP*JI}DqMzxlNcEC2hzE&g@Q9Pr6M%2OuHVrNUTt&= zLF5`^J-YF8U@@7Rv0{X73Tpbr22o8d0c%auvv~qL+|D(R zo0!1BkSQ(~fii$vo-nufLFT$FyFc+XtV>GHGd@gXcMDliK$-U&K>vELVAN4J;|)BW zy-Cn*f2ta|Z-}0-PdCfw2|U@bF}(iN?2@MB{uL597`Upn)LtH4e)lq`PqmEHo9&V= zqO^(8HfoNoJ)C6~c?Wdjieh{MQVsd`zK`nrK|gd0qT{Ll!X8sB*{ zBdDnPsn>o_^jTe+cP|F+JLl^%sIb?3$?AVQIvZJ%lI#LbG! zb3AR7171SgBEgP#$_v*VpEr&~L(V|oN>0t^2XA|92WTlvH3bBS z^9WJmKYn^us(t9Vk7F@U%-8H4YhI7GMN0*3uJ#`Drp2I|;mE$D24_4_n?kSW@QFrw zbZ`_{{?QH*OcMtkp>^37;In&v&`k_yX^)a41Y5vKe)B4@w?rh~@PH|S|2lz3+YDyG zVOI!l{nhvgMWAi5Rpwh0j7vqcq(Qd9sS5XY+q$8Wp+hS_y|0>x5RtgYYB7Fuby zOrruHBH!_1{Y4(ZSRGv(V}QFO%Pa1Q9T1J3lM+rr*8ql zSmU}QkAHW6S(>$0ZXd{?#gHNY3G1IS`d}-HMKqa-b2Pra53pf}YVCiT)5LnsodF=K zf{8yAlcm4C4b_~G)Ik;KpDiPr6ZeuQO}(kT#o$q;JJAJ-YpH8A3ezK%9z2WP?oK!- z3Ahx6-;em+-{DcgECP*NzsCO8)UmQzWUYDDR0{mM-Ztw-RLO4KMlhPwg={CN!HIuD zgD2{kLL5npxI;Dr`GQ;9&v&`|;25!PwRTTOKRb7>8e-cpnxz&Dc;(@}Cr&r=cJHQsVR+bQ@_ z2O`jW_-|iZO&gh%R{ik=iBMtnPQtyx<-y zqA;91=_QsMFD_RxQB*lp%r#RmGRfXkzXFFE30cG6L>~p`PJhNSLlsJiYv-@74lX;L z)SWG5E^NV0%bm?@@*93BGE2Fc=BOEYqM7M}PtfbmgVc2;|0^-6b!6HhxEpqU`BBWN zKdOPb+$_-P-VMVah2ccjZg7d6c#DJ^HB1H?7FkKCn`mSxC!03F8Ih6r^Rn4OSaE3p z8E}pGM5Ak+3YXT-y_}mL(lC$0u3w7-pZ+!3Xo&f0upzTAnK}Y4~p?Zf4h(f%t^g|Z zWA3~viWoeyeg{x8sDU*mV~9a>u_r5lqxNObc)6|`;-r>$B5oFD#g5Z~PAHiX9HPp1 zo^fp(artFu4rhGQ^^^to*NSzi&6kuZaoB;kRA83rxjw2T4SEOTLc;!>Sl)LI-;y%< z3m2N(sRCn0f;u9<<#)lm*Yd5a7Ggm+YQ*12Bzn-Q{dd`i*$Q zD1%q;CmP%c!s9fw)+Ki7^j9Jya7SNQ&1#L!Q{eRq2+TkJ4hszw{yO3biSz$_ zk+Rc&yw@=>yN34Moe`CvJO^E=e1whWpyP07Ru`i4K9bQxPaW8wOplivjkDJGF4 zg-w>7fuMY05!Z>a#m^Km$WF`Yh*xsMg&mmDkvCF)++JB!OO+%;rSZGrK-(-92gP0G zuPMI*hiYkEN79DdmECE(>H_N$&a|I4{BH*NEttcVsQ8d5B4Iv3cfJg6jki6l9M%ZR z;B}%AIolhrs|l?z*k$0)orX-Yk>IG99l~0H8Ol20ZvYHL?}Gs_y**o*)%#pG*S3vS zI$XvSi+zz#C$A{MQm!(3c~^1`M(gz<5w=LvqakJ`u2My+$|e!mBA}DoGCGC`m5i@$ z$4j}!C&ga^93shj0_|SbBKlXb$NUlN>UIx0bMZWx8o10QkMlT~kdd=im)kC@cVDL~ zciUVMD&g6IUn-jMajzGngWM>q-mulgXtf4{&_6pB_iw?``8Vv9bSzQRJ{-S2kR4;) zOm=2ayNarXtrJ9_JhUT8?9AY}!cd+Tg66SK><~h-waec^h_E9R&Mz=5;Gr?JTc2eR;-D10V`e7J&pOntU z82AR6qh=*XszPn5OE;%`{ipA*BB*Szt3mkhZ?xYQ8WdU|h4dXahd19zaGo!!(J-zl zZc!Z`zSC^RRxLyq1X(auZ;>rWW2bdDm`+6F;u2B3we=s*7JKQe8DYYF>LGn=l_w|Y za3G}O@NWkV&v5cZc7-ngaQk(|pfvPsmDyatw%WLrh$nxn^Cc;Xr<7G8yxR#`E1}fJ z*4UjnD0HD!2JZ$MF#;nfHo`M$z+?*`b$@kTe`%AyK3XP9Lib}X zy^Xe-vdOb1A+R}M9vNPx@H_2RKA^SCsW1Ma)ar%k)zS!x&1QZ?S%2AsTPmds$GIGD zOre%x`HuWnBKX+6-GqWthiX&aDn{(6LGdt0N;&&EY`%=OU+LngLb1g|;kW}LfMpyN z6DTG60!DwFLw2>o**b4Y?mBOjGlSLKRsQRn|Bo%LbOHWArXSN>t@L@+x#?unY1_ zCHmhU@MI&U(aA?C@sP$b{bbf58@4l6@DND0frKUpqu{9*_O1W4boEj!%~}f; za~62^eo>NxCr%e?oP!k)0^JcOysTVQwV}y^>rvyc<#jpkaQ$=2NH@2ze?AOaoe>{} zvcjUUF9@Ml9tOQkTmrp@R*&DE%~p8ve#Xxg4KO;>;4e=o%X8(_+ za|jHjYr1u8+qP}nwsT_J&WY`u*tTukwr%_7{r)@Z9`&eq?W(n&s%mm}SkU)T;qsz- zz5ZISjqU$i@LT>W-c8Q0;S(ku9ASMWEWTT-t zOpVwIky7@mT4^GV9}5t){|PP#fEKZlIlCV-9z&2bVtxnN0sBq~G z4*m$=`n`ahi01-W>>fHT4=k&B_U(~9nxTU;e`z6b&l?MU#r=%=M(%TWqHt#hai_E~^>oG5D4%@v@^9I#0EN*U${7 zp*j&>TQ4@T`M^jrEnJM#2BTeAG2GRB^8f1`t!GzbnH zDr7?IP;cPr3$g+xr+YqB$71VEaiQ%PA|OexnPww6I(I@({O; ze+-Gl%zr4DSnvOx5Xk?9juco+p56`6t@c6yL9JTDks!bU|1yd9gZt#~yfewbTL z!A3i?F@Qqn?*U!j6$k%6CgpXb>#kL48GhyOU-dq99prbFRyWi4ROr683)B*ayG{1K zEAU-OIFHg^C7)M8`{zG*!^yJ@e5g48UVj_HB-4 zmp%5*$H{o}Cgok7O_-_KYIK+ylAyiPUCMr3?0DU*X}tQcPLf`O`mo{+jizZ(b0$wc zjwGT(abyTGF?Wt&u%JIaresC2sxQ6~MhjXA%G+IfRkl@5V`J)reO>OL3vOPFL%99@ z?4R&_H`PazWc;KIFxdV$NI?TaR2S0PzePn?$2QHgVygfHl z{-)8HDJdq^t&CfayCI=LXPlHRtj!m($iuJ#j!pae_w1l1-;piRl-)do?U%j;;hCD$6yj8DBB9Vb~FOzc=MTUrqaB$?Fhs0*)o@QiF zsV=5HTY_vW7lZb3<&E=3FyKp9Axc5EY7q7#N(uC+HHaMHsV*|ej`EO!v}pKWx-YiE z#^s1Belyl&4+-<;lt|-j&IvnWuD}l;GN7d+t0Sb9vH&c3j-aB-HSr!)Fhdh6Suy-t zDz2Ak1qf<#cdCKFO;RH>=%cOj5N`)uy=?75P(HYt?A?wZ#gZp8w+B% zX{+>O$&mD1CsmVUZhePXrmzsy9ADt5m$6fk4{hNM1pSzS=9j_$-PEfQzgMu5E%$t% z1Q!{O)w&qW2F6?eaKxL6(#)?_qZfPeD@3(8L;eOBS@1|!H`GM7dmL$^b59HhdC5YK zWrcK6rbtpO>o3TR-#~OpPCL2F@7PpPVtT90{91Vp~VM1ZP1yfxb@&o zF0hZQ@q6_@OofLhNa?6ei(s-Yn&Js(u+}+Vx^Rm3sIC9$LK!&VPc-FnM=8 zwC^gs^qv|7F{Cs3V?n8OP;s~Y!I>i2U(OK%*~e~+Ydqo!w|}SZ|`mY6;MKw zAO1V*%nuJ@*q^2rNfeSWxd|>KVuRwJKSIy`YB+@{LU-&R_=#6)sLCL}cU8Nk7&Vexl>aR;KV0!6)8!19p6GUpt@>ceJBdPy`b4w2i<7j zYPo7=(JEX#O*bRto{jeLCvQ5}zU?Zhkw`bGc;VmILW0`*Ec9W7~l6BwI~EuLzohh zb%goMn~mAnxG^4IWjJ^Ay@049YEF=VXA(Ao;gT*F(hyrJ3EeBW&Os>cFtZnHd`BIYWD%Mqy-N8+SF5jtMShn^>9eE z^s=2ba~tcMypQ6@z7Lqa!z%8G`xtl{+dRF!UY zGQ@k#&=97Ay`Xaywp8&sVU^|oKVQj`8#nfA%J!s*x+YAlZ=F5PA*pQ+ddF4gCo` z!J8hEVy)$BV0*rMX?c-Z8Y;B$68vS?s*abL8Cv*A>GKT;tGeY+iiIKV$ z*T^MRT3f-B4`$_Ke;PsC(c<1^Y6e`pYFi@{i0%?He}52!;V9gZ^vb*@fX(!{>-tBU z{pVXO<65^}_IuwnrzEd*E-;%SBRbk5?%7x+6R3=7ouf%obvS}lfu=2N#az%Pn~MA- zWoaJ~AJVG}iY%fix@}|IQ#-YWB3Zh54y)yhPX|g_^rG5?0XubZ@R>b>u;=~xMA+Rh zFFo$sIUJS@4||t52-3m9i1O_t$}x#g(jwoHl1fdu|4ru>QUE@IEsjU5giAO z!Oj!xF|D7GwX?F2>snz_98*IOKyq;=OG_lZ9%o0g;lDF9=}$I%H;ncnuu*38h+ILy zY6Wt8(+~}wex)UGvDh5g!kyZ09-|%8d4R>DHw7wK5~@@2^RPNy66HXa?e@xSe!{K# zEq=IE=|FlYU#A9jKf(@F?k6SF7|MHQ^piWu7h0y)+k*ZO5`hQ>;8;Z^3y44fgE0QZ znD+;f##mjQn`8K086^ zM>QTMLGgO!`$S{iEC#I5MZ|++UVvj$QL9P%xPW8nu7K>!61lKwoTqjfW~`mOw2Sz% zoXhQqwrLR=X#pAg$5+jg&{g&DHVlU=5$4A_HSX}K?hCz1c_TIzZ{@7ENzgVF_G1jQ zpjR4?bgWs&6X&SADvGQ@%2Yhjp*7R1=kkxR5o(Ed{)ht6{comH)WG1HFot@H0Q=A(Bx zZZkAjW(`zRuVGGYWmG~nPe zFf8SOT)3u)^;WjTRn5-{>KIxfCPQOoLagCcg`3L=MM}WKv_2cR+{nl5_ z&z8S;dv@rtIa;mWYb%OxXYXf?<12N<*}V&bA#i>d2;$KZ`X4dv1gRybnY=vwUSp{D zmT@*@0+cT)Fa=_~C~l*F+t7S@R6ll{oEG2;lKaz4W2SPpu4eVjwl!bB!9$a!!qOka zr<j2s`-zTaQfP^{tZxOkg1E4R9gXy~j6KA_y?*SzDu=$fL zd>SrphPmnkz%vNc$pfWerw)B4I7Cfw6 zW8nsk^s&p@(aDjd`$-p_lTiYQ<}D=v%l)f(p+SrT(eABW1_=!lY68JPjV0m{GWMbk z(1G?^n+`4xro#*cgu)0Dq5}}E#tCbJAOI9Di*8n$>%+<}5eKuUW05vcu7oVm03xJx zKuG|SfZ?oFKtPikB&i2xZN((I20 zQdXVH!d3lB7s7MkT1d1uP}-@ z!+46)B>kk*U?EYqZMNAV?J~&vSBsRC4kGqBiSgqwq^zWzZf;NY{q4lPg+9$KN z&MW0x>^iE3=Qzup^6&H5#9iIY{(@On@9#qB{WQD%v=^Ns5PmQ0+>^ulHmj2ZY4rnT z^iCSL)5BQPeIGB}r|0c~7{MqB_2l81&5$^G&6a8(J?>j0EfN9`O<&Avd_Tc@b;a-= zI=z1c<^`VTbNj;b<0UuV0wzV7SjI1TIk*aMA5}w28rxh-tjYI-gAYeut}@Bw;(OkxG9I1hJWLFlw?1}nYyBTBQ$2MgiEo9+b(Q0C3 z)+{b|CM0QvJp+=!^G-{T#!VhNz;cQu)T?F~j)M_87+YkMW+)pw^JQmc-8~Q598CSe+FZOG#7U{;T zYU#B)E0B;hD_?Uwmtc0$z=G|=XkP~3d)+FQn{I<2^P@qd~OMve7r6Uo#NJR}okGKUATyV^U{J;Z^cAYp>Rv7%f(} z%E*_?;Y38(2@(oijRZh^06!H8;{Rxcakln0)?_zjQW`=@!LujYKK{KA*ALjR@xJ72 zwA>VOw+REPiF?6`w0w1w#}Ot6PHgxa5D3K(Lo%QO<_2pT;U=GLe~f7Bcpfw-Hq#WOe+>AV zon}9(n894hxYzTckC(8wb(WB{l%DC-fpBRf5U6X0Bb}DkGk^mUClDbcQKv|75f)&? z7moA`k^bJQ>K&ERYXyG`?3JS{H@kRd|Ry(eALmk7Q^v@2d4T zWw6cwn=2fyI(Qg^Ueow7PH*Imw0*`Y8E?RCB9#<;{jb`~?^X zP{1Z4_%|eAP?(p(1kw0A60Eg>%OzV6$28&Dp=)!GU>kL1BI3ei=&63+Q`4af=GwFE z2*8%C@h}|(ARK|a-V&xT3JAfmkJylcR9bPJJTN}@*+7;qoVyThR-Embz~Yyd00c#l zgrJB;Q9!H{D2QW53PKT=&Hy(LDsW9E{|yucppSQ+TST-5Ll;f^Pg-}LUzvI}MK9kx z+tqwtJ%MP*CbV&A+HTyS=|sR7jYj2}GK!KoW7)7m2K|(@- z%Q^!$JDQtrERyb=%G@4C-wT-XliL!%@oBe)&%t2t{^&JS`VM|)QlA@Zrn|%aT?(4= zZ7G(E8Ek(`+-F$eeW8Sb_j&X#%-|7U$58131oj^W{`evIsEofubS?O0;0BA&X-ed*2lVyjLO*-7t@f(-xOpif8~Fu2$80(K*h`?R$M*Wli$EX-;`L zge}8IL)NY0FvYs_(6mn@18#H^g4DB02(b4lrPv zl{iF2`8`TkomJE-zjcT(7m(PWD9_(i2pB3j1qo^tIP<`W)S_j?>1iLpQmT-UeX&)l zQ4M$;5s1stc#Z>xVSl#2(AcNA*pg_d%Xio0ZNFO>uW^?(;ccdRXvOUfWVG$f>e^jt zd-057q=xh8;IzewoiC@ubv0F9v-RHQ^Gy8VCsUK>E-UdnoNk{rDj5G|sd$DJHQni_ z!+s#%x6OKt`NsXJG+gibeCUzADfat&;Ym311Fi$B43e&Y;P>LTY5`~H;F&A{=WWyI zCp#|h)my{&dw20Y`g6r4neFBNfjwiZ-{UheaI`x%lI>xTaL)=%g&xfnU93|V>?x&| zs70uG4Et)ESh9ykjUn0g;QE!{1?2+{V#TQ?E_@&2Duqgpw5NB|66i=J*>oyAhRS_r zIHp|AWLol>d=xG+?uNdQ0v;s|1{ezhyvSKEB0!8_i3@qu3t$P!D2v?Bzo^&FwL+!e z{Lglriy9^7UlG7vp%x2L?@|~TVN{TG3LI+z@c$w%L9wW^46{^t#TSke2>`4xgtGx* zZH%#Ym_(=u%!_cpaB0weEfL^ow+NnRc34#?tU8Da!z&?Zp@c`-P znKQ~g;_6-9Z}tuFW&_G11x_~sgxQkM7#Bh0FFZ|z2Z=?3QCs|J8|PXa$XqvlQp_() z<^AJgxPvaob-i`q4;5-^iO%mp7doMiZWh}{bsRn##s6hs`>c``&iuWX@jBYH9uM42 zu<;q%u`dt5GA(BYDVU|f@8fFgX=?jyEo`Rodbjxd|1>doWcKVHMi2cymvW@eJO8|% zjTGFmw?x~KQJ~n(%F6Sybh|PQ(syxHVqA~gIg>-~!I?%~6SWAQ6a*J5DIs8*9`gWmk#8Kk&uC=Wpjp3qWl5l3jQ zDkxqxnshWG0&+(&5a47Huy|3{4CQA>)YL*EU=uRzSWEwHqoRC%hcQsE6Q@ZAi~@nk zwSt5qnbisgpf-mji-!v2QBH1fz45Hp!zp~FWJe?q09De2D@8+tK&B9gKg4N|;1+8N z>!RU(+FXk5EYsTXn}jS9dF6a)M76rvx^!9yJ^0c`W$Q%@BWtm-S7OlKaelY&e6ja_ zqy<;}&B{IlzT{xC*HH1f>+P>UHy^9lf3hol?EmcbvTy&%&fweEd0E*h&+59qt%S^- zeQ;QU!;5iJ?5^0U=I?BRDqZrbDcnkWmyIzvB{;JRlI@YvQ z@(X7hi3;K=@8Nc5{w(w=y*Er|}3jgK2f22Oaw1WcIUcZvOcaQD41s0n!ia`Ws8u?OfXY^RH$vyBuK*+76g{ z3V5l!iY#D*g-52t2N390>8XT{Vx&WwwLax9@)cDD%TD#+WM zxhhgrb*f<%*3$SeP8;M|WE)#fT4zQ5n*Q9s*fHzMW=0YA$toH~CoO4L={l{&q451h zSGR@8W-a(^FeL zyJ4HwwT|m7?Hxu1Hd}euOW!>=>tr&-dVi_OOmJBMObKWN_bO`U9taW> z0OHm5r3y$8c?1f}sDFoqlqDRk>6DtuUk=xsLTG!|z5LGf;#pwwpjd#Alt3sPFiQ}P z030v{Fr}s74c043@HH`X!dm&iby8! z>VB(u+OdU5;UcZ)<`3|_R_m4z?pJEN_Nkr1noE-#c)352q-u+*Wn%q|5=L+=kt_-@ zQizyDvr20UW2IoYf5=yyG1=f7UV2?;4*Xa9B}94&CIO3Zfb$^0PzZ4)aTU8%VE-K& zf!dl~PA)|PgETgVfGA4?zz9KtVJxvOrm9LgvS;eZDkxV@+do zceS>DSK+k4+>|JOJfkbcKt-2%9ImZvOif935JQ4$lD?!@Q(UBXV){JXpg-cr-+BAC z9XtbH$g_g~$!g;I1nW>n_Ot!8yg6C44o-etTtjNZop5i$mP>yZ`e8{vy==-C^D?#g z+=K7uz9=mFnONVLL-*nASquP8DQdIl2C5-pE_5%xJRU2_+w>H0yQhv<%EQ%sjCuEJ zbb;q#xRD`CevnjdJ2`#BhHm8YP%K+szt!>L`%3n0Y1gbKKQT%=aD4H=m2!tqzeMM& zbgJps5|6wg;XUQ*>-}i3-oh7xssB~%x?7P`oiqJh-BB`E1^1qqC-Y^mKVGFxE~hKk zZ?7X z(+ON6hyPr=7(8*pOSZ)OvhJ4kbYyj-A6&=An4nQhR<`smKY+GY>NX&)9^9kKC|~aztQv; z-0J2gl0JKs%E*^)ahnt2<{9{2YwQL`5Af^k9o$A+E<@|spM7jG0zTVi@mw7Kx9=4Z z|06?vF>~JaF45idz2u^#gTiuwIT%)Vk>#Z>68~q3RO;(imRPa8){s(vHYD%@XeR%< zq5S+!B}+=5*lSq=BM2)P0P(zjRS1w}5HS~!k%vTyxn0B)2bi|a&IK@ZqlKJ_L3Lp zhVNY94|9n!C#R3M?R(bxhx*nm=@KO`-ekey0WbQd$%)xPODvuDr-{uE`fnvSJ$30K zTrdLRAXR*ch}oQ#!Azj>LQj z{ry}*t{;bIRY+}@J&zduS6scir<(Cez=|*FslesD@gaV~k!Dv%qw`BeKy00wL^bAc zHn`i8zUv6xHxk#84Q}{2rr*Bx4}F9G=JI>r8lvBtR_%Be^uF!iPbEJ{n>j9yoxJH` z;(usEm$=KeJLz%RFL&JyIpBIdNq(c}cg)V}@ndA~qW*rTRkyYHLBAV9=GWzKtevPM zT9nh%`@FUOyyM^hYlbfG-Eun++C==5$^Yf+i|w@kBwJ&v*@DO0_EnZ@9UnZCpPJ+4 zYSiWoz0ieW=e`L{2L`R19==bakTI}dQ)vsVCAJk(o>jX2GNSf=zEl++HKa7Aw%I2p zQhgK(J+tH|zdFn2TRPZV-M(WHub{BSb0)^*WiDjF9nZ_4nfb5vL;k5Aq^Q(;Cnl>W zc0Ac@9<0N$wtA`J+=Z346DyPbK+a(MmirN zdc)hE2zknU^KgcRh|sj~+w5RAT~LnfjHME|S^eyVSw;RD3G__&n8`)iHkcstTud%G?N2<~q(s zs}ZS~-62%sYSVj+g@&2ZGD_{cwUns{BiQ!C9gpV29ahMx#L3Z<%YR#JYlCw(N*ms0 zZR`XP>OHRNOvwo&Y|raGEXVWu>&>e@&X>D>4ilLFAe$`i>7#9k3@v|d?*3q`E5ZMm zuf%xYB9`k>@qUd<)AN7K4@*6TN6V|@`?>nCIE0eBn~C`1D(iFs8~;of4LvDkNJ($4 zjxAO7j3|BpZurIFr?^Ia#tM zhHcyjA%4kv#`et0XsDKvagHHdG9fEM1`A>dLQ9Mi31IFEoDl#-iE0VR0g(iEL84vAxIGo=fJzY2^av2q{^ zJW&9JVr2qiAckv2rTv?aeJr@U^DkYFfI388393l=UmKryu~6{5U&ejp6kpC?aq9zK-m4hZkE(uSx+zjJ)M*7ceD6ers?*+nztTKJyjX8z`oxSJFUx|VElq= zDNZ-|(NmuFT^)a%GS4!Dmz~p<#BWIGT~M!BO0LI}oQfAPY?}?0t&}jz4o+e$gOedC zGUMfoBo=H3+pu-sMddZZLScTchW>$zN$Yy%i_M6!Vxd9SaI1cRp)Lo{8%HgFUYxtXvN77)py?mMFj27HOuiC=&rjG6(w(Qkgn+G@5t}aP4T95R{f{f zVU>?Uia9uD)CJ z)JBW`j;WzwG=3z7dhXQJutqo2z+gc={9e_DgW8GSId}itoLhN#(c1{pm|n?Ygo@h! zlyy5<`6ONJ;8BLNK5Z+xaYnr;BWELr5t4qvEG(LcQX-jiW;;rtA!HS$Yq#sYmRk4M^W)}Yw3WP9HD>!s9Z2C=}020N>!Q}2Iobj z8P@KBejm?R5%q!*1i8WlEc==4N(clXYy!x9qQ!9##HqVFl*HdXDL7pmlbII<+VDaX zJf*fi84-}It~aKii$O4;A`%830)RtFz-6)^9O#0%D0L|(X(c$#1?^5#;e;m^Idd1+ z*su^JY=Z(wP=Kg}pb-kdAo*M{AbSv@9(*az0_^vX`wS0_v69SjcFseEZ?TLPwjMN) z!hphdZ8Iy$+~qW-itC+{ooKtu zoS$na@b|XIul_nsx4uU@b*iJ&Z}%T?IOpaM8Si7EJ^ha9Ql-}#U3{)fd8jNN4!>(9 z?=yT&jdbErc^u!EOWpw1C0E@Cj@9)!BRJPXhA0Sfd+nRrj2_qWE(b^&7Mq=p(9R6{ z?~SL|Rp*9?^(E?uo2(k#9z^|gn^r#E4Z5#|zhhP+5 ztoIeGnK+u>KNwuvvI%}{+D&Wxpx99xD1EQ0NB*CObm6P!uZK_v=bpbP?7ah^;2PN& zZ-1*de?rd-skU-s{_AZ3=>S*?Vf-BxkG;TEdh=}dlXr-zC4Aa>KC{hHr&eX!lF$YFESPGQ>$P{ zD-oFAE#+VYoG1S`g-YJI+^R3lHfO;$)s}>*OEy2()#9`2a8e5lAA&`Tu*!KKowznv z=~sm&hOusQtGG*l!!`_f!8$ArdpGT@$*%*okYz`JmqS+4`0(rj&2(U2@$prkJy+H^ z>^T4Cl71edOivh~wlR^>@rsVEr)pL}M!dQYlSw64gtuZVH;-162W3;Fj^vZhd2_DS zfxXaYMJsu=RX6s56!UOi$h6f}RYh<2)jqlLJ64UH^TssaOpe};T4cy3+o|K~C|VoO z>wAnm;bXZ{|EAh&T5RZ;oD^PKJ5@(D2ljftguEmogJ(!76^$kRm-sL$y{jy9G_3#A z!qI&zHwz$&C`twaFbDy3j*%6-kAi?IMi_615+aQfm{b50qp&_*o*-SO+sxf`mbkb~ zd8q~vtfOg&456+#13*T~PE@-HW|{jxwQjSlP?EreQx!$ZAP+jW;J>(ejQ#2&AR-(6I{JSlv5TKaS)b@L6h zHOL-Vy)PHpXP$}x;}F4_OVBDvf%p^l!#w6pB=W3K)L9=+3`rl)nb?E{1Z7gd8zooPD0g6l$D?xjzaDLNG^I zZ3nA#iSu|ep3G_JiSOs^T}4(~oK)|?4QGEkm!fv3+0TCWK+{W4I{&w#%}?DMy*#OL zp!)t6zHxXfj`S+HxbE=g9dtBP_d`j^fzhpy@#&5IeDcB26-Yk z7kQqCH0dts(+nKDRj(b+h%BoU+~_lV%r}{u0fIR85x@n6ByoNy0I71(2q0xCWC!ccds+}q z8DMLHd*iKe={L~KsTK#d4D*)#!QjLQ{(%C8Ld^P$N)Q`D{NhCa;RpuMv&EReMIx&h z{=t9fuy6rP3Z=Q&Xac1|;&Cg_+TM*Zv~y?9af1eZ;+8f9AAOP4Y!lbJoR;8Z&SRox z6@yr2VH{fkSQHSX1u#ijpkPZv4lzyUY%7n``Glpe_<*-mEtR2~Aftikmcc0gz)<8P z#6b|@5g?b9^1}lL6oAAUEGjB+%hSUVG!bLohG_zUxHPr>{s4{F`s3;T!W3z++J&xa zyKLqjC$ZkBIc>Y&ZkGz5OETWSHNlSINP)-G_Vltm{`}n+dD_U zKVZhITb9~IH{akU?|pXuyFDk$j9-p^LSo&0X5(Qe%O#+w%S*?bhB(Ix+;wRh=l0OE zi8yCQw_`qO$&7ALoSwT*U!CfHe#6G|K~QaNbOn?3&iL{Eee9|46}jlCW_$0^HEmqy zh48Z>adY!}e9$G3JvF*(d2aOUQu7e?SEx5Q=?Fkc-cf&d~yqcH^J)q}(8+`?)Do;Z2Nt5Y*?2Y7VcbShI4Y@^1 zf+Oto4qqPBcLlF)+&bRh?LN_~l!XK_&l-j(@TBaeF|2pd!R>ha2bZG2`(68V=#$c*M$zM`PgZay-xb1@e54 z-XS!La$jr9vUkm2^)V^-H(v)+p`omQY_75GxSucOx4!JBv`8n##*^_ILV&15b}RLv zqw3?*MiGeKflVQBOq@abT#-3aZMv-GwH9ziNsttTK`Qqk6s~&_^CAFJ!@;%1r4{d< zEZu)G)ZWD5^j9iE;=e^eiX@Fr?vw<;uz*ZF32K(G$+)He9gfmc&l{JNqWz1yWmgvU zb=?9#9IP}Ief8-*2WUY#Kg-wQe1;%GRV)b%K>^_MfPO4Mqxb~GCK-)vvrP#vHj!#b zMr)CZmsyvm=sE<@2v7_VDdkWD2N?fV04h!-B?dgOHZqIQ4))&|DN4J#0q#t$3|;-| zujVa4&6kUcKN|sm>iXet%P9t9`PaZ0u@=!;(F7APj|MExehhXneSsmAW=PPBN`{V* zmj+bd&S72E!5^?GYybjd-|{aJ20)cNhynyFvxrlp$e!FwopliCm6p=4RVwCidf?k3 z8Zi*@VKemAyzi^+&_lcPIdg0Pr8g#y8uy@J3HEF3HpXkF;&U0HbLP5P^b`->n537c|6 z01&1CkK^jCV?pEtfQk@f2+C+72_nJNQmdvP%YWZaeKr-)eVulF!3}B8JWD7GGeP3S z1lk`@hyjVG?Ac$Ri}R@yXV{%F@jPF@y;Qeg#X;sM?`t5$db9c+7pxD-e>YvopbdWh z=C$oYnfTc?Q#ekP1Ubb;e!mBQ3cm+IpNEnC!)@grMDw=p?_UVx0j#<^cG>Ew`wXqtsQVM3V{KpX{jO6bWZtw#t98S%Q1{L=<2Eg4-uPYd zj3{R*CbVEE5eoZCrA#0~Ai#eKAylX5C(kv2BC`}|#7J(1vM2F56L8Ng#AHZ9*i0Be zIOb+sSTH28x|&ueo~(omd3gVW4g3w@?TX8pQ}4nd7zmO4#mIo5gg_7ui24JR2VM<) zvP5#l)fGj>ERj&ONvdbEZf z!t(dMa<|$Pfl!eE5mEq!0=hJuWHztP->@jS5YQCo=uKl8@MU@-68N7WMX!cM5Fm;g zHwX_X6%-`|0vr(%rF5Gmd*mVlzwd?Xs-2w4ZxN z?^oWDc;~O2J9TmF3}va7@9Q139-g({M2`kKWCFRz2&@S$QoJ^YLU&PROt;O?BqgZ1=gC#V6zNx1g3zw zn;-(nV*w(oNF%_MQwif}MZ`cZyG8ev^Cr~VveBTzh*IV(2tir`;S2%}hyesBYEGpD zYM%d>AtzN44Cqu=8A1Tj5r8HU0FyMLNtgy51!)-N2c&0L$cHF4zgKQ^0~N5{j@yWr zxm2$Q)LBX~^tGW>UrP$rkNd08?17oN(11@+UPK&6b1p$)-h5G8uQyfFdwS^bW9tYU%~zL)gKNP$^Xp?6oT)Dy zocQMV>G7Phzkr+KoJm?}1gY-52^r-DV_|fViPCFwj{YmI? z9EO|>kvX_Sw-w-jcp1OR{D{5R&{|5BEEd+qi}8FOgPUfe^Yj4_sMp$*IhOrskgQC8 zx;;K##%(>V+!nZPAF#DQ&XMV3d1d>Xj8OaWS6e-Fx;e%0v9V|4S=2JAf`%xl01Sq! z)>fA!4=q-#%v`7RH*$^`B_T=aegmk^zn_!I&$6}k?-ykDt?Tu*ix}dg*RtS=RBj%T zwUZ%!orszq7bhQKUGA9Fii_rGn~hhUN1Q)Ue6`1viusARL#Qb0N)#-j;Y@%r0I(=v zDBRCP$fEuN#y~ct4W5z*DPf32>$o#*mB%I@;MrzYxV)JfOEp{@9gNd`dr&0MH3{$s$lR0uTZyjQ|!jBJA@2Sizdg8Lkg_fjml3 zU^07vphgshvvzh85OZ_Vk#uTP%@~J*+O3f^D(Dh^Q!lp>$wW zCNLlqVH_wMnCN?N8jDb=|72@23;dfI+*l#)yYEVOKebMN>g^nTWDsaE4IwPRNEqNG zM+gu|NCu+(5D1&VqQLZU0>j1v3@bcH*22Y0V(E6H7Rm(#;j3R!=Lw22*O>tD7z`ST zF{Yo?@#T&po;c98KI-^%$gt29e`F&Iu20LMvx;Ne)^gW)=+5;5*RDn9n!8K9&pJXix&^q!eyA78|&jG=XWmf=~6}!!--NL4Q3z; zIAV35B)||41dTihq&6^w0#~IX9%iG_wRJO?j_GBHJ%LtK5#kqmVnzia3Ia+twQ>Mz zQb%u^zO12~|8QSSRy8A=@LYmI5WsRqa3m~1jyx3*K?4IJkL0mZ$>Sh-yE31j>Heb^ zOK^C)?AFHDyRjiR_(eLsjEuF5)@=Ey>7U=?q3s5p|8rHHgVx&Iy5(Sqp|J=yI9Ogs zQ(k|6QTQH*@oh5Ig6QhWB9+IcVyWZ;;_kweL zSz2-We>A-Va3tOLKOEb(Z5tD8oQ-YU+!z~M8*5|R+1R#i+x*Y-{k?D1R87y+t?BCN zzUQ2KKDf)mX_>D+UoVnKD4w^!y*(09v98;8SOej6yVc$+3jEy<<?zwNR3rA=u|FW%SO(W{ZQ_&fAC3tF1j&0V-~NIM+a!#B!Ru zlzn*0-#^1jqh^}V|0;1@&00Rnxoo@(!v=h&?bruv<~0{=hyDTT2Ga}l1wag9K!4Ro z_=wW1TiUTSVKdBt<5u1$85o7Y;=F`#@=>RE(0iw|X2wETr@Qm|dkQj_2exeenxV@cv zo8y#~e+WmcC4xDePnT6e#X`iAq+u``V5Fot7Q|tG9|91k7sP-tyr2rLT_@I>0 zkP9-y9I%kJE7m6Dy&**ZTintVw{L-&p#0vvM8)aGYSdFmpOK|_NPrQ95M@f%m8I|4 zU4DZL{;JOlEI1Fe7Ym1=fq|TnfirkhRf+{oG7?u6&r)C>o-@jZRJ^MkHHs+ns>!K- z6T-?j6PM~q5e<+T51h#W9gz}eXpmt~J2v5$y9Gi>FzxcUBhJyF1$bb1Ay^|M2LbTa zqJ>4M4A#rf`zDe={QeMgPxSKOaM0sQb^4%N{C8q5(xXHmUt6K=2_>&?^ zC#q(W!psnf`QxVLWrbAIb9XZAnVCss++Ei4*`g8YY|~4kSbbqHzX+hE~q7 zsn@g2OWG9U9(8K{lfUiHQGHWpbfh7x=olte0TMkjFQoP=iYAp@&GxjL9 zje-Lb;&|wWV!fbdGN3;y!Tyx7^kA42%>Exar zR)=B+y_rlaq_RsGL<<<3O__ALBNK0P{U&6J%^U))|2u+%#;XmDq%Ij!ZE&SBx>jC3 z76}I4SdSthgW7w1biLv4ara|m;K~Q0w>bLf4^(vkJ}3q%oNG7*iIOQTEM|eSyGZRT zpUu>++ilXzM95m;H1-tXUD8n0cZOf&X5cq`lE@T4uKG1F_D7Eo6Bu7^hBP#^#YJ~D7{FDYxLQ}FgK$mN!o{$mOAp|iEK*vGChQ*&5!JBIaf-@hfNB{_0 z1u#x8QU-f!D?*_TG9F8SWAW7DSYW`=gQG~}SVoHhSJ|bPZG2`p3bMrS*VtfOubgGb z^iSJxizC2Swk)Ufc#>bR6pa`RR6eJDH!_c71X2Jlu5>wyGR`3i1!ylJ$|M1rwN#5e zvDVyTRaAI?R5^&DNl4VD;<0Z^j|72EOqrQ%QRj>jg(QjfFzPWdc>=TA{5v)z@KmEy znDgF)4_}Y^Ii~z_DvsL_~zb>EwboFU$l@xWnLVs?aR;>P85MKc$EE~?XqlrXu2-SuC>#UAh z{Jo3-`hdR+fu?#@U{;O$LdcbHyu%a|?CO7gE1l(oNU6XtYPslkW|z$V5zj%1iX^H>b#UGB3U_O9ja(1x>aE1s z!4u-BqQojse6H8~I#ZUfhYCe=`!2PvvtxKU*U4C)($XQBLu-qwQe#?hSm}5&O#U)D z*uBAsQ&Gzb{F1RGMh#AZ2{@M@yyzqdIN-@z@ZuyqfapCb9EOXzzkxHTqidLrx@;D& zexJn-1n{=uMNl{g&I$RBm;!PTe^GW};72%+q5tE1;SSWqWv={TAqx+M!@&o(XqW=S z8$pdQAXK&b^YX!){-eqk&_1COc+?vB|8T5?iCw4Rv2W9o4`E}7gdss;3j;3zXb%8; zLZ+no6vndGzP1oeHn{8`!gX9pR;t3|X$pC1K-~c)e1tR?PWYx3p zI-lS5?pJyLQ3_~vnT$$Nq2**@XnG+mH8LRYM%cs|m&IM{^nLzTfe&%W1^ka9uJv)@ z>p>Sd!Fv(O{CTsuTun0w)x!U^9T5-(ELx_Yy-wtQ;)B@hJ|BrGsI4?i3%55v+$<#R zjvIg6?wuDEBhD<~d|jB^V+XVvC`KS1q{Kk{{IjEsUR9$=$Si@Bl>rk3F|L$L9)ZVR zOQu$*q=J6Gw)dYL2sZxzlLNi%z!N6Zk1}d~Heo}wk(R@_Ic}QW+%qlNc@wA)ZJ_gbE3dK>^+*pplC3 zmngw}EVN!Z1P!!M35Et9c7_h9iwR*xPpBy6Qe1a&8)G?B%H>Q^ihHZBAbeCiG(RA* zu?FE)sY}3C+|*F+uTnRV^oVJ${EOf--wI3m8yrR(z$psQEDeZmZDa@l3u{}Fg}&om zPR3DYdBp7``qH-~9hzcu-0cA%9EpIF85x zek3dnr_XqgGc%hZ?=LKmvzfp_59Yz^X6$H#|M;21$j96Khdag6i{f!~&otr&x)c4D zU;k`8uv?=gfv7chS9siK71w>8N!G7tMW6rCM{j!@;2RpYv0W1xX+2T|bm69`<4GilcLs7h_gWH>vU~GP&)PQ!aD54#OPwYM#vD6fl)F%-A&H z-@6z|KJbsPKW>$kYsMbE8&Z9z`Jl4Q%XAE8=pggWI))4``3^^pU6!jqyqB*U`L_fy z6uJrhfuul313n>a;fRuwq=BKs+CWw6D(P|gxb;Z+ax;Ze>FJW)HlD^Pm{OV9=})Odx>w0Ps5tRN5{a3>2Ipb;rxRfCZ=buW#V-8t)(G z&rGxh6xuSAeZ>J(b}idLjA}L)aTKvM82Z8g%T-eTYopjs3IPVFt8VGi>nK*MBT{~Dn^Mw$5u6jm~pN8L`nfx3(-TBf>sey))|MFBsq{vJmiKT2X zcyqq@YHpV$v4vLk_?>&WjqTsoHZHd3OI{WgS8i^wbXN5pjdo6i zyNpKu4ZdexKGW-3v5RCHD z=913*;aTr~=lo~8R^Rl)(>-w58+n-9eNeL74V_9W&t<;j$o%KG>*q{gk^dC2_xfb~ zeZ#3(tRKI}{ynhLx{Y(-M;lu0zi&wWP^>7UH5*c)T_&(1WD(_v3Q$r9jjV%=jYi9Z zae}}V=gdDgdF>$xtcav`}coK@PbfWQ<@*>L5asp^U)KA?|xZ#VpwO%&tYF*}~PdA`g2irb{c6%@Dyq|_Gm?kE39)Vod z&aCgFp^r9MAesgg%ewB~y%wLQUaWs_HF@t|iZ!XE65bzwiSxI)atgWS>-P#Zkzq3# zM8$PoSB}qf>xwFo1#O5W8h84sdm)hw@21P$ujPY(uekvqiM5Zq7CpF;bokp@hy8x& zeKB@FzHgi5F!+wSY3WhkXT2<8H}b9S7WdZ|;ClTo({*;=W?Z2Mn>v@~r+unUAKG@| zNK|i<^~^$)Pu`bP1;J1qHwT-{Fq&$%ks1@G#GEj|%f^Uc`=7O&C{jF;mH&|CVluVe z?pLNppnK<>rv~$ch1&{Q)1!vk_4!DE)PDn;#-+EXFsR#I|NMBXoF%LlDcDb+YY@T% z%B|m0%?nhjhR>$&^xKaJ(`_RbtA_D-i4gBL&ZIuC7h^r<40e#X{V(Mtcbh5Wi^GFim-oH@_#&I?y|EXCHB3q2AAV zNJye~aa|)C;=+xtAX_+i)2Z+wno=q^W6^mA%>V#jR>ZO@hG0w|pH1<96t$a{EpKD4 zw#s+++JY|J!c1e6AFUKTDh zWXmwf>?pp`gMo958qtH8CQgHzx~y2h!=mUxot%CfGBmshnmjOV04WAQA|9;2ER+iX zs!b=tj45*|ZQoY78SgYN7|D@9t;uy4W)vsY?(R+e@`5>7CZn%=>DVKzPWvB zPNg-0v4W6~fx6LFu_eMu{nP{~npTDU5&{+~>bzb~G?C?7Pwo;eXJ#(ft?AZwn_cz| z)tS;*c;pzr)q_CRQ4~ix;{Qkh6oVK;VMa-o-%d*rQf*#B4;@Z7XZ1={L7c)7OaP9B zKvR@rW^ODN5psDjD#Q5UCnUuV(98;KXqYNUvUqJgFpS`828o`4KSm&6sr2%!U2hT%# zgF)zR@S`c|_KUvyj|t(XE`P1fRYw2b@NjJF&ea2U2%?E}^T5Ufe^=xdFe2U-`A-0U z+}+Io<8&FQxKd|t2Uc3}wXbQ{6Be<00@4&TwuTI&AVAIjGcZ96r6#tnebp$(i`lp` zTKVqOIP@w`6L^50nE1O$Q(fU9Y;uWd9leu5j?gyO; z6pyHvz&4Hb|9A;f_q=D>9YA$yUy4J6bkU>SPwe~$SbU_k%k}-)jpf*v2`mkK_N{NV z%b7qg$<~ql@3auv<0Iy_=y(YiUrpz)wK9pGhwTn`VMW&cL0X%QYtxaOO`l`~vlsa%&M4?6V zSgGaj`(=4(q97cT>vNIMP2~Tn^l?C;*KKt%NxkjkxSk2Fj#5d;xASiO^-#37>gzYL zRv+5HP9iUJLu;R|WE+L>{?jUYo4FwQBAs{ zv?>(OVUvz2H|%Qlm2=sc+f%2 zFs6S}YRgiYRza-?=y1i<>53f0(r8j~{#?oQk(wWF(^l@ped8oF;{)d?^KBWFtoajq zvpcJ%>)U+MpW)92bsW~hQJJ&z6sA(=@~7}L&*t{hXN>vFaFB$})nq4Igb)_sU+uo4 z8h&OIjt=WNlkB<%uUulIZ;<=H4~2gmd#Y{vb1eUwiLPv_I@78OPlQQT#98TnHeq|p zxmNY5{zxge59!J(dstDJCd^3K(VUaPwh!3?iY+K6?riofDlGw1i~*(DQ5*ah z=?#??q--EacC=A{Zq3y6cb$7CMQVNXM^?KWt`rqR5CWiA53FJjz@KP{uk?dvHh$jl z|G+jdR(Y75!6k801b`tpBuzhFsZw;eWt~)q><1vNF`&lhh}FD}t$5 z<8i{pF9`3q$Sh-DUS(fg(SNye*GzL^i6?CkUR$wcQTJxj_xWe;&5_R3qq5IT8nMrq z`+`NS=zIUp?C8i$aSUNasmK6U6nbqu5P61kXjrUmuC-|i33=fE5^V1A=jvuGGSAL5 z!;^PV3oszo6eTsVz$Wt{!{>WQ&`}~ybK(%!2;qT|3S>CQ-R<9RN)j>(FjY|)*8$3K z3^p2yM3!=GLipjDA!xlj-^9VUl!k@pi|b03=lRF^C}M(`I6`T7YpT(fsg`)n@*Ih( z^v%qbdJ}v&tw@e;(vl%duwuuhe*mx_`?6vol)JS4i73_`KZ!hT6?rC8r>j3q zWcHITnXLHjAJ&Zz}ZZtS0X|7r<~hf>4{gD-!Kpqx3w(NJ1e(XP_YM-FdPUd z>Z_v)C6GFjf@JAmkg+{c?nU8^9DtFy)IZRb^;DMREWeI%tS)2Lu8l&>c7G{|ZeB!OI5p4J|VP=ygcG zF>m*EP^g+4Skyy4N7H&55R7yXO9(L%+Gw7?83{!+zmklu3+G2Ub3>C~d?mUn2Er0` zupr=Ib{kPHSPJt4r#AMNP#Ej9$dXAUaq(ac`DOm|f+4x(4TZ8634qN%7suXH0fi)6 zV1WrgWGj1_OMcM`3VI21z=Hs%h3+c}v)S&Rw-UaOfO{Deh<#Z3OkU;(SZ;Xt>KTiwxj4%I~fwutzH-kR;n}&wP z%>iS!AJYqt2As@jWp6eEIE%aoXfHrX*;Ux%^Lt?T{w$SSs|4Y2@(&hD$9x7wnpdiC zhvnmP&H7!-yLsP{&u7ZzpI|(Fe(yT|JXSBn8xaRJ-s~klvPmvq(-+dq?FYZEke_=N zhyE1X9+L927D-%7D{SbpA)jpSKoQ7wt7wV2%0#N2w%| z4*Evv0`Q;akrO)P$Qs{$6uZ;pTZxlLrv~6=-X2xEvFli~&5S{n{=0S5{T9UDh9A9k z!@T zFW6?OW|{|_{lEsdd*{BHQ_*09qijMoU-vHI(W|s`H#HJ$lw%kRLW&h2c5!!B2_==; z1EtJ1`Y!21OEB0ALwad6uR9fB_70Yb)D*r?q6z~xW(HwdiZQeDPa0rcm4}n=_g|3cJdQQZIQ1z&ct)8Y<0YIDh{z%F@*%G|s*cf6*kKsnt1E<0!jp zfkDB#6&rmkSb4)F#_1s#3gIx3)lO7O68zyJ)v zPO$VKk}I&SXbxbE3V9EK{bYB~9iTd1@pbo~pp1uOSukeYT%_b_YLR4(XR^A03_Vei zfefY9FlKds{MR?on9~Decva{_?QPU0i=+TgLrvq6>~CkB-Az|8E5#G+;^LxlG)KWi zbZx*30gJi8{!R!Z)HS`~Fsmrc{qS`rMF#FEvwz6*hMJ(DV1+h$Z5{bL0SEi0o}SyX z`|>|s>_{+ZatVwdV9+4Yq&Te$koUhrKeIY6)1}feLQ>4h9=GN?N*JLw>Oi+NL2*e{ zi~@c;6PoU`Joqw{hA$+>JUZuWS9h8w?#U#l3JrC7r%8J)rnOj4=x`o=wnzUG79kUS zq;)485ZxAGxFg3vnTn6*arfP$8T~|vYE<9{m^M^?(QhQYRnBjg`3RP)9zc%_HV3B} zW4x}!qC8563kpYrHM^R8W^FoUO;HSjf@v3GL*giIm=(epEA5bYB0CcvuJ*yFj{^*( zNYT&3G0=nYpn*01oBKe~(672>;j)}Ji@pOJHd4|scFZ#Cp3Y3IVh2KQke41%HkZC0 zKW3j+WH4=Iimm`NL|q-K?0e6|M=jxh#D8@ad=$Iym?(74W|q$`(p?8CMnmQI^yf#5 z7VjGt>5t2gzm4cjf*j@D9e`>I9-)h>eMYBNR`6ztdd`pT@tt?Cz0tXT1!(`(uKBO= zw_#$hgZyIyU%D)WTR|ey26lrSw0$Y>2Z>RCQplURmHJ{P9e+%@u4jT<`$Rj9&{MV2 zgAV00ZDqMLt+gC`P33&d@LQ+xfuc0clfsdMS63E}2nsb)57eVrXqHE!o0Jfm2T={f z;agUnIiZP50wY2SZ7r@iGk!pZKE$$17RZgcfDDXrJ|c{pkVN2}AXeK1l|75%AiK0F z2sDhto4wD4XZ|HQe>@uNbWqtU`NF?-Mn)CZOe1#6!?Q zWATE*(LBe?@*?VDt#qvm`-0<*J8#%|tePk8kz54lH~Pw9!0Ax~CYou{q9sP|t)wCz zipaIa)+?ZqmYE6IKM<{Cpz@mdd@H_&Vt^4!XO&SOj>z9~{9s|B;v@f#{h)13wQvaf zpxu|j=%CV8W66}ZrtH9&j;~?1VrC5c!Axu())DlXZ@o&43=@tD1*C*@NGiOOMLGOk zz)+}859`6A#n=?$K1*!Pt|NWutR6G&=*rGTfhPxo$^w-XYZeB!n8)$pca{vCRXVtu zK$19=qCi&R*+Rpw6L5FE5ej1x%xQR7IP&4A@cmzXZqVgkROyxCwEIP2z%Og>uwp1>)@PC3eBIVzxZbe04i z=o;_u7OM`JgVv|JIe->qUCG;)*c+hFone7goOM4q!J;02%b%1aE%m@^oiTg-nhr)? zt2<=GMgGTtP~fbUGG0~km}c*uN;!0I0trsIAkfu8QK+d*5ndHQvMEVa#I@I)nAjn7 zTq*3$OAaLCxE?H;X9SLK{m9f&!a))J3qwvz7MONtdl0r0idc9Ig#_;t4PEX@6@gG2 z)h<5;j>!w^Zi^wYA|j_8D*)%BM;F)@V<*`Pt|piyr(PfRlwK5k@fUn*uA?sMtZcPD zf{B+oQ^$q^%s7lmgbl>epx2JVmRmdrA7!{VHu^)3aE$LKx0b{hT+5P>@7iWrf9FUO zdE^BM`+zQq8kQ<4krFqW%qU8j7T$Ri^zZ+kYe`=Fj^ug}R5R}#hQyJEHpK9G@fR}X zSSV#H`zpflzqG?YGfF;%y|+Q^7gmGeHH5yOx8SXSVd458kFbmJ?H(ma!rLi6jVJ1#S+D5@k1h^N%$)}R?vNc%bOrP}Tu+99T-0G9uH=BxS&Wl&e1}T?_hR>t z@IdgYtL%y^{?vfVrLJ~@4qzLm-qFtgNzixL^fm5KGKcw3vA*ZQEKm-)yC|QTL*VUs zUfls8q5%;l$qp(c&zFQh>XYSlfgu9$NRhTOTG4No{v-uf+- zv%6sTI`p~o4>8c}GvX#8k@YF=nhjlT8Ru#|gz`4Ss{7X@3(-&IUqqzRr;pU*s z-;DfD+v1B{{xLX*N8`9mqu+A=%;-_Q-sFBclQV=kU*Ng<*zNPkBfwtdrE(PH=8j>3^NQQ^uxw{26_VD1BSi;yEZr8 z#A2^1A!BPz=Xpgok|n#>OHBnGOQ+3LvjsPHo?bA1n`?LHOS^hv=dMXf@v1My*O0J< z{vqB5{-2MYFr}hJsuN)a-o=tTo5&}T2-iW(I+Llr4NhUZvc=RSlFc0D+cWHOKTC>J zBmdpc^&$wYkrTRmimJ-IS_v903%@_izpdkaO0lanvpRz-se93-V>gStOR-irM`z4A z_Z{p8xm0LM`Kk3Q_DJ}uZ?mn%JKDbmG9^8J9q1YEsF%-pRwn$(*7Xml=49^nmoN8B zZWn6g(#zb9q=_k$Nlm<(ZtFy~w=L&DduMA`t=(NaX^uunRejO*(~e1%oT!Bj+Ituw z5|nw&TQETh-8k<@WbG8PZTB@r9%-g@+A@_so-cc7?`0JIIo_;h8UuwK#xm zph7tVoufioIwRG70G$Cwh9kCS?qm#hhR1cKpVZ}Rw~UR@^ykjy=-N%N+b(ujQ3u&^ zv{#k!gWm#BZ(64bcMl0D zmdjb(vj-mx5h)4tlWofnTJT1E^!omc1B8qFc{q1)=8OL-FZ56WL@;88Y;0+VC5!K& zsZg&ioUdnq^|wE6W~$K=U!KXq$~n3x5Ur}$Vc_wmQH}#Pi^Y=#X5b8>q@_N3no%QK zqcj186s?yek}GTvm`@bfamYh7ieM-kegq?hUd-=un*wQG9I9jJI9W?*tW3HGfkwZN`Ip4AOXHhdQn~HCV0GbWH7|g zdct)WkEl>8sPO<208}}gZf1Lf<)Kj~6E2L`2iWNPUFVF4e%zONY>nMjuE%5SnQ1-T zRi4wX&(EbH=;?ktu^LhVQb-(@Z}4Jm^fbQKl4^q;t9rauIyI3dGbs2 z>?>s0V^`76&-6X&oH}bRR%EhpNJ@FGVFB;53P#%|v~pHh)4Q^9bOEnvPGUZku~9d1 zsdRDP-r9{jYlsp*DvQ&a(TcxmZiwGz;mlmfZo>qDn2>uAYhkE@ejYfoKfwC3u02hW zF|8D9AYyFHwFGjpuOCaiFZ1qk7Bd6txfxe!cM6NWba67)nlemC84#MWTwcqw-znuxlxUsQ#)& zvmk-ehQpp_MX{=GLBpo%hdyp|&{FOR{duL2X%(%Oe=W@Ml7qiZ3WI43@B>Y#gUH9E zy)InkwvG@tCi@?C^6;jDrN*yTOra}ni+|PM3UMo8lS)1dYbNq>X(dD~MF=__56C+B z!kRVQ8Kx7sWvJe&a8e=VFF1O%hdp>T^SvL#nt6L&YMl(k#7&0W=1yrFJ+4 z>c+bEEq}C;T{8{)i-W92hGX$3r%zMWR9}>@rENBIRcX@Gi}olG9ym|Kl-@R}^ftVD z{>E4U=mRb02y-)kV%n3c*zbcDEcTEizS`(iG&S!F-r(8TF+6A0UbQshddQ~I^;U^Z zdM9BGmaG0r-E=Twx#OC%dKzF1YJEgKSJ2}x(t++|+aQbF9jXm`J{^|e(-+Jdv0e#x z(KI=%NGLq*x2jOTYrwOIacP~MF&fu&P6{nJVzhJLD8vBr1^Ym}s}~3JY^tPlbk<-0 zk#K{!MsB8lTAcfR_=xw(Y z&``*F0qlf_vyn^_7}_UgMsK}rNH@!zDOx^hoa0TCNzC^_ek~Z$kj0)Gji@&R?Ze$H z!$3KsFrZoT))rDI9s~x!hfrNYV!-g%!_fmNlQctOZ8`JsRuJqwE4Kx71R(;8o9?&O zkO?ady1=N0GyQ8m_xs{pj(g62?Kn<6A`Niy{c+uW@?P{UDPI-_)qejsToE?SsO#Gy zZN)C^VZZxc0N;Y_X42wuF>19FRO9~+-YI*ZXRV0Z=OSbU61)&g6w*3Re|%(NUS&5h z>q!pZGQGs4SM$%~aod}D!NE_v`_=97VY|HKs|?(Dy0GjRNaNG|eEBn~BYjcIx~q@Y zlCc^2HjMqRZ@xp{=js%8+1I_~e&yw@Y*RtOVU3OC+52%#B-nLM)VHYL&?c2h19&Y& zUF!UP6roDeLBAZXJ&(Ujlly>i`MLjS{EE1 zKXd!fFk-^m8)BU|u93aaOxrlscg=w)9k$>nOwSI|!5Ukx@nmMD?Uv#v~eZle6 zBYwMurqp5|vhE6D_vB20BP%+BAwy2wDoW{Hz)X3fFy^1Ekf$9wN;bYJFA~e*MJzEF@>4Gb} zy7zCZQC8=JuVS7_LWH0K}Y-KuH9Z(|P5E;DP7!y@S1?q}&}&O2R%q5J~mBa5Fy z6NN1`s=|D;QC*tCEjOpVAqsH}y6Xns?;W`T9WNoCNeY+OYjK%NNEt8)`PefwdocI$ zq*R2VU{tiuthX39s)i1*Vhw_#9|Wf?92Obg<6BoM-sU5Oe?Q{M_rTifc6v}5Soabp zaTQ5gH4#qRj6t|>mrr&y^DNrCIqN0; z9anADiAZidB4F=utORW%!%E>&IF8ds1A}Xe^DT7*#{@OxTBN|aEHbt;7At;2PTfP) zqw^}|i6q8h$}o^m+>RGo-gb=FzWdu|<)BG)Wu;0dDz~%2^VOiX-_I57f&q_w@gi{% ze_I1Nr_|SNHK3yzBomLzI0+6x$>#W{Nb$vG)MLxEIw97eGlYXVHIXMw~S*22vT`le;F zPDMxnkxzKT6rvoun79BxkTW}bZ|NV1IIG^VnvZqaDwzge%?}F(IV-m7zcOpFn}zwh zUMVdADkGX*#v!jX^(cw!DI2qk6H4!n4ws*C{K#B+f%@Ib+1>^2R@HnS<^6w9_pdT_ zTyZ^?B4^bAI#dQP`wf}7D|NR&mVdqNQgi*3`OzFF{5ruL{LB4is(Ze47~M!gdz}I| zoc)Lg>Bkt7DM}KO9p0R34{a;~hShi!z3zcz8tHEN%pryG@14N2 z9BCSQ*3*Y{t7oi;iH7Dud@|K*As`A6?hK~0i(Q=Bg)U})MW%pYB@XN8TfBv$BFIdanb~R zPSXz!T(&E(lNR(BQngqBlT!~klg|MvHVi@vntP9#DKzeacy3Ea3Hxa-{X)dMWu%^) z@itXolmIp_DKhGQtaxA_sIrWrgznVu?g!9-!%~H{K+$?BryphR|LmlKU-s7RqzL_< zE}jaC+5gSEbJXGsx)w4Q#h}7w!?11}NxkGmg%1eb7tq{%7AdSSSj@X4h02i|7Qw>) zdoMpPMQBUTG7S0)(q~cHBZWeXN{#ePQ2Aj4ujPMM%5hNu=L5+*?vG?b(V z#J%MPLs#i=4&YeSg#kEOeZS(^;5vje(+wTPXtW0 zHu9|ae;oy$J<>06B}UC7p=op&iB^u?lG6Qbp0&pd~B^-yo>>%aPp|u zzl%Vx*V|0Qf4B?}0Fnl21}Pf!q9vc{_*D#)W#-LeazSS=5e;PN8xB679avPFQU=-F z;!riEQ%Fnm7=@<(1bO$28LI#?4upOX^^Ef8Gq+B9{(h{z;zusaET|NOr<;c`=wfZM zibudiwxxo+BRC_KNQ)7W_6Z+u&OVhDB|F{UI6x zfl6Zh6hXMtLk#Qy0~4oU+jJ$-?^)xJhPUH`FtRjA85!hN6x>NsNm5i;XeCixl~hV;eFHpR-=8{S<^xvt-hWH)!k|2?TJ*(7tE0sgoHE%-Q0HPeOFsn zx~1-3OprfknN#(B5+bW~a_@+IE=J{%|26()Y(^fPP!)+irl#hUaSJws0?YMN;!qr& zI)-AX_l>A^HTn$!YO5G;gy&i9n$}|sE2P9_e4n*l2W62#Kfe8%(Giv?gF?hg0Y;)* zX#e{+_U{oyaNu6|h20pwKjShi&_+r44U&GWDJ83DNQ|KCl2Ad2_!SBaU^+n{3SNX6 z=e1j)jmfxo2_@OxWIF_mx=+{fh74c^Hi{jfL-BNgqkM73) zBc0dO+Lc%-(de-MEhmwKAt0(L4r-h%J`(oC>;5))wUD{T{bc@VyNWi$bVF1SR6 zA+o2V6e)DE1yWJ)b|k{#gp;c;z3dlcl2_`a(^ZV|Rq|KML1GYHg;9~-|@oe4jWh+BP5U7m~8j#Lgs zePi72B{QQkAtmhDJUuibBcEqHa@mQ2kCi9Fk7e!*86f_96NDUk4&tJ;WC&EgZK|gW zY|2j)#gxLuN$w|Gb|U?R_{qMIPm>obPMV+Z2rlhwF=~Ql!HeI*oR+S>cUYk%uQuob zCMrl3kB&yniuY@zMT{6W7ak8bXcK_+zgk9o(%RX)Vdkmq*a-uMfOLo~6zqZx!;|R` zD5{VSA4;*!COfPdrL}r4rWYrQxm!q=hsH_ARa0RsJq#k{-=NoU;P!pU2$eaPVWK}K z++P#DxGZ>&n!Fj=8L{XW8s6p3tG${js;NI&#K|DsRw7u?(AY%CcfmJVVEZXQ#j*~4 z)~=fVyHyXiMdAZsM+NbYp_(J$2d~wlM7wf^>&S(ZYY0CL7&qG4{NLd~`1QWA7S8%h z{ImF%%>^QPrD`@xb3Sc%`+XPt!w>*!m;Wjx9`#rJ30TB``qtfy_P)J(1n56L<-d94 zg6(}D?myl;fU=}GJrJzwbT|$OsAXqD9v8b~GG28;?QxPUK348t>r-be*L5FMPQ%*vYcD`zyq0^fj%WUCv?D$G_Mns(v?kZ1*h| zGoabwnA9ZGvK# z=_z5nr;1q+`WGs!lDJ;)rj()utrt5|C4Td=U+pwUTWU=^=OQtgPv(1UFpmbRapVuF zmsn9Gs_3^3vnyrXQ^EfQ&r*B&zwW{?y36E3i4={fCSwvoE(i|?K^?lhSIWJ#g8wTJ zls4ZcvXal1ES@f`CI|t3ZK)PN=U*rZ3C3oSP+-Z4>t>QhkTHJz3=by3NDE0AnbDcJ z0vwL9A*i>~KA+YS(In;&%dAV}4+!1R%0Q44u})(LD5KDxnprW}2&)0{PZE8SQJ*WR zK$a*9BV4Z!xQ~%9iE5mCdRr)`-Vi6 z=+|`SO83~u5acf17e|B3160jnH%N}{w^h@e4`81QTGrW{$Wow02F`P)>bT_2Ijjkl ziVb6_43BU)mE6c%xx}`DhLy8 zL~aCHN=UkAMWLEpO70=L~h2O5Cft1giWORYIY)keYfCQ-!g0 z1pni72(dlGh97b)W(kspKacL`iSFn9>f5T0IXv*6Z*Y9S_?W<6?1!$9?5djHsy5(J z?{3Au61cnU3!4f2tBmUG-D$`DuCRvsR2m*nD1iclCryFlxn~>ebTCh{C!2TOMm{m+ z)C~?x6Xgj#(~;q*vw+Ck1S=-sj4|-cI}%5RyeivTjk~l4=M?ENIPD8B(#mxW%s}>}})sv;*w6 z1aMvr)tx1vHx)?k?{8Lh_q+3W&oC4%K3i0TA@vzuE{UL%^9#G68a0*L!>&4L{Dm12bJ#_piTSN6)Yi&W{Vi z$Nn!EACm0DM;6GUAbacEYRp~mHnZzt=N9%%`X4($!0Jex@BIn500dckyh8&EBpWD*&CuO(UE&32r$8&_|Spt#!dDTeNR zml&xus(?h4u^8Y|b{7|lbYi?I!M#}W8ge5(dtbk=6FvmQn+V*m=AJs9ecM}VVo`fM zGKt{p?f%&YRgc-ohcmj)+{ah^WsMe!1;T=BnJ=nJpcbJkQ&4W#Z=P=!dpdkKA@2- zxpJ^aE(IX>jK8*gIOCwDH@?FsIJ-IIr`VQXUc3^w8rgHa$^FN$;mS>w?H`7r>~O<< zAqI+PL*=N`uWx8i;qFyFig?2(rehpNd8{x>yAcrMBBV9d_Bk38{4`~q$gm@~8wczZ zolSWC82sEmmU0xpjO7aYKJD3PSjf$37=}yMOJ+PdC_NSE-H50m5i{q9TLr zJn?ys+y|?x3&V$S4POXC)3SfA#nyj*G@c7QP7G0V3!F$DYkllY3CP<8$o%$)Y7J#_ ziVe9b^8s}i8lxeyCo4@``$l-7)uy&vhLo*L*X4nb+nI{lnNSKF!5!sY0~V3>wkP} z9f;t`&E7G>Q18Nz$*4pP#gHk`0>f=jl|oX36i$_H<0U#u0{$0MzTbw$b?G> zBE>=@jBuEK>S>d--*aZ*ZsNs6`qGjQb$uIEH;roa6XlrZi*OCrAzDp+AF0|#S3*QT z4~%+K0D;lA1D%Q_A_DlY?mPVUzkMUJ8P@C8iEPuYat(}7WOMsPmr4GNAckN6KbEd3 zFtTXb#Q0UPmoTE{z zMTJH&)ZU6Z82EIW17Uwdqn`rtR$?5mY7P`@qPPGD8=ah7+Jn&^Q8ySn9Mwe4vdLPOMDt%m{#(?og9WPNrJZ=coqmqzi$}$x$LGa_xYlrL70>Jj#-;mF>*!@)E@uM`f2XGMa zPt}m&s;lW`B)l~`{k-|lUk|?rt94_1==q$F8unSo+ly@YKi^LWo6hL5rWuwk6K(Ft z@4!Y=lw^0VSpNC$VhMQjjP&T`zjuFqtxzeM?2Vt$W@YOJRO@GD_j%3pgIg)9Y6HR-uUw#F`(ISDJjnZksPoEeSi! zCoFVm4van*;srl{uaA2mNFCY!E5qYgkq^>v^fl!3=8%x$@vRNrEBAk`=D9ncjD5{q z3*Gpx*!kc^3buXe2*_mU&FaH`p+V<6iBMPRx9d^`C9OYh&KV7cSLYjN1OSp3VDlX( zF!2nvK{cJ^z&G|8UUfyZqKUc>Rq=$)SNp%Colo!lPz7 zxcrS4Bet*g(tB))-Y!h}9U*C=0ZVMBSpr~GHv!0B@9QK`}8*3b?+Pam+*6w3w3ohqCX}vQKeA}mllKG z_G=L>lJ)Iwg87>Yqm=OGIkgZozLX|HoFbOsU`GCjib5FUIO~UL)*+Ji64Ul4TX!R`ks;Q~~B@KDHKrFEVEzgVwcNd}~5fma((#6uPA0TNCRNPs=F* zLZ$aPCd_5!W~Z|nf-J0_T>S57J42G@c!hMCEV6UHFLS-1sjVFJtYq224ou>4D*+1R z>+^THW7hDx5Y3A(Qgvs~C3R<-f2AG8NMjWJ`*0~{hePVDjQfpiv>NV??o#5Eq;FKG z1iGS!VF|&>Wrw9@|J`8vQP{zUSp&4?0h13f4Xokhp9ztz)?PimON)DZvG^N1-3F<5 zd>angD9On;3nes>uZqp_AnH^|+Sr;A?H zUA(E2%HunhV>@waEN5MbK~4V}d8O1b1r0)6RI!6|MHdm-&GCmDxg*UE0~UtsG^^xl zZq?33PfpGz^B-B3-wv-r5{iaxO$xFqx|*UNJXsK6xWqxOQE3`L;u!lV91USoMTYv@ z^ZuBX-F^JEQFLOetUscizJNv2w+T*!Nf-Ix?X;Lv&eVy$hAnIMM}Q z*G&mOu__S}=}5xl;qJU5X_C|eJ(dmA2x!os3>XBj15b&j8$q_Fl{g0*NsjdB~Mf-_E+r5{hvCV?D&7!f@-RtMzd=>LnBri9DX*Jif zI=U^tpm%o9WMOByv)fYJx`VDhXDyQ8KxYnWdP`+pmRh>G3gN0=vyD>E^Th!@$4VgV z@Z_|CtBaRvld+^eXED>p-BYr{ggW`z7Dv%`qjX0LXlwmvOZ}@_U+J*tx;R-es50sZ zQjwg~klhX>hGJ+)N)#O$nxT243d=mvS^6nW@4&x5g5k$kbB>FyV#2K_q==~Z4Z-|J z8xd6VSn%+|Kgx)X%$F$v~#Zkn^cn=%e5379IVNXta4#rZR9^(j~<1314um zoRJ6=g5_{08A(vv-C+N@OoQ*sJdW3%T~J4dD}btK!SO*F4L=A(8xjB)y$Sl@5cu4d z)@C1F!M4d>O>ZFA?-1EDL78r-_UHOlk|FX$QAID_S{pa2C}Gkwj9cM-aXt$a^qm+m zV#8yJvf60?_nAO}OtD#aZylVTS#H-83fdPmaEziw00QAhVxT+wK+GmUYVq20|HR=3 zX5u_5A%{ya5GpBUapS~@3Yf$M0XD73u2jrYXv=sDiWiZ=NzHlLzs+|`CNC1ax|Q-m zLTg#d{0MF+iI2>Cn`AdMqDvbnk+o3MUseyK+5<(TSsDhE`)7WooUrXdb_eWZ0|J{u zKA$R<rCdM)tCkb{-11D$^IomdhmO-O}vg7 zx1kxVS`lQ@ou5J!ZQuuS#aSk>DO0t{#}$A9y&z%l45oN;m^`M+wX4tMXQ{Lk_8kq1 zPq@Rh+wK+a-Njj($0E{H#z~^o2br8kUEhAqlvsjm{Vn*pqfYOnMdEj?S^@_7uUoDt zP$d%M+3uo~-zja>^915fi^pLFj!oJbq_+QFkBm@~WGo~cCs@sah&O`fEFuu@6sm}m z%HX;j7Rf&5-0x%bqgM8woml-ur)_1Jc}JK3m&x98EpR%IE63iv0)CbiwjTEBch&QK zv1O?i84O*e>Clf^E0i(y4Mf^*AV~nyt44RHgVp^m;lysIPm@pd*vGBiA>EI|+%xWS z;3&wj{VNmq*+L2&ET>S%|Jvs9f%*~t8XRQfIUIY&pF_xpWezOq<^?!Quv`KA;`vH( zGSvSz0cI$=vk0x{lrMA<|INSilNu zxmf$)cWRx9R$ASx6JaJFZ`q?*G^SEAxvDzPZ?4i%he|+In5LTijB%7Hv;G!cd19b~ zVm=>ME@ftpUl%_pI6X;&;PDZF)X*eaEA}xjXjxhgS+e*xW3F*s{8Q4u4LOjuSD*%j za`C|><3K!hl$7nN<9(#*qU_&YBx749`F$g_&=J;ox6=1D^Q#V|74z6$tixFyr`j&S zZ)*l7eOW-QPEW&}@XD-vOXr`4zlq9dRBE7DYlzF`WP15fd#h^9i5U7*X>l%93XNiF z3&KG{md4j@tVi_5a(d^i= zSa(j`ccDkI$%N%>Y~(MP()eY7gUA-W^gz2S+M1+DA?-_>;NX?Hr+wa?&g_O3L1MDTmCR%ptXC+oFEw3Dk z;RhpR_DB#gRwm1t;2<=mWJEq6T)8dhKBf-Cf|F=IP`pYIHO>B4J11E&bqR?t`u-==$A)nNtw8J`H%!k+`Irk3nO zV2sOL_66_GSH6$Q+&16sECKi8Zr`*l=mx%jfN41HXuGt>C)2|UeaV0`*L-|%eRMO^ zozSz;nXjH_(aSU3L3rg);39{~?KgaG;7=y*#hcN87e`KrhyxxQ5$nfd+ndy5!n?j- z)(>>EB|451_h(pYI}6k@PU{pE*Y#+N;82hqXC&9()z<22tZaO{IZuX3RtGXmO9p61 z!6RJVp#&3B)fV~ZG9k}yMN47m4iDzoWkTsL&FKN*dY@8nv)wr*QC*)0WxW&Qs08R((oPl?nRk_hZ^AthYxR}fFu z5r}PeHho+Ubn^UWWcFv4y{rywPOAp(Q2&&C6d8TJ8@%^bE{8A)*P0|3TyBrEZ}vvsVb2o^`@yMm{ZBt86GIhu*xo%BDzT}b`DcIi<$kma zdNg|bw|O7wJ>GCu%+QjI>H9u+4HaX($Fv$E()2PBR2qI&3HtE8YY6DSL=R>>wDBF( zubGxKypLI5O9zOVD}8HoX~u8YiCeEU@snHNZ>r^7T;+S~sI{F?4L#uTZ_*GcLnFJn z>2hvjmqpZ$p^t?F;1TKPMhS1Wt?mOZuvgV`2s-c;2s{BZ())Aj>C$#nh#fk-!8Meu z9Ubu{7+CvjBEpaoLGb*Z?*GVk5DCP|B*Q?FOZ(JFzzG|rryDo8XXM~Fn=sRk>xkRc zl=9bToG1te+QWSZXo|#9wC2RnkTm`?uMjr2PQnXI@oB3D3c?~#;C&0~nuD4GRkMu; zBGLa>Gf;ztuhs2Hd~VO^o?PoqJhtG-1<+{8Xzs{IP~mCBf?zOQ$p2Opi9@DX;9MAl z8na34YKyO#e3ut9p8}wGlabPNRs-l+O9Kr` z)oEDVas;&nY7w~?|0XZ2snhV@4h6^mtnX>tX1w$-HZbc z@guzrJ=<}jZG-!6^|IZ;XHXy3fgsV)CyHiqpzNt6Cq z9ii<$D(&BTTaTNHw_oqDUT{699@jO*W3lvQFl-#~#n6TU2HRf!%R(kP-^T9MX?LT9 zU6(!qWTq%OA>%Q=&Fi^csY%Xzlo$C~F7$Mb+uD4piabnWog`utuQ%CF#KoHO8N_A) z09kiTed3{+GA{Hyw7cBgL(^>N9rtVW^Ru>}YPj$$$uwXI7~k@{14?a(yiqqZnz z*_fXQLik6ISF6I%2**9z)Yhov}w{Aqc7rB$vN z@@rgRI8us(6lP@%Y&EB(8d2}a=@;j=U45IUx^r%>?UZf=$&Tc?SzX{oq=t;yw2EQL z7@)))NwT%?X26m)?gg>RGbS?QriEjxA9sd`bMOKO@#><9sa!T*3CRR*g385FOB0RP zQ4TU*;7X8V&Rk^|@L%MB`Ln6hf%IY0E+kE8b4(+^&xD65Q#Vk1y8fr7&i+?fSP3ZV z^tFOf;Zg|=rSKpgs@8d)#!u^p-4t}*-NX}9QB>nqvB_tOJAE0YUAAlpai_BX4JZE0$tV^Pb{#NF?xp`Y@(CHf@nk~Iq+vj`L5D9#Q(jR0|ho@tB8zo zf94%Z(Nv+_M@D}L(;U)g8*RWhcBLQ}N-nXFnNdDn2xgm)=WW>zZZ2#x1+d?hcgZY% z$|REt{33n%`;#YzW(b4k&kA>}rC zWt0~LR?QBY2EtWs!eO%#GJw7}_Pxu{J+FHs0`Sk6mH`re>F$l{_>WbwZ{^R=wd)lD z)?8;@`FFCZcRcV3*pLW~vq;LjA!uH7F~4U`9d?=gDjr=h{tyeWT|&`s7QWSN=zGw_ zK}I(0O3kwfGd%o218qfp29tw?rznZLlLOO3m6+t<<3EB-Y;%@`2gEeZ9&lAljhQxWpVN%6hI*okI%#J4R*6i_w z39Pgfsvyd$CG>~>4fs{0dpWRucA5<23#*}fGxI;2p6XTM^NH&6K5N>pgU=*U+S0`nGp&hT;GQtA38;VqbrTg=*H= z)$xdf!v5;b*fUpJd1ivD%8H?IHGK%Pv-ic;FgP$;dUpgI_PhK7fII|pm z+cpSu>k(bjfaXXJ(r^ba^LP%WAi&mY?D}2h$|Quu?#(ZF*qBH3U)#x3wSoODzgx z&QJgsu=Dz>9%rC|?Yg6}V>c$i9U9XLbM)SAMZo1wH-A(C-Lx`l1A<+n0x{x$I-_f@ z$b=S{Tvlf06V1?|{UbIbpdwhA#feh6Tqw$8JmW0sGmROYwcfRP7sy%%Ol9(qO3qm+ z2UK9l2o7CFiKF$PUzJM|h(m|SC!+^S5up>51~;0!{&N&eqpsJ~LYXpRwL`QHSt#tz zNY6(5mHC|n`W`cyfb8=?Exj|^)GaxPU}=Z-q}K~j>^{BItMdLf7y=D2S_g2zXLU$W zcPqIQ3)^y{aDmjD0>xc#K#rK$4-P~tjP~Tk?h&|GEr&0E-!22lAgq_YYX8x0W#3up zdsj;@kJK(4YFi`>ekvsfBP+KUL1f_;8IY&DFL(cRd7QoYh~ z45WVe8A&86buILzeG-MgL>0Y8)3ivbK%@YBy!2^&h4hs->7$JY;ivc0n3n<=h;I?B%}@+a3% z0|+zYMS$_)M&dv)tU$7o#-ZD}fVnE1UA}c!IjeP0=>;~2c{XkPrZE@RyqF3~Oa?v0 zo%A8uN~82Y)G%^l%YXdozZwAMxR~^&7*rt)bS+8Py}9a{ZcZtADap?UPyruMbDY`{Ml=b=IS_kC!?NuT}F}>C^W;cX~XF1?mZc zY8;NEhdt^U{BI1r&Axa%u8zQ1n9G(R*_HTGp_0FO1MUxwwFLayVWYhEFM*mC6VG~G zY@ibZQS{_va_Ut#0Q#vbW!ji!V4gQzdLfjtB(R1f)e!2D+@J2}imHI`)IY31R+|i0 z4)TI&&^E98%%dvCsppJ+%lhS+ZMMw-;U`z$fX5GyH)o0Kg@m%l=e}jg)tBBlkCDnr z;*VnQhon}-#ccaV|ApE9xRt&)x@GH!p2xL~Mi3y;)iE4yaZX*|s&-y`P)S=~yYzU^ z4kYIa-SJ*Hq)WJ}R1vD5w$+=6jM0GAfOmbbmXgvdRm;$vGhJKqR&qI za4C_t+j?n;*Gx(=`_6{NSQzon^h|W?4ZgWp>vcE!TW-F$-RwH2s`7oBtDSpPk?o3n zoCfspg|+xW;R9urh1+8*4qoDvh}s5?DpNz7a^g2;$THJv%;||Lx3bZx)q;vqG<%=M zln#w0lu|amUQmOCsX~hr+E+0%tF~bsvPGzZZ~A|7!IoZ=jil7ENP>arf!9E^5;EU> zaQ!MC49L#sgRW!mxdQ5mtf$A8I%I*$Gtiu)SK=yeBg6OS?dVo^Zkuq$Z=Y+(X4a(> zLEy}~(EE)7A<@)usgGYn$kp|BANqUShjsg%NuRrmh=jZp$N0o@dJ*WJ2G+;3%IB&T z1x{2Nj&3xb^El$)p*r#1Zx@$mOZZD`>_C^b&UwDydKJ;C;rG=%P(KB9CRX%04-))1 zNVNs3yM$eRKKcj3xhHXI^L<=zuZ0#B{JU=kW7m&@l^V^y1w6qvcXg^cHng7Z&)!bB zF8v)Q-fp+P24!wf+#}@o2ET|s_`R_G0=bS6&l$_X?;@=Jpn+A{Qj`t&9uX--1EHy4i!Pt%b zRcN%Mb4bL+Hbrs~ji{;B5v)9Oh!f7rOz{;tU)2Gy_vSb!och}JW{o9VJ>+aF zZ(<8njIOcqgVGXso7*orChWo39IvAslsZtnEi0z9*La-$jjsb)DKj{8#-Ghhu2WAc z!Dq1ul;qWF2x?vribKPL2nWW<2gU__zLeagRHQF3>K)c*jvseYl{~k4 ztcDdenMB*1DfICtr8>dkxup7mzroVZ7JPY%TwZS;3rQQgEiDe&T|EyKkFV3hd_Ge5 zBnThbGdIDYYKLMLId6U-<=B0gHJyY{`C5Hg^>X_A)v&P&qA6zV15><+HXa^SBYAj6 zSc-+0h3HRoRv!L+yc}B^)_j;xgfV9STeQv2r?qR$>=EeukxHbmk{rN9ubi~O_b1g? zBu+8(?6y&~v1CBdTpo;8hJH zLg8JG5Va7%h*7-%;(6QJry*`Fr~cK+fh?~1S1u*r+vWXf-V+%6zW}tt3cPH}@@4pX z)63E_14TGtEq|FJlKv~gX~8;EuCOKVnw^Zz=fxyWpb$xPTpV-j4l)thFL}BBI4qeK zUtSwANr&uAN@Y$jSJNG6q#3;&PI}almQ$!oq8v_Xn>-IME708?W0F3jG*<~j#0@H> zw6X~2j=c@SZXYU+m>-2k6GDDXJSZZ8(_6u=Qp0mPVxXNcivN_4W_FW-p_sh7mDWd)-Od< z=GZ^;X4Ikc*CuISPOg9$?#UrsYt>V5^guub^r9sk?3rf*IGUFlFRFeNI_*`2yQ=>G zYi$6VwN{<#K9f}f7E46UHu2i6fK5!F$t}g)U!cg+CLurENls^e`s0L+-ir^3(?mL2 z&Od2OF8ZYD{|fz!?}rgz_ZMF-BrZX4mbz2W(XkidJ8)qA#HElA6=IW}TjqY=WIVPL50KPIGSOueI5y>5S zNrg#o!f(~K2kD>_7>9DuQHo&^f+o>*ry*c=2$Km{xH{2Ojz^xUS zH*;2)J%VUMP%xsbo9ovl_-ydFXbj`C07?Z(s3suT<@rw>s5{J{eA&#t|E~|w zn17g;=L}v|=dvgHaYD1Ie7hR>JfG+JfpBOnAt=1yx2fpe7J+Xup#OXyhkr1}_1=zv z*T}%{VSayGRapLGdF?H6Fp?Yx?8$!>IS2;PMoe|@?e!0dj0AutBcZBAXr(c7L*Q-??hu?-)qlpf=pp_BxKor=P*dTiw0zKh10eo-Jk8A3u7}XOfHNryMqs z7Ib>tS>ia@2rI>!(UH9$*DG5xmT$$I)*Jo$As9yX)Ye6|`+rSgQ*ol4 zPxN(sf4$d+FKYD~2|%~{zM2dBw;1FgfcBu)fRZmO+E@8?+ys0--%UW$U%t0IQ3wqN z(5_@Z_N`K;B)BaI24W?T|9{lVef7L#)jljwGgPKt#L@zHLEZ_xrX+jjoKn!&i!jb zm^veHBQxTdvE?Y-18r}?xOux^OC4!jnJ%{op1SVx1x6{SfEg!8391xkitLM0vm-Q9 z#e~4yfKvK^a$^ed1h#|;(152`c5a~hH_jIlw4I)fNa->eg>o^FUtg70hm|4fu@A=- zeg`|i>C|uVRJppVMmv;Guk4Cfc~o(Ty|DE46>KwtdOR(0%AqHLgmT;lX1hfo^PdIJ z=MGXUJsnWsaz$gQ$R-yUOZ!>|O;RGH<~|&(9v_Ao4^vLtkeW(4+yN563J|s37 z(C#L~l)0+!aK=@u>mK*?y4IedM$I{=Q`bXu{fG}|-2MD*0g0p>yuNi4jJH$(?)jsa zZoDBHhyQ#C+y+zDS(%O_VpczB;{G-oz2K&OmTSlX7eF&iDu-30eQH(#h3jOCsme_u zwU7XzPKRe%A4NtfKmDzW#|+j08ddsnWl~5sQhU2^YgJBj^izaRzp^QnIYXiqJj(WU z=u$ovMBXvnGoExp^-+2O8=D$EMc<<{;bTFBsk78iJ`G=gP8dO zBIJx!wejhS%WuujaXx^yF{_SY;w37K==$m_jMd&{zU+NbEmfzakbNVjL?H zg6#N~ZvGF7D9J={458E^1MFna=V-b1yf0G8^H;zi)WK#0NYf@aAxQr+QAPp8Q8ZN2#BN4sx zuke3N{i*FOZvemov;IWKbvJ@H%r zR#0h3^O?Nx8@CfOa~Cpw5;S=dFn;1Q8hCALXj?gDo7iI;Tyr@sHos0bfB)Mw(b2S~ z>4#a55I&BXOMAVTc;}Eu(@aQnz3+9$L_n?So3)s%9bechIhK}e_8fdj$S?U-0vH|f zB5{kj@DJ-wmK^Q!`)$WnHP<7c7>|n_k2@2$6&EL<5I5z`*yC%?!)5V<^NEk+=?6R2 zyxk90F7jq^qprN)!i3OT+7&2LJmREiPIVCF;!UNPLP>-EKEg^=t#+Z)rGjv39)}8= z5Fqx^WC#Tpf*uJd8RR>STM@iKi(YnNV@a?nZ!*xee3b$rix)}*M<{$1V|OTNiH|p+ECHE_n^U3elu0%M>}+^iat- zbu$QQZ5LSkbS8b^UIe{mKHG*XuJrN4*YQiOK>lU$clO;o>uphS4B-|+e_uctb%fr_ z_hKS~@7y@CRKTmm723%?`aZX@vf1+q4xRqp&nFxD`U}OE2J=LYr3B*$L^M`;7h14L za1R5zPHZ(PoO{HzJNPHff|}t|P>0tUf|W3s424SG0a=+IDE|$PbPTOW$~mMskPrmU zGj{v!xc~mr+j#t&W~6#SMwJ8OHT0Aq9}E&L44HB8KMv~$%X8*HaG)JBTPR;(`}OF9 z{cjiMA8ybi|BwU}K53kkLM~An`%$GlgMwi!V%FN=!1=~!FLQCWGDjBRJl+HacHGK6 zQi)ly!$+U}qHw}9XY`?O1Ka13cr@nFY+{8(yK^pEB*n~ud=_8NKGQK=fn5uVJ-28t zvD8rMVbEpb=n_nAWw(EiHB|#4tHszYxiy8IE&dS8cb~_P(v6x>MPv;{zjn`mLj$$C z4J$-EdVNj^P={ZtOZ4Ab{5}Q#U$ti|<;j0>uL0qGAQee}`l|I`H4%>edf&$ITLbi1 zBEP8&N}sl+Va(?=sQNFLvmkbXS`^S?{-Jiojh6Y(z`0qG*NC9AUdV)$^{#vk#U5p! zcbzBu&$(FLyf<0e^&1|a!tM?l17B-+KjY`))0_0fa%T5RwE2JbedFSO-kRu-;eh*4 z!Y#Ci@bttip>8vkFErZcL4B9=1Ff}Nru2eo53;>=rQwvUn4@Fy48_lRfp>w|hngRu zWp$Z`VN;s~i4$O0+?J;ZGym`dxH%92tEid5LRm>apN~iL!ft{*y!a4Nk%mK_5 z0<0vVajpZQBQ7iOV{p~eQ{Mg_eu*KbDSQ*B1u}ySY!_={By@ZQwgHI4Vkr@rhQ_{O z5&F;jU`(=onDoCwB|WZlHnp~!j$k*o)N(NJ%l-)M9~bL;IHvZYLl~X@+-8CN#?$nA zzhb)UNnbIqxf8&4bV9EbUG2(vfl|xM3L%~Esu;=oVz14UWj=S7<-!-eOMJ2IX8QcX zmh5$#sa3{zmlx`~XWq#FGJV-E#y89%2ym}yIF~*dzNF=dB7&H1;Go2qX8yJ~R1^ZA z9H8Hl=aa|S2n>&lNWh0cGD+BA0=xZgeg;-fjOVTw-Av++&4L0Io69N{rVc~9w2Z9_ z6qo(=bt2ugAuBsOm=l;AvTG!q7;d{@djhO%6fEtwTjJ+jh$*SJ=s!1nOH1qvS-4Xz z7ETF~zG!yV-ksG#t-enuA|w_v&gapa>gjRtnX@dT7U6X1^n4x2b))VPu(So-dZW4Z ziD@OX^Wds(FTob#B6-8j{GWugNiQ)VmroA<|Ua*AQ~he;;Lr`D!4G2#?K!o76El{hd2v?$r5v#Thz zDOx#sUr+r5+vKi>dQ}9uIxn7@j}L3Sh`L#>3RWAw(BS>FyG=GSsG z1*?H1uq`&A+6CT+R^~f}9p~5TGdZ<-?{zM3E>gNP3IW<3E)7lZF8zcrrz4dZY=X3v z73pQA+9wO|Rm2yyzFzz(W$NXE8Mz%SeD@9lg;%PFlE@mpS(SRZNY-wO!Y*IqAF1%P zTN9y*^oE46oHnlc28OFXB?bk@p#S0xbUaUvKahn=FqW=AY`^Ap^NQX+E6*g$@2NYu z1*nOAMFrK`&i|wsa%NNSR@-be|0_JfD+F%sS~mQQvjf*bK^UXJ$%5fHwa!|tl92E% zUwfg_yqmI__6W778|j5))+oKB(yTJhhp6u3qB%JMi+j8aEKJ{-dq-0-r19LHZ0QhXD92isCj*)+tzcg%`PQf7O6BY22ukJv7*I*81)c7 zCq%q=S@|zP^O#ceLb`3{L*){moRtp@6Me`a6gWT&p<)?Est(2IHytBHNXX%uo3iYj zHWTer!4fN}s3Z!wc_P@N3LL~7mXST?yv9!irV%DMIw#}nzbW{PO7#{|ef&sDyFt=t zV?9j>!|UbDnhw31YaTR5pR!l%e4pkrmFfo--eK!}Q zaXc<_Z0s<7n;vMRARN{|HaIN*IK1E^Yyk$ZYM!G;-8Y9;`hIa?-G53A2~8Pto6nY_ zyCjjj1<1on2+*{_!D{^?VMbfJn_fG_9$)!#_BuVgq>R$C^z*)$BChf}Dh8V0=Jp|j ztpOZ@bg?~Ltlh3;g{*6*&fI>tnseB8j8w9>|Mp>`hhu2{^*K~a87yR8cfR@dexCX& zqg%_QN1w;gIU*^fq^>1p*Mu&O>2Q72bDzmEIwHLi<8g28o@#FsNKxXxI4{R8yZh6v zwO9>C_Z~&bsRPEVGdx`ob)Wl#O)jgM`JW%qOHcChJkhRj0(Sperb(+N5_r?cl!5J0 zTG6e;nM@63*`LeA3)vK>Z*|(Ab7ztr+X4(?eJhR4(OO_zvgh);;%A7EZArDB=;(O145Jj6^<$1!}N>K@3CT0!*u& zxWN-|NEb~=j+^+?FBQ3RnnoqCyx&AeIUrNS01dz>3op4*1zO9m{X$UFY&-bX6H@9C?Q}?^VR{ ztbQk)G)FSD{Ei+y@&dkxscFh#e+_-;d~-_d)tWbkYQc%BnY;4`ZHIuXxy%e{c@Q-g zW4Q%k00d}VgeH^LEd|o@WlLG26_?{(MUbGnYB+dTtqt1HZo~+fktt?@xGG6e1n3?| zkPf~{7xXGDaZ*vo6MH(wyRtlCcZtDzUv^JP+VT|xp{|CMN`IK?HSr zF)A3EF#^QzSvG_pgmar*k{mzOC_QkR$KcbS+glf+@O%d_HGCZpq#B zGS5!O#^(l`7n1K!(orn}WH!Ioo3jlvw4U#>pbfcfi8c%K{MtVLWz7*aAM~1!Lx!w} zNc9xRC}67Txl`ur-uwG|sUm`Fba~bs!VTYv!v#D=9}WA6gHDT!;>|_t92V&>^Y31l z{lyaM7j-^wXFD2LwW6o6YabYVD^O?<;hZgWycny1)a4LdFYkpQN!Pz?v= zCt*P%x-d>PXj{VjxE1CG4`709-mc;#$%Glif#Enz2Z7}QL-scg3HhbZUkWVIPATW! zQ68yGEhypZq3pP?$mK+xB8a~m$S z#j^(bVgEEDollQ!#0coCzS_x8Ya+a*aasa@r`s{StKp+|n7({tf;H28%E{nkf4v@C zAgwk;@%Z++lPf^F^LjB|sZy1+tT#l!5ENYSKhM1pc675tX@_Fd^!v_U%f1ek=@@_l zLr>{=)upaF>CNZk)bH@NclZ9j0M@GBN*1zsLf1FD#|gzO8#u6VrHb+F9`|KU6wl6r zv9njjJ6*(Qp|{z=qr0reljUM;E`gF&F?WsoSm%9=@9c?uo`On`%*gyAJE;Lw)ENc`J!&*U zmZ@{~!V@RC5Qe+l-R1P%eB%Ofkh`f-os|_tS02P|1dNsdiU=@k6NSN6@xQi+tIG<3 zkPTH*4ooT)l!FSwih*Rdkvxr6Dlw^Nj6YuPt#OTyX4}9D*&%sf|1UqZm-81o!gpOK zXS~g2op!SF@qG2x;#rc^5(*jmJ&sb5Z?FC<2lGN8PI3EkoS2*|gXgr@{qLFP#)N04Gzz^CG=T9UCbq^Mu5E$+@jUb@Sq&~O7AU4WMi`pa&W~Ld z78wJT)X*vo#gwidGWi%+6@q91j6WMfw=|uwRu~9qJY)ZcPZ(nN zy5Zu7Id9x$4#lS+COa(76;kLWG|~l+C=+4^Q#xv#uy$3cfL7qqq1UWeuP2ht&x)y6 zkk~wuV}2*$PYe-jKle74Zm{)w*9mP9;2_&_0QusQSrcGCY^oC8ErB9K zSjHtFXbX~e$x^xCD+TjJMZarVhikiwIhTQI3Yyr6ob~$TbjGra1dC`|)cOw%>+hgA zQX&@bg&v>l8kMwIJ-tYK0R$LrB@~AW0Zla|iE>PYQI2)_|7w&`(&*bPVRT0v255p9 zLd7o_sY{p!UJW#te`Gd3c-~Bz52i`KR>SGBwRmTU?4Z&dwgP$%r18T-IFLvz5DPNk z3Tje8T%}AcjpZn&SvTZ3EGb@+Bysz7{Q?6T^#=z20(Zy0zWu(?)NW1GLqHYBa&8rpxper z8{fvpENJz|VwYP{*x4i?wTIoGl`6NB)JN+_1{?omt{{qraT_L0-#CNdUC8Du|N6_i ztS8`~MY~_Fum4Br$-%p(r=mPa#f>d{taeGojAtm2N;aY)1ho(LO`hG4jsmwOrYpjDkx!ni>VcafWX(=g!|N}_xlh# zWrzLyI-#xPX7q)^$Pb72qH``Gf!>4R+IXEOe(yuv*0P%AirkIa=0AP(%;#mHG-}0n z51Y@a_>!GZ$L41WgN%-f>EcuUcrlaZKG zf-b|bf|inrKXW|kiJnr=&hK4lRrb$f$%>fAfXuU1UbhO<9TuaMBTl9UJnZ&^eXu`0lv6Y`M@99Y>5wnFx1>O0qT!lZgZ#qDfoQ9&h30jIxA^sd0J80&hSeopLcLi{WC* z`YSJE}_eBuG>EWjk z4w*Ta5jYlmf`VQ-5gF^CBh;U9!sJexjyVIQu-RRLuF{Rfh_j?pzwon@d|*(_b&_>| ziwR(ZTGcW9Z}GPE;GRD#3~c+4dq|}u#r60VHNyvae~WksI%gup3ir(fEg1S!$+lCq9=;AO-3+Tu&78YunrQw++u%@( zdT*L?1vbWC>!g2D5Gm^L*p&xd6gj-;r@yrq29T0uqk7babYiK4aFGLjJ9-?2;35M= zrBEkHr9$kBe1eUwg={H9JyJc8pd4dEJ?!6&-+}=rj6Ld~r<)H3J*vR4r@+8>{S+AW zuFgQkBuwmsr>YXE&tB=hz79_GXA)8FJ|naPUM!S_et|ZgDiGcJHXG#BE$S{knP3Yk z6@g}(eyvFM(t4bHzWEwx?(l2n3ILCo{jKRf&Drf+FW$2n*k?1N;FPthOG9xr!PkWs z5=ElgJU)TGn~U|c3pgVG41ck>Yj^p8ZC!}Tv}cH#1M#E=6|ofF+r7~l^XWH61r^E zNpXmq_Vd9p0 zEhcOYy`2seWXmVQ>&zn>p9f9S*?#F8px@o9|0glYEm2KlclL=c-M6j!sT)3F1O@%I zC(h5-3$)bZzuKYe_9I#zl*N2xD64JP+1Ih_l{G1Z;S$0PdqXOV>=OtT(e>giTkh{u z!r3NBjKY%><7c`xuO#?5@7ONG2$ChxoCMiqAsY^OsV^`2^62wk0q2Ubr~3(|Dx99P zk)%Yt1bF{^0=?xs0T~s6A@>|AQV7|ZXDS}lf`?s@p-LaT z)bHKQapET#`2~y6PJ&XfVo994LM+kH5@LkucHyC8eAFSf5WN|L$8M@Ll0%L7XU+R$ z?BS3b3p^&%2^S}{Q+<+-`M*7T)b83)r0&Q%QytA~Iiy!r{@-REQ%uG|q6oIo6@Ws# zIFKnPXiWSEFP<`?hoJN^auB_Gp=Vr&z@RVzV(~KwY{aWi80dmA(#BslWF~jwsMukx z^$54%9$9@3Ypby-VD22ZgSaEBD0x%)(s@U4 zsce@i`WaK8XbOi(*h!MHV!%NITZ9dWb`0q82q?xV*bp+eiD8H~TEXkbOS(-V}5{PMln3zP*>>h&9SkWzoT9EDE&=i zi2}OnFf+f>ZmSC>NEjNh)tN920(3%3wrH&EO-F*YJ|jEf3Id>j_0H#Usy? zD9V6!XYuBBihEY~DnI%v8nmQ)ubjpYQI(9htW@~LbY$y$3Aub*c zU?B}W2?HsOAa6KAW=SqC7_omtc z5BL}-vkW-W(Y^bh<076Z5d$J%guUt$H?ihJ*IpJ_pcUH_6#@K5aYpcb-t?=^<(;1Fb2Gia#VVW7 zeohB`p#4DR^K}QKOEpmJa?2h%k-T$Z?4>gZRph&E^u@Ht@s6xBSbzI4Ao3EQVFR=- zjMe)K`lG|!(RBP~!dY90OC$OYp+!22wD=4S-X?JwphfuR{`fS0e~|UBC>i*r@_SMm z6!9@ZvTGYjHznla!N?;G=>rZEtC>9{-=hS5f=`y|GTn^+O+#e_V`{Bj{xTH8$op!| zQunds6A!NcuT}^~-e0Tu&dA6ZoN8AHNDnBkGLzZEGufsR&<4XKtE!*sdNI-`p!(9X zaQ+PJoj$DXMlw9K#H_!-5K^B(RgNZ=*&~dtW0;^wqP%cD84@3jp%$S)S)+1Jut7_5 zLDXoWQ&#r|m=$97x#B$k?*Z_e+C$`VhNQgIa677u(_Ky!43F#nyb9xUma~FOz(Af` zJH#~^0bQigSc56)WY(aRG=a#@&UVi2@O{rBP#A6PEgeXZ$Rgg090VoL0EtYvQ~I~0 z6;r;T7o=p1y+{I)-4xyd%*N`*Lz0*=u?^!xWD)v>L}EezY-vr^1eLe0&B1!$Z3%bd2~ z?jS7>Vto=TOusS=pJkrkxjf`w>bJkD=gv3o?6d%vC>W*(mbt@D7Oy)uo_B4ss`=n* zwDD@_ZQ7H4eRJJqP2I_huU^lGw&Btbzo+T^;>{L&6r_gA?y4Ca^_$WR89H)am-Qv7}^#O*uvXrTRyZk$y znBcj2N!+q@zSG7-Of3qXA37Zd0FUnjk!&6HOrA3)s`*=~7_KUBeYB?FKHl5j;WVtH zG@Nk|A7`C8X=vFqTC4grp=va41KGq;F-~?S5+=szLJPq(k}zQM+7Mia6WT+COnUb^ zwi%6u5o{hMCp?^K4!WH1Y8Kq-YRCbhPp8nLLY4@DjDt-IT*IC0iS*3>#a_eh<_Hx! zFlqmC&`Bir$C_jH>;e9(^ghPMZnQR5rxo3qnIp+4YpJ@-o7q=#jlNP zO7M#U?Woy5-JUMJ*F&{4-z%MgI+Cg3Vy8_!@UIj?$g^|#njm;dl`6Y>$CI3!hdcqy zje1|`jU~+|#O68O+%4ugq@NsPTY-!x0)l&*S5K^|RjVo!sXW_HkJq7SSmqYb=xJ;W zak{zwr{-qwD%hT#-u7Qj_dA8+in0hX2wJ~sPY>g10JCP=HG`$U9ueXTkpGz^tvs-~ z@34pGmCV=b(2&#<5o&QNo*&pZs`6QQOsm}uR%DY0Lb9$&3e0_<1x|!2elGQR&rvQZ z>dI3bDjkpAF^wCYunzn-Yrg+nHM*Fc(XGhi)4%BUwgFF+=IQ&Y4iR+qFQneg-$>B3 z*Dz=BOm=pv+|`jDoej19R^&tsqn^aoX@Ut$rn`)is}SI%nBnB^B226Pa;qDXJ(>9wqprLnaW{=HrIo)nOCRt zoq0j|lWQ$7wp|XvzvZqVD*fQuetG(!bfBc6CbziP)aO^}xwq2_9e>on1so650)uG{ z!R}v~8>;IsPr!o!jwt_`z7!TESr9|vMz%>0AXxcz;XAwS`WfAEVSz@vHNJiZZlql9 zteq)e5oTWvYm2Qi^n(~i<_8~1LK-u&6?o>J)|tH;;TD#l(UxuLQC1_NTs71X$jH>V zl(kT~Z~)C{U<9A2i*1MDloN_%N7#<;M2aj0|G%n?VPq9@P*+^=A0<#p+Av(FiP7xn zQ}m^RwfJ|G@%e`?t2AXZ(+WiQ7khJnW!BSE|8@=jK1wy}Hv__L^`84015(xea5CmX zU{93Wiz#QzZ+NQvW_F~g>*U4n5i1Q z-``)3)+MIni^-|cqoPo&bvHjz@Ng?mzubJU-`Df)bKuGK1fYG6bj`jy*0aM0p>D|p zy*c>LU+{l8ya0>3%c5rlV4N;HTNl5smfi_2R#FN5+$S%ruON~i#ar#C?WTHIQ0eVM zFAGsj2P$gNgzTDz?|rQpjrXx^LoRbPF(&s!E0zO+&AHQI{_tSC4QwcX-eMVZ)rQNM zZ(;!8=P55s(x>`3NA@?WGzO@3i3%;^0<;_+VuHZl| zkO}$fNm2hUB2qY#KQB}HK_e}_lk)w#!-agRxtP2hd{^O-!%Z^6d-sN8n1uK$N_E(@ z(qZ`2&1zh1Oe&^t#)r95d}=lA8wDz>+PBC1X7gRwv+%2vQvB6#^W;X8I&z}oZv_Ld z*Vsof*v}dd&1@VFSMr!TCAv-enIqa#j6>s zdb|Qb^qU?JVm){3imq{lN zJwd`T1)t8IVkN1J(mKGp4> zcFk_|46)|F8D;E0>dM7 z6}<04=e+N7{-Z-x2`}50XfydpMc%sgL9~`c)>x>a+uNh{AZNv6bH$6jYolmUA&8AqT*h*$T?`uISPf@#l7 zbS_8A!)SQiA{8EpO{37z*H=TB z(7+hTV5~Ek1haExTi|J;6l39+L>g)94^b20pE#iCo>6G#kO^r+Q!*wP=kg~VrVEP2 zeSwAOK_cbQnelKSo$^wJvj;Ug(sTuBi-I}EF|}F*rFP2+iYj<2 zi2i(CwdJE8W0`ZDu8HWwJI>hmqxcid zp|d0Q+wD18PV7(fIV?M>vT+Mo4iA!PjB!D;Bgn#-rF4Rm^zq0&CFv^si@I~WtK~oU zRQ%eoFbz@Bs=~tMxjAb~OLn#;ayHjZIAg1$KU^G8ToUjFRB?er3m{n3qdBoSk>Z5I z+Y9j-lcfKeP)mc^H(OCDf<#0}aQ1pxVt!Nv9O;6S;e-#&W1j;%UJK>i)LF7ozP+~4 z(G6FVq&7-)iQr>Q@ej7f#H7+7g~+U;KK#0bFR4){jrl-o=)sKlcfFkfrV9#*1hykp zSOBR}fHQVr#xJ;%l`OoxLo*z!=P_sLhTYEV68X7S#;Vj6DJ=*o^x*&zq;V1^=mwQ3 z+>v%ny$Q+=XxPy|9ksl`yhbX4EdEmQQ%hM6WO)cAmJU(`8-aOwK*G{V&ja&-Oga#Y z@b~?l$e4g8@Z{q6v%en6>`-0oU4K1d$oEzF!MLz;z>4JytDZ!W_x)uceR@0ItZ^95 zy39B;t1qa#c8dp5C_Wz+)Mt8L)!`rj^tF1Kv%KW>eZjBHc`tWTe_j4~`OfmcnHqTN z1N<6!*qyoI(dK5s=rsUdN>y(<6TsOM~)*C!Vvu{YpipcZ%>{CIooxS+&Tm!)XYv(jF8oS(2akl)J`dA_N; z&VCdp^LE#))veNh<4}uv?QAQ8wkV@Kjnk`>>q9d|%c>JCY6=I=rFiA<;*XkmIi>Al z3{wF!Xl&%7&lkM=np;_5y&!7Z9Qv%grW5?!JAqzVhwZ&yCWtMl!6hPEe_M4@w@;V1 zJc&hQM6-sck@N^fu`n2a0N>uw$$cKXPPGA!VQ4#)9DrZqnd*bD0g1hDyJ4LEx;@)^ zV>|(#v?du0adDK*b9Gx!UAv#P^&@t4ahQ4cqCUr|%HP?iBQ-Q#K}azOSYM0thnf=K zr~dk$X3YumIaps>N`u);L;U`9&8?G&=sUNo0Kq4COX0J~hD1BFm+%eAVdY&H-GC>9 zwSy23&u};|(<$4EZ=T3x1ZcWQNB1Q`nl1t}!(1F%a#appYyvzxK4Q?{ZsdEd08Trf z-*w-}-6xM}#yGF7`Uz~v2v~fGr8I%DM}XgrEQ!4+LaG3fI8quwJQcx~IC$f) z`FpA3E@<;wh8NBX`0qSYJ{YZp7_cPUV(qf2eu~m#vv=yr!(;fpc>BkTS8RrrgOq;? z=nCk*%2%6UWDC-{K~mW*4MI*SfwF4-g>UO%5la*9(0cCr;*2m6w+p}6io18*=Tsg_ z+>YTj^M+d*3`q(NhyU(SIFjDj)TftvZIx!+-Y77btEqim0f*Z@xl~vlAO(7w(Uvhn zrJ;8!KcYgl?Y5JmA{JfyP+DT_l;g33Y!VC^@;UY9PoEVWhZf$DZ~M)k^r`PFfeF`R zr>$;6gWE*lOqP4nefIA7Zi-aGA?X-pxA|2&ex|%peq;4gk$(?rpvAA0F#j5-i-pT> ze0?{n1p3neSL?igT$z2%>ar!ccAHQX{Ol$%1`5tkAVI1W>^^X(P4X-AoiWL0k|D60 z!sjxcQ348%D#s|H(fo`Pww=0vO`)}8rn_{rRkOrn<{9-*xb(9@r~D>d)H^z$1|yDV z;rnZXQ}J8e*?0Q$*Eu?Sq{d(R9Tbcki>&Ghe-xwSKyAU`}SYo{gkblh!{$S=X**j{hi=joVRt$R8ZU9R+^b zT=-0Fg%2iKyk{trcFf0yw#@P+`;qrEBFrZY47ED-&7-#K6Ujenrfm(ir!Tsv`?h}D zvzQWj+|e4%U%Yj#La1XCDN}%ag^9V5K=}Bi{%W9*K)P$Q#9^c05H(!NU}|d0eqF?R zAX~4M5igOh&ba(-x@G_Dbq1ub_g{`5gM+&_(>vV4j^ zOy6=Vo&w3N$gb+a`8bf3f~n>rB>p<#Dx1QRZ9g=s3&kS2%SohXmB#r}~mh56d4kxwJ_IB*~) z4B+dxtDKT$J7Hl3lcj{ndH}sz1eLV_=b-@SIR^8)R{J}f3VSPImvEb86)-YtmkiQ^ z|My={k15FgPY|BrZ=3yZAmH4`^?O(r^2y) zy2%d)WLIXAS7@GC8)28c;+LDyguFu+E~Bpgd9f`E!A}U`e`Je$eG3HqbNnKieA`X| z*uM9k0M8lzOgkcFq}eusLU~6&?S?mfk%!5+sW$&AIo)2(iju-Ja*9&SWCtm`b(A-K z@OuKurT38*OP?=Jee~$r$8}u0lvsM?iA8%St3Vm0F8pXFQy38imykwMnQSAThoQ0e z>ho*lY3tcNY~IO=Mj>J(KZ0Qxn0_u|UCR^fW6}X`{j?%X^gce7%fiad11%hU@A)@G zqzm-96gXtEK`O}@>1OOwR)`}J2>Y)^z0?XcP-vD4@2~SRJj$Edwc-B?X$))5z_%w? z`AP4v@&Qp{ar$nTCg<}aFSCcsM+<6RlbZH*EZW_V@KH=&-}R#Mt95?=2e0FB+TPN) zTTRdb7UJyC_lwZ+@rjD~g}XQ2oF9K%TWGF2@Q7ygdGTcGEG(W)tLl8GU#=_OZocy% zgWwA+;gTgS$^>=Ry4t+10qdD2ei^V(Is~eA*N^KHsqr*7_?dtcT_KH01B0H-8ue9ienGKCpNL$A_Jmgw`LKIjRzqKWehh7GuA;JuF=hu$ zW$MBCDX=CXq9VW*Yb5kI$;F1?S@hWVjT*8Goujy10poeF6yuZSU*9Mtx8RVf{r5Tk zq^bO{DKE*B>)~8mVB28U$Hj}*1hYSzRUpABP{<}GOZVzGfZA^k)5#>2>m)BEyZBqg zE;qIxDa=T?`qv&GK2QHy^QDubeKZWP1x3ytT>e^G<|!kdt|5<+JGv>i&rB&1HL?v3 z4T@VX>VOz}$|;s-=tM!BofinmyWPwUZQyd54CLI9ADaPut+_Mv)lOuGwN?_m{O!*T z5ayb5SBJm0jVV;{&B%Qnak%juBnlbqBOuy-)#=MvU@j}j;kD7tAC#@hdUTnc((XFW z$~EI;-g@nr$$kpww5ji+Zaq=qoR03`p8MNl3**Y<8!FrvLqrR7GuvwV&G1y7x5m#) zIe^vRhsajXmHyc}Is6Y_YYMvaNa7}+(sgfo69MX6@yRTJAx<#r0D3HX_|#ytm(X6! zczQ6tx3lT-=$%&vaycW!-%M=eQUPr18UfJe4$lq}{czFp9I)+Xx0;`%LwNg~?SM!p z!0zyJ_FK}Cw2tk_{WHcbJ03W!KeOi5M;gQ*h?BC9}%p$ip%`e?_cr7D?e z5yA^P8*>6CUOWLRczoUuhiuT!QAk)>sK$kI!%Q~@4ykypulNkV0&1Kl&A!*SqOO7P{0Be*2>DBE_P%;ZNKrDu-4DUcuJ798xWJ*U{!C9&S zi%^3mK{5?WM68!s**hy@4CE(pDw%NT5IZXoR3o;TLl z@hC0oi1Gjj(d%d9Zx7I-@}kEhrpKzYV7mN1^DfBw9oPlNr*$7>DU8Kz``+?o_X=w_C`7FbOR;6CGC~fq%eqPBu?{7OXuU#?QC-F~Cnt#(N^XX7Q_*_ZmVXQsL z@djiO%^>A7bh|7zMNtY3xs;WHD?5~b!~7n8yAaGvK2Ma?_jy&cS>eAsLaVE^yn0Q( zXsh+$^@fle?86G=p^XW&)*&anG6 z3g$eSo(VDOEuMH>P5ibyx5pzqce(D;L|R?qjsFIHHe2PxFK3&abO2yowp%NSb9wIe zUx3GUH$ZOEbAIz?!&&nE<=XeNr)~7*N(`GvS&PeaBOl;;_2z?+R+J7F+jLgQ`=T1? zXm9C$SXnF~u_d)2u1ZCn{C8*#>RuZzN*_v(USs7=X5Op89&RFMyHw)GFZp%|5PPw! z9H}}@9m%bsp##P^g&2$5e;;n+;MTjkY+0}l2k~jq);~MAn3wh1Jv*O$50&H<%{GGD zTa;ME1EO#sQnV2v`f-)|@zXdY{$#(zhT#w%sIftNKD0kgGOSsu@jC(y!sS4IbU>v1 zfcVLbk4#1;$~7!Cp9hsEOUjRKTQ5-)$uy;M1R|^qK{W^_HAcKQ;z&kCE>*A-mM2N^ z39-W49@QgM_6e;!i~*I~+AOFgnrC?f=k5ln$={!P3R}w))JIs?i&wf0B-aSSY?ncG zjp7C?zhA0uzc;`l=|X<~@@g~4=%}rqrP6mpB z1xJ#|b7MSZ6c-_U62jy4_X$66WMy0@Xjlf`1PF>vkdA+N z&vsq3VN9nDuQ=|svG}9v{LOl`tfoG&>j_jer^w6n%wJ$(aLbfucPkuo(hOLLALO1}6xcUN8Y-2y(c=IVTNZ=n}|El5|Qr-nvjYs(}b6jhvbP1&yEy zh`f%+2Su(szS9|0HN15ea_C~sLTO?F$j7v1#GnI85HP5}q!1FBxJLO@SEf)BY(SqX zf_UZJbTDjn@kM0_5+JOFa^-%*;WJ^vnI$e|xJI7u8_9&5(sKR=a;PHdcGXy^kf=Nm zR4#-l<4njTa@ zWoPmJpLD>hWpQ9!NQqyy!}asS_emSfe)lxr z!8^h4dgv=Cxp{ARaC+RS{yO6T?R7~(q}}+*(_c8%tVk(p?QeCjJlUeit4gTw#oj!h zy861<&eiE;r*d;H*kxf@&i6dVswm*3_zyy{cJ^VSkKmu*;IjEm-1LrHG)oXZY!P)q zVse19Wq`;WXmladmP(Ci_{Bn00KDi*DG_ISn0Bl9I@4r(!5x2V2obXryTzhzOBJK1TO{pvt zPy_Ip16bm7iN)bVVyX+$xGA(((__P^Y!RP;s2EZ$R#60M9CrY!h!6^v5VswOB&mjy zA!ITrr*0ikR?_W|;>xcU@5Ak3WbSURX!g>F8K+m_cY6%XyZkzxoS#oR(4+4srYELK zIx=NL5t9Di5#ae8JxnUnU^!4Aucei{l>AWnbvZGK&*|zO3DFANSGL z)z%azNriB5pz^4LrW>5^*3d-_;!u79?Old*BO4ULCv2%C}2a18>Oia{@C zhN!B{+Vk*l_Mk1}nsWx&B(w8v$6^v?1}KyzT%`)Cxx_e-NRwFXg$QxIzs_SoHZX>5 zRsWbLIL$7HlTKHTxJZc!*(Q@s9*>x=P{A?6d1(hb9z>Qj?3=2-l;`pXk-BHt`@0`tZi=|ZcY znRECx$`;ya*$qGtItSY?%eqN90Y|23#-3Kg1zP1M`> zctVd9m5U80Fy-yMI&ezlHFQxm$_E8N@y|y<-~GH9e<5{#2^+X<0f!sy%gpUi$?Q<$ zzBfAFgMMgx*0A^eT8vc%YW1o{`?CAIn78<_akasJ26pFV@pf8V16xyh{BARNFx0*y ztIKTywYpqt>%QE~2tMAYA0=&-fEO%|Td5E4`<|uqyEKuL*vipb^|CmiFGbVsSB;^P z+RSAQe$Hb_c_g(m|I-t&<21A*9Sifl(l#^I<3C{FV?wcI@w|eQFZyQaa6EaKN~U>Q zr$6gplajYPAgS*kBk=k$^E$6Nna%FwW$Uv&wwU?5MfJIk@TodBmDw7YB)$u=li(wA zB!C5{-sX?MK2n4T>f4wM7~>#E9zW?2=>c?|RNqSI-y_4Z{e7VQ57ryf-<%;)8d51? zTJeQ5%G%In6&?KUOTt&`biO~}J}R86S7Qa=e4uC>k4CeaM5}sb(eScT%GI|vq3@zFhZ8Cq!aIyJ9>0iObRr?3s z8Y}*p9@qP(tjF^1*lJS0g2}RPCnmq|!%Z^w3I87byPMXYAlZF3U|TQ1jPBhegBR6h zWciNU9H#zmE1mfZS|VBed~mIm;H-=i>FJx@$L7;kfF~FKG@b<}v?0<|5iQXs43HUt*vSk>*p=ev zQ@DHDljc)1j`sUdlw1G<>LSumTv8MDmvWII5lSc9_26|rx;B8ad@-|JFsZ_ zG#Q;rmOq`}mjUreS!FR(<}rDM@ey3>d%6f4`GWspe$zrEpu_LNEC#0{`4Mch{Gj@l zd$QUpa5sjgGwLqEDEKMzvEWs?*L++fr%q~(#bCxyp z!PnJi8uzY#t9zY((@H;ZJ~$%cBdGm55D?8vv=nc-0sRB>T%7_PNJ<4i#C zhV%?dgK|0zF;C2Xf+cmyxLXF}CZipf2TBqZhsHZoql0=svgHgDal$a2%ErC@?-p73 z|KieHSrpsXB@H7CVesk@RHZ38r8H&`6jK&wIme&gxZVtfk0xOrT>j~288XjsZ=zor zH4nUEC@LIrD#^24;OJ$*X`|GYHNqLtz2zVarTEUhmfx^TEqe8=%ilC0DR>8#yHnw%{v3<}^|38c(TyLdU zbcM#aAQ~i*aB9>O)sBWvNKM5KLO5hUPyauP+iEsai7qGPFByk-mNR%U3aXHJ2}a5g zaX=@klYurP=pUV0}-JG&2klY*V@G85VkaWv4%y-;=i3h@XPa&4&O1D2p{saVnYjSfnIeuiHH`qw4JZA$DjBXiao;wP%Iw z$Mxl-+zK4xlO2ilwI1MC;IC~Xq&;!r$EHOCV5MUHUNVYWF7rbZXg{T zA!2F^xr2UxW`rM8ln34eBFQpT0|Q*v_verhiLq=@BMaXgRLh^kCP%b5&?@1zH$lP? zhPTNF2TfM)d|;>u`C_a>+L z`3?tb>SxP$6S{5KkOvmBnFnBi`nN}*7h9bDShg{-?&=9^AoLHP$eZOmxvbF@qdcAK z9S98+vpqaCZVJ|`?{B~^2C(b#_0xk6{f8o8!I{$XRqL_V2% zsLWzctY$SH3Q&F-TAs?HU0HGXh+Wae#I6b>5;>v`$By?5OmQw;5O&sIx~IUIi={># z=SDYPj+5Xsz$z+A8~_pri!k-JUg`8!Az-I<} z;w$s&QLAQV^#^D@Zn;{e~y#mS;+c7!Giz3?y9v#c-k_jm#m@`|#(0 zKSQ>n=A1NprsMsW6se0G)y^tSogM$%1{0hb1~hJjt5ift)*J6X;2qUXI5X>)J){~s z@r5_e&t@T}AfY4e2qu#etF6T~7uXUaqNd=M1vuoxt0oN%>@Q=iA$JhSL?`D4peN&4;$;mvYEu})0yy;-*y)d3oDOW{b{4KHow`5Pbr^HIeDBn z@nwbGaQ>u_vp^x^!IdZBT8_~DmE)PgKMFaXC!w=e*gNIf1Hg;9az$;1XXM0E{lwK0 zM=99QjNFe1j=0C}z-FG0-Jsrd`~z9eSg;g0nOyS6E(9|d^&HKn2tx|%SJj;@I|}r zJtr{L)~!PpD$nWN`sdf>Rm0+9dwo<}47mCxh^N|7D)(Vm?cm|}IL|)ckM51W{#%G% zezXLPBMAddXA6tpssn{ofa_EDcWpS4Z^sASZnxQF=N-EZksi40e5d5_NaU&pAD`w$ z2oQeNhrmqvd6yn^F4N2Je^JzNVE{GNoR3pkRfMFnl|>cbNNcM}CZ65xCOk}NH@$Am z9qndsg6U+-T(ITgVhwZ#!F=mg7$ud{9XhnR?APokz-_;g!h&iRTEb$aono)woUMiQnwmcL>RM5RYq3%3X2xWkjhGY=p`TxYN zqhr_g`M^G%=2y=a*vE2Xyf@Srsgl#_Z}}~T-cd{j3Lk! zCa5@2Wnc_UHbR1-6&`jA;q?Mog6%l7?fqMZfjXUM{Xt7apR=Yn`u4+jYZCI-t6)gN zJco%Wvh@AVhKk7nMw!$G?>T5hGqqSuXr>HW5n7YuAz*TEgLzAEm zd0(YO+(z*HLn6s)lwN3oTPRgn$jZQaJRz83VU0WEuza9q)HaW`UU9T&&}w<*&QODS zvKb;z7C|Y{gxJJ7a0MMNFf+Q7-Se%oltd(ZQS-lU-eDkltTC#NP|k9Ihx*V@?`lA0B&1kpz`WifImx+wjTmSq; z$+gk?rO0US*GlxSoUII>$ux;rb38UagwPEMvuQhZLcB-tgU=^z4vv)ac{b;v$v!F5@~hdM@-y z0&a%kXSP+~Bt?m6X<#zUPevCoSu3s&HI;T>Np;-F_8@N8u)PzbO8vq zF;X+2XAaBJZR)zo6%=g8OK;+Vh)e$WTC(ay%< z|J-5&5#2|CjT?OktLf)%+wE)H z(oJS^W%Ru;ml#KElOOMG3brz}OGA0I{pN@ss1WQcI@?e1Z}@9Gy5vmIyoBYj5}2#v zH#;2q2Z8ju5f9pn^46boPYUlul7lFgoCO8zht}od$dV!PM;~%4vnr{`oEv*5Hc`-@ z4%DCND!RQDn@m`RA~K5^AaI4?YFwwwKcN#jzK~W407~+bL748)0Nm6}gbP}+w*+rSxTGy*1v{v@tTh?z9t~WCuZb_gRu92;1 z*$1-BflgPur`&U$Ul#(qm^u@d&bi^fUTy>mwa@HH3nOxaFqWiz0sbuqopc4^{7n%?eK`Tq%8 zpvvn3AnAHAE}7?o*B09iAxY?~X77C`ex%>&uUAp$SW+jh5WooMWMyk(bfq=E+%qcT zX?vF)A4@TxnpI-9^v|9BPIid4YnaMzsF@Abn@9jm4d_^bL;E*U1I7wtv{N>ni%4P~ z*=#!SMfbw*Elg+dbw6oXdS^gbk&ncutB2AYRgD=Hts*h$f67YcE!;;nm|x@7bHt?+ zub0A?%p3^BQ^;cME)8t_vHa;u@61~~wlzB@DW48C-*O2oswcCgp?v9>BT|q#&ePQp zQ@@G|L&OfLMj;w8CwW&V{;7t#Wem2`#K;tJaUyD!C7-K-r`%rw(s0n`sT#MAf3L%| z5iO7`F9 zuT`%)C86Ce_`SOiF5S7RM`eBv~nW!p(gOUOY#sx;ngV}I;1`uJbi){0)d z07DSMuq2VE^=K=zidWN7@0(Ab;X&?(#ZdXm+FB1}QVUikmcUAgF@0bnJ(?eYz^So# z#+Sc$T0d5?*JHnNPUO9who1>5S3otyi{r?Q&CmcebxQrGSS_hg#+7ujE_GrlR98?K zU$Lx0{M6arep!}B`i;o)HZlg&iAr!HQBWUI0x~S3&oR2#Xy&PL$vcfFP4eTR$g+4y zj?fRZpr}k|VlAe*VmX11PIv#4{v$ z;xwVf&SKc8Be9Z;YAaR!|zg?&_+pm&xw2K3Sdh z2KB<#ILz&~#DvS|OPTw-rzPg*u+62_4;$OcKIN&#_I^&rneEQZE%Arr_A-Fq{VMZU zjBH2vquaUX0C`ae<;W#j{({P!5^zD|T>vqAuR?(qM`9ncHmVgqyRrLQ`01JX8tMqm z2uo+reco2XOjg13dh3($>d6p+R37K^3ud1Ig*qOFNrZu13(l0U8T~~nEwPM6QlMKR zC^|YGj5nP~svpGxlSg76h4cs0wsGLKe`5D|?l@J@GYLP`5!+Q&ae7Q0>>4%oL$qFU0Rx!md!VH7an7!rrjy6V-Yy=2PW zHy-y+iC8*e2EfHDL$ef$q%#5BM zl#@vF^{6TRu{Mvp;W{aq{#t{}Bp;{Nd(lp|@S@xkX8uBjMi)^I4R$?5W)gr>%0IgU zsvcp{a%H)JcjYKI)E+k&3>Ag)1X0r%_BQZcqkERlSv}5ge1BjPErR`wT^^vKzl=+o z$5-@QfkaeG$h70$A92-up9-HvlA3%6)f>|&_@80FR^N>Z+QTh>ZTqrt3EM^(Y1`~h zcvwM{_rHZCkg+gdY$b~SAM*~2kf_O~-EcYq_Sv>L?QpPRAmoW0hE(W9wF~O2$~Y!O z6tS0gcZ89j)sr({%11{Xc@^ct)k5X*uYq7)Euug}fTP4{gt+cG7uS$Mv5kjZh1g3z zI`!s=vDsjlB?&&mkD**4{0{UNP}b2Opk*8GqNSPaIJV^O(%%8^+(|{T!9`ryyNB)s zA0gpnJ4;c&GGlNO)Tu2O?3C~lL_ARN8_6jdS?*p}A5+dK zM&=CSFvpQR<y0E_k(aE@OJrU}4-XS=F8uZU&|&zdt<*<>~_^C&uf5_5@G z)1$(_&I@2zTgI0h;~Qi<67EQs3M;Ybcyw-O%Q5Oztq{L+na)d#ZVxa<832Y0hBBbW z-p8YBC?CRxj3r7l9Sk(eI@6|LylJHThDp9piAuj>9ZcCHt>6nxCa%SLMlfDxJDMTn zw`D!2Q=7*wZ{YH%=euhNmy$?D%Sa+sn}Y zTY&u=p-MP2Bf4z&eU*_?-rrv$`d$SH#Mk{GYxf0T0h;wdWVTOm78$D__O#jdm5%ST z5M|fc*?M})keLPG@DCpzpD-pto`4P?+N-r$@{DK5$t}Gl zfxb+OH}wLP!(Ea1p*1ub0as8A6bbgWZ3kw;|E{+rw(lc_FGce82IS1SDH~f}DvfAw zz-UGnfl4PF+XVu)n1kn+PYQpEBGe6tS2c+r)of#ZWOX&k(+Z8_(+`#+pGpkzT5u_v z>{f}ub6Xgmhv09{M2Zon!_PBVr^*wH-Y$u_h~fs!qW*OA>iA*?{FQbK6>@g24$3pjNP{+?sM`RUc#ji&;Y3}4$#jM;PS=2m;lB^iu*ix#y$ z`&u59pA#4ze+k<&ChiT06B(!qd0rg9LH z9EBv{$6}W7#^`25{4tgNVgoNNjh~{sTm3%uzWf$I{585Wo{*H1*Q5RXFI(0YoXOU) z^7iCm{OJ#-cBjf3JN?{C*A1GtEos4SJ|!N=;^;NTg@A5u$TgLQTgKpC2P=C%J306wXTEZ%C??oWrM?dbl?{_kqebnIfg9yC++M#y)fH&S_wDoG% zr<=K~2UBGmVrNvZkmaglW(QCCh(D<45t!C#_uE!*#7SzZf;gIJr#c*4d$I)ki(y3A z)_QoLPKa<9qWU*>%l8laR}-hN zlFp+K)eEifLidI2OpT2}&HWmxj`2;LZ^dw8)|&zYcBOi0#Wg*f$=^N@n_+rZKAb8W!jl*Iri>K z4cf93C<=6SWJs&E=D<><8>9daS%NY^J(2aExL?iZr~AfD=;`bU&k7(GVt_u6O=s?& z+?p)usUJJs>u%N&gA`R60Tj>7q4@R4-hmD;UDX{XVeyZR8b(-s)iR&T$LGPh{#X@= zbdeDlYh7#zf!HGjsg=oH@tx`j6ba50)H*~^g523BE%`m-;y`b_)<6P$$4)^DUM#uHh?KtuD_&l zau5d)g!{^2^{&b-&2l1#yZr7ylOJNL&6HlQp}4rqkl9|zdL@}SH%oslJ4?R*nYKZ~Xkq0oT>P|p0g)mIwx^=Zg~K4(ApZ99i}S7_*!K*k#!%St z^3^AoH%asbN#;rDm{&2FhIJ%>8#_mN8u5t?RKP(#i-)fhs48O_Cjya*!zlq3JmIK1 zubH8Kis|&;)>YXH&AIiLubakj`=_`G_o{W%t$y}mD?>%jgr8+JN`oK-M22QIOD1J9 z8HZhPj%*$+SLw15bT1X}Ifj{O{mZJu4S)m7Bf5OWpVr)IOn(h@*Wk9T{4oo)QCh!C zM4AunJi82f3FrJ9jG$T&ns&DDe?Q-m1CTDo{)G;~TPnKsGn0{l$%*^J<~SC&7AZ0^35YajZqh!uE92&v^B-tafE!?D(T%I2JR9SLbE_eV1|>v*-D~rHwU(undcVFhWi@ zqvu8FprHZ2r?cUC?jnS-n|d3FrTf9gb8B<3H*k;@&#uUQyVd`YHFrNU*OxcWs(vI- zNHv3>)8mnUyHy6Sf7!At2EWX(R-{2&bDuZ%F_tI3U6Y*?5~NmTiT%~~JY5rPwKrJu z(<+L#M03efwvTJNj@d*3@*Ku!`m=Vp-4ARNLX{F0rSH^y#|j)bM69iG0z29=GHR ziAXR^q9l7OehTzlk{VDs5Qk}e8Mw&L2mB1Dp?-F80_?W9_12w%0A*{#^6U=b>##;t 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%0Do&p>}7Wh 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