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/.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/README.md b/README.md index bd640b50..dace3529 100644 --- a/README.md +++ b/README.md @@ -5,46 +5,53 @@

Zenith.NET

- A modern, cross-platform graphics and compute library for .NET.
- One API for DirectX 12, Metal 4, and Vulkan 1.4. + A modern rendering hardware interface for .NET.
+ One consistent C# API for graphics and compute across DirectX 12, Metal 4, and Vulkan 1.4.

NuGet - License + License

--- -## 📖 Overview +## Overview -Zenith.NET is a GPU abstraction layer that unifies DirectX 12, Metal 4, and Vulkan 1.4 under a single .NET API. It enables developers to build high-performance rendering and compute applications without writing backend-specific code. The library supports modern GPU features including ray tracing and mesh shading, and integrates seamlessly with popular .NET UI frameworks. +Zenith.NET provides a consistent C# API for resources, pipelines, command recording, synchronization, and presentation across DirectX 12, Metal 4, and Vulkan 1.4. -Visit the [documentation site](https://qian-o.github.io/Zenith.NET/) for tutorials and API reference. +The RHI exposes rasterization, compute, and indirect commands. Inline Ray Tracing and mesh shading are optional; check `Capabilities.RayTracingSupported` and `Capabilities.MeshShadingSupported` before use. Bindless resource handles expose shader resources, while queues, barriers, and texture layouts express ordering and access dependencies. -## ✨ Features +## Get Started -- 🎯 **Unified API** — Write once, run on DirectX 12, Metal 4, and Vulkan 1.4 -- 🎨 **Graphics** — Vertex and pixel shaders -- ⚡ **Compute** — General-purpose GPU computing -- 💡 **Ray Tracing** — Hardware-accelerated BLAS/TLAS with RayQuery in any shader stage -- 🔷 **Mesh Shading** — GPU-driven geometry with mesh and amplification shaders -- 🖼️ **UI Integrations** — Avalonia, MAUI, WinForms, WinUI, WPF, and Uno Platform +Install the core package and one graphics API package: ---- +```powershell +dotnet add package Zenith.NET +dotnet add package Zenith.NET.Vulkan +``` + +Create a graphics context in C#: + +```csharp +using Zenith.NET; +using Zenith.NET.Vulkan; + +using GraphicsContext context = GraphicsContext.CreateVulkan(useValidationLayer: true); +``` + +Continue with the [RHI Guide](https://qian-o.github.io/Zenith.NET/docs/) or build the examples in the [Tutorials](https://qian-o.github.io/Zenith.NET/tutorials/). -## 🌍 Platform Support +## Platform Support | | DirectX 12 | Metal 4 | Vulkan 1.4 | | :-------: | :--------: | :-----: | :--------: | -| Windows | ✅ | | ✅ | -| Apple | | ✅ | ✅ | -| Android | | | ✅ | -| Linux | | | ✅ | +| Windows | Yes | | Yes | +| Apple | | Yes | Yes | +| Android | | | Yes | +| Linux | | | Yes | ---- - -## 📦 NuGet Packages +## Packages ### Core @@ -61,7 +68,6 @@ Visit the [documentation site](https://qian-o.github.io/Zenith.NET/) for tutoria [![ImageSharp](https://img.shields.io/nuget/v/Zenith.NET.Extensions.ImageSharp.svg?label=ImageSharp&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Extensions.ImageSharp) [![ImGui](https://img.shields.io/nuget/v/Zenith.NET.Extensions.ImGui.svg?label=ImGui&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Extensions.ImGui) [![Skia](https://img.shields.io/nuget/v/Zenith.NET.Extensions.Skia.svg?label=Skia&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Extensions.Skia) -[![Slang](https://img.shields.io/nuget/v/Zenith.NET.Extensions.Slang.svg?label=Slang&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Extensions.Slang) ### Views @@ -71,3 +77,9 @@ Visit the [documentation site](https://qian-o.github.io/Zenith.NET/) for tutoria [![WinForms](https://img.shields.io/nuget/v/Zenith.NET.Views.WinForms.svg?label=WinForms&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Views.WinForms) [![WinUI](https://img.shields.io/nuget/v/Zenith.NET.Views.WinUI.svg?label=WinUI&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Views.WinUI) [![WPF](https://img.shields.io/nuget/v/Zenith.NET.Views.WPF.svg?label=WPF&style=flat-square)](https://www.nuget.org/packages/Zenith.NET.Views.WPF) + +## Documentation + +- [RHI Guide](https://qian-o.github.io/Zenith.NET/docs/) +- [Tutorials](https://qian-o.github.io/Zenith.NET/tutorials/) +- [API Reference](https://qian-o.github.io/Zenith.NET/api/) diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 5aff3b15..3e34441f 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -2,14 +2,13 @@ - + - diff --git a/documents/api/index.md b/documents/api/index.md index 2e464262..a92e1ecc 100644 --- a/documents/api/index.md +++ b/documents/api/index.md @@ -1,100 +1,32 @@ # API Reference -Welcome to the Zenith.NET API Reference. This documentation is automatically generated from the source code and provides detailed information about all public types, methods, and properties. - -## Namespaces - -| Namespace | Description | -|-----------|-------------| -| Zenith.NET | Core graphics abstractions and resource types | -| Zenith.NET.DirectX12 | DirectX 12 backend implementation for Windows | -| Zenith.NET.Metal | Metal 4 backend implementation for Apple platforms | -| Zenith.NET.Vulkan | Vulkan 1.4 backend implementation for cross-platform support | - -## Key Types - -### Context & Queues - -| Type | Description | -|------|-------------| -| `GraphicsContext` | Central hub for creating GPU resources and accessing command queues | -| `CommandQueue` | Provides command buffers and synchronization (Graphics, Compute, Copy) | -| `CommandBuffer` | Records and submits GPU commands | -| `Capabilities` | Reports device name and feature support (ray tracing, mesh shading) | - -### Presentation - -| Type | Description | -|------|-------------| -| `SwapChain` | Manages double/triple buffering and presentation to a surface | -| `FrameBuffer` | Render target attachments (color and depth/stencil) | - -### Buffers & Textures - -| Type | Description | -|------|-------------| -| `Buffer` | GPU buffer for vertex, index, constant, or structured data | -| `BufferView` | A view into a portion of a buffer for shader binding | -| `Texture` | GPU texture resource (2D, 3D, Cube, Array) | -| `TextureView` | A view into a texture for shader binding | -| `Sampler` | Texture sampling and filtering configuration | - -### Pipelines - -| Type | Description | -|------|-------------| -| `GraphicsPipeline` | Rasterization pipeline configuration | -| `ComputePipeline` | Compute dispatch pipeline configuration | -| `MeshShadingPipeline` | Mesh shading pipeline configuration | - -### Resource Binding - -| Type | Description | -|------|-------------| -| `ResourceLayout` | Declares expected shader resource bindings | -| `ResourceTable` | Binds actual resources to a layout | -| `Shader` | Compiled shader module with entry point and stage | - -### Ray Tracing - -| Type | Description | -|------|-------------| -| `BottomLevelAccelerationStructure` | BLAS containing triangle or AABB geometry | -| `TopLevelAccelerationStructure` | TLAS containing geometry instances | - -### Query - -| Type | Description | -|------|-------------| -| `QueryHeap` | GPU query heap for timestamps and statistics | - -## Extensions - -| Namespace | Description | -|-----------|-------------| -| Zenith.NET.Extensions.ImageSharp | Texture loading from files/streams via ImageSharp | -| Zenith.NET.Extensions.ImGui | Dear ImGui rendering integration | -| Zenith.NET.Extensions.Skia | Skia rendering integration | -| Zenith.NET.Extensions.Slang | Shader compilation from Slang source files | - -## Views - -| Namespace | Description | -|-----------|-------------| -| Zenith.NET.Views | Base view interface and frame scheduling | -| Zenith.NET.Views.Avalonia | Avalonia UI integration | -| Zenith.NET.Views.Maui | .NET MAUI integration | -| Zenith.NET.Views.WinForms | Windows Forms integration | -| Zenith.NET.Views.WinUI | WinUI 3 and Uno Platform integration | -| Zenith.NET.Views.WPF | WPF integration | - -## Navigation - -Browse the namespace tree on the left to explore all available types. Each type page includes: - -- **Summary** - Brief description of the type -- **Properties** - Available properties -- **Methods** - Available methods - -> [!TIP] -> Start with `GraphicsContext` to understand resource creation, then explore `CommandBuffer` for recording GPU commands. +The API reference documents the public Zenith.NET types, members, parameters, and enum values. Start with the [RHI Guide](../docs/index.md) when learning a workflow. + +## Start Here + +| Type | Purpose | +|------|---------| +| `GraphicsContext` | Creates resources and exposes capabilities and command queues | +| `CommandQueue` | Owns and lends command buffers for one class of GPU work | +| `CommandBuffer` | Queue-owned recorder borrowed for one immediate submission | +| `TimelineValue` | Represents a queue-submission completion point that can be queried or waited on | +| `QueryHeap` / `QueryHeapDesc` | Collect visibility results and GPU timestamps | +| `BufferDesc` / `TextureDesc` | Describe resources before creation | +| `GraphicsPipelineDesc` / `ComputePipelineDesc` | Describe shader pipelines | +| `Surface` / `SwapChain` | Connect rendering to a window | +| `ZenithCompiler` | Compiles Slang source for the selected graphics API | + +## Public Namespaces + +| Namespace | Contents | +|-----------|----------| +| `Zenith.NET` | Core RHI types and Slang compilation | +| `Zenith.NET.DirectX12` | DirectX 12 context factory | +| `Zenith.NET.Metal` | Metal context factory | +| `Zenith.NET.Vulkan` | Vulkan context factory | +| `Zenith.NET.Extensions.ImageSharp` | Image loading and mip generation | +| `Zenith.NET.Extensions.ImGui` | Dear ImGui integration | +| `Zenith.NET.Views` | Shared View contracts and frame event data | +| `Zenith.NET.Views.*` | Framework-specific View controls | + +Browse the namespace tree to inspect the complete public API reference. The guide pages link related concepts, while the [Tutorials](../tutorials/index.md) show the types in complete applications. diff --git a/documents/docfx.json b/documents/docfx.json index 28f27224..56d86754 100644 --- a/documents/docfx.json +++ b/documents/docfx.json @@ -44,17 +44,17 @@ "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 a modern rendering hardware interface for .NET.", "_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": { "baseUrl": "https://qian-o.github.io/Zenith.NET/" } } -} \ No newline at end of file +} diff --git a/documents/docs/best-practices.md b/documents/docs/best-practices.md deleted file mode 100644 index f95fb574..00000000 --- a/documents/docs/best-practices.md +++ /dev/null @@ -1,12 +0,0 @@ -# Best Practices - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Resource lifecycle management -- Command batching strategies -- Data alignment requirements -- Common performance pitfalls -- Debugging tips diff --git a/documents/docs/concepts/command-model.md b/documents/docs/concepts/command-model.md deleted file mode 100644 index 9434a19e..00000000 --- a/documents/docs/concepts/command-model.md +++ /dev/null @@ -1,11 +0,0 @@ -# Command Model - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- The relationship between `CommandQueue` and `CommandBuffer` -- Command recording workflow -- Submission and synchronization -- Command buffer pooling and reuse mechanism diff --git a/documents/docs/concepts/graphics-context.md b/documents/docs/concepts/graphics-context.md deleted file mode 100644 index d68b3b89..00000000 --- a/documents/docs/concepts/graphics-context.md +++ /dev/null @@ -1,11 +0,0 @@ -# Graphics Context - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- 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 diff --git a/documents/docs/concepts/resource-binding.md b/documents/docs/concepts/resource-binding.md deleted file mode 100644 index 15772c1c..00000000 --- a/documents/docs/concepts/resource-binding.md +++ /dev/null @@ -1,10 +0,0 @@ -# Resource Binding - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Declaring bindings with `ResourceLayout` -- Binding resources with `ResourceTable` -- Index scheme differences across backends diff --git a/documents/docs/features/compute.md b/documents/docs/features/compute.md deleted file mode 100644 index 06a584ee..00000000 --- a/documents/docs/features/compute.md +++ /dev/null @@ -1,10 +0,0 @@ -# Compute - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Thread group size configuration -- Dispatching compute work with `Dispatch` -- Resource sharing with graphics pipelines diff --git a/documents/docs/features/graphics.md b/documents/docs/features/graphics.md deleted file mode 100644 index ef2bcd26..00000000 --- a/documents/docs/features/graphics.md +++ /dev/null @@ -1,11 +0,0 @@ -# Graphics - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Render states: Rasterizer, DepthStencil, Blend -- Input layout configuration for vertex data -- Shader stages: Vertex, Pixel -- Output configuration diff --git a/documents/docs/features/mesh-shading.md b/documents/docs/features/mesh-shading.md deleted file mode 100644 index aaff57ce..00000000 --- a/documents/docs/features/mesh-shading.md +++ /dev/null @@ -1,10 +0,0 @@ -# Mesh Shading - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Meshlet concept and data structures -- Mesh and amplification shaders -- Dispatching mesh work with `DispatchMesh` diff --git a/documents/docs/features/ray-tracing.md b/documents/docs/features/ray-tracing.md deleted file mode 100644 index b988781a..00000000 --- a/documents/docs/features/ray-tracing.md +++ /dev/null @@ -1,12 +0,0 @@ -# Ray Tracing - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- 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 diff --git a/documents/docs/fundamentals/bindless-resources.md b/documents/docs/fundamentals/bindless-resources.md new file mode 100644 index 00000000..7441f9db --- /dev/null +++ b/documents/docs/fundamentals/bindless-resources.md @@ -0,0 +1,99 @@ +# Bindless Resources + +Shaders access Zenith.NET resources through `ResourceHandle` values. Store the handles in constant data, upload that data to a constant buffer, and declare matching typed handles in Slang. + +## Select a Handle + +Choose the handle that matches the shader access: + +| C# resource | Handle | Slang resource | +|--------------|--------|----------------| +| `Buffer` / `BufferView` | `ConstantHandle` | `ConstantBuffer` | +| `Buffer` / `BufferView` | `StorageReadOnlyHandle` | `StructuredBuffer` | +| `Buffer` / `BufferView` | `StorageReadWriteHandle` | `RWStructuredBuffer` | +| `Texture` / `TextureView` | `SampledHandle` | `Texture1D`, `Texture2D`, `Texture3D`, or cube texture types | +| `Texture` / `TextureView` | `StorageHandle` | `RWTexture1D`, `RWTexture2D`, or `RWTexture3D` | +| `Sampler` | `Handle` | `SamplerState` or `SamplerComparisonState` | +| `TopLevelAccelerationStructure` | `Handle` | `RaytracingAccelerationStructure` | + +The resource description must include the usage required by the selected handle. + +## Define Constant Data + +Use an unmanaged C# structure whose layout matches the shader structure: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 8)] +file struct Constants +{ + [FieldOffset(0)] + public ResourceHandle Resource; +} +``` + +Populate the structure with handles from the resources or views used by the command: + +```csharp +Constants constants = new() { Resource = texture.SampledHandle }; +``` + +Create a constant buffer and upload the structure: + +```csharp +BufferDesc desc = new() +{ + SizeInBytes = sizeof(Constants), + StrideInBytes = 0, + Usages = BufferUsages.Constant | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly +}; + +Buffer buffer = context.CreateBuffer(desc); +buffer.Upload(0, new() +{ + Pointer = &constants, + SizeInBytes = sizeof(Constants) +}); +``` + +Verify field offsets against the Slang layout, especially when a structure contains vectors, matrices, or nested records. + +## Declare Shader Handles + +Declare matching typed handles and expose the record as `ConstantBuffer`: + +```slang +struct Constants +{ + DescriptorHandle Resource; +}; + +ConstantBuffer constants; +``` + +## Bind the Constants + +Bind the constant buffer after setting the pipeline: + +```csharp +commandBuffer.SetConstantBuffer(buffer, 0); +``` + +The second argument is the byte offset into the buffer. + +## Use Views + +`Buffer` and `Texture` expose handles for the whole resource. Create a `BufferView` when a shader needs a byte subrange with a specific stride, and create a `TextureView` when it needs selected mip levels or array layers. Use `sizeof(T)` for typed buffer sizes and strides. + +Views do not own their source resource. Keep both the view and its source alive while submitted work uses the handle. + +## Match Usage and Synchronization + +A handle does not change a texture layout or create a memory dependency: + +- Sampled access requires `TextureUsages.Sampled` and `TextureLayout.Sampled`. +- Storage texture access requires `TextureUsages.Storage` and `TextureLayout.Storage`. +- Storage buffer access requires the matching storage usage. +- Producer/consumer access without a layout change requires a `Barrier`. + +`ResourceHandle` does not own its resource. Keep the resource or view alive through the final submission that uses the handle, and update constant data when replacing it. diff --git a/documents/docs/fundamentals/commands.md b/documents/docs/fundamentals/commands.md new file mode 100644 index 00000000..17e12812 --- /dev/null +++ b/documents/docs/fundamentals/commands.md @@ -0,0 +1,58 @@ +# Commands + +GPU work is recorded in a `CommandBuffer` borrowed from its `CommandQueue`. Recording preserves command order, but execution begins only after submission. + +## Record and Submit Work + +Borrow a command buffer from the queue that should execute the work: + +```csharp +CommandBuffer commandBuffer = context.ComputeQueue.CommandBuffer(); + +commandBuffer.Transition(texture, default, TextureLayout.Undefined, TextureLayout.Storage); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetConstantBuffer(buffer, 0); +commandBuffer.Dispatch(groupCountX, groupCountY, 1); + +commandBuffer.Transition(texture, default, TextureLayout.Storage, TextureLayout.Sampled); + +commandBuffer.Submit(); +``` + +A command buffer is queue-owned and valid only for the current recording. Set the matching pipeline before binding state or issuing work, submit it immediately, and never store, reuse, or dispose it. + +## Track Completion + +`Submit()` returns a `TimelineValue`. Use `IsCompleted` only for a non-blocking status query. Call `Wait()` when CPU code must observe GPU results, including command-buffer downloads. To order queues without blocking the CPU, pass the producer value to the consumer submission. See [Synchronization](synchronization.md). + +## Record a Render Pass + +Transition attachments before beginning a render pass: + +```csharp +commandBuffer.Transition(texture, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); +commandBuffer.BeginRenderPass([ColorAttachment.Clear(texture, default)], null); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetVertexBuffer(buffer, 0, 0); +commandBuffer.Draw(vertexCount, 1, 0, 0); + +commandBuffer.EndRenderPass(); +``` + +`BeginRenderPass` initializes viewports and scissors from the attachment size. Set them afterward when rendering to a smaller region. + +## Transfer Data + +Command buffers can upload, download, copy, and resolve resources. These operations can be batched with later GPU work. + +Texture transfer commands do not change texture layouts. Record the required `CopySrc`, `CopyDst`, `ResolveSrc`, or `ResolveDst` transitions around them. + +For simple one-off transfers, `Buffer.Upload`, `Buffer.Download`, `Texture.Upload`, and `Texture.Download` complete the transfer before returning. + +## Label GPU Work + +Use `BeginDebugEvent`, `EndDebugEvent`, and `InsertDebugMarker` to identify recorded GPU work in diagnostics. + +Query and acceleration-structure commands follow the same record-then-submit model. See [Queries](queries.md) for visibility and timestamp results, and [Ray Tracing](../workloads/ray-tracing.md) for acceleration-structure builds. diff --git a/documents/docs/fundamentals/queries.md b/documents/docs/fundamentals/queries.md new file mode 100644 index 00000000..5b2c6d9c --- /dev/null +++ b/documents/docs/fundamentals/queries.md @@ -0,0 +1,85 @@ +# Queries + +Queries report information produced while the GPU executes commands. Create a query heap from the same `GraphicsContext` as the command buffer, record query operations, and read the results after the submission has completed. + +## Create a Query Heap + +Choose a query heap description for the result you need: + +| Description | Result | Recording commands | +|-------------|--------|--------------------| +| `QueryHeapDesc.Occlusion` | Number of samples that pass the visibility tests | `BeginQuery` and `EndQuery` | +| `QueryHeapDesc.BinaryOcclusion` | Whether any sample passes the visibility tests | `BeginQuery` and `EndQuery` | +| `QueryHeapDesc.Timestamp` | GPU timestamp values | `WriteTimestamp` | + +The count in the description is the number of query slots. Use a separate index for each result that must remain available at the same time: + +```csharp +QueryHeap queryHeap = context.CreateQueryHeap(QueryHeapDesc.Occlusion(1)); +``` + +Keep the query heap alive until every submitted command that refers to it has completed. + +## Record Occlusion Queries + +While a render pass is active, surround the drawing commands whose visibility you want to measure with `BeginQuery` and `EndQuery`. Keep both query commands in the same render pass: + +```csharp +commandBuffer.BeginRenderPass([ColorAttachment.Load(colorTarget)], null); +commandBuffer.BeginQuery(queryHeap, 0); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.Draw(vertexCount, 1, 0, 0); + +commandBuffer.EndQuery(queryHeap, 0); +commandBuffer.EndRenderPass(); +``` + +Use an occlusion heap for a sample count or a binary occlusion heap when only a visible-or-not result is needed. Submit the command buffer before reading the result. + +## Record Timestamps + +Write two timestamps around the work whose GPU duration you want to measure. Timestamp results are raw `ulong` values until the queue converts their difference: + +```csharp +QueryHeap queryHeap = context.CreateQueryHeap(QueryHeapDesc.Timestamp(2)); +CommandBuffer commandBuffer = context.ComputeQueue.CommandBuffer(); + +commandBuffer.WriteTimestamp(queryHeap, 0); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.Dispatch(groupCountX, groupCountY, groupCountZ); + +commandBuffer.WriteTimestamp(queryHeap, 1); + +TimelineValue completion = commandBuffer.Submit(); +completion.Wait(); +``` + +Read the two results and ask the same queue that recorded them for the elapsed time: + +```csharp +Span timestamps = stackalloc ulong[2]; +queryHeap.GetResults(timestamps, 0); + +double elapsedNanoseconds = context.ComputeQueue.GetElapsedNanoseconds(timestamps[0], timestamps[1]); +``` + +The two timestamp values must come from the same queue. `GetElapsedNanoseconds` returns the elapsed GPU time between the values; it does not represent a wall-clock time. + +## Read Results + +Wait for the submission that writes the queries before calling `GetResults`: + +```csharp +Span results = stackalloc ulong[1]; +queryHeap.GetResults(results, 0); +``` + +The span length selects how many consecutive results to read, and `startIndex` selects the first query slot. Keep the span within the query heap's count. Reuse a slot only after the submission that last wrote it has completed. + +## Manage Query Lifetime + +Query heaps are application-owned resources. Dispose them after the command submissions that use them have completed. Command buffers remain queue-owned borrows; submit them before reading their query results and do not dispose them directly. + +Queries use the same recording and completion model as other commands. Review [Commands](commands.md) for command-buffer lifetime and [Synchronization](synchronization.md) for submission dependencies. \ No newline at end of file diff --git a/documents/docs/fundamentals/runtime.md b/documents/docs/fundamentals/runtime.md new file mode 100644 index 00000000..dfd3ae11 --- /dev/null +++ b/documents/docs/fundamentals/runtime.md @@ -0,0 +1,79 @@ +# Runtime + +`GraphicsContext` is the root of a Zenith.NET application. It identifies the selected graphics API, reports device capabilities, exposes command queues, and creates resources. + +Create resources that work together from the same context. + +## Create a Context + +Add the core package and at least one graphics API package: + +| Package | Graphics API | Platform support | +|---------|--------------|------------------| +| `Zenith.NET.DirectX12` | DirectX 12 | Windows | +| `Zenith.NET.Metal` | Metal 4 | Apple platforms | +| `Zenith.NET.Vulkan` | Vulkan 1.4 | Windows, Apple platforms, Android, and Linux | + +Each package adds a context factory. For example: + +```csharp +GraphicsContext context = GraphicsContext.CreateVulkan(useValidationLayer: true); +``` + +Use `CreateDirectX12`, `CreateMetal`, or `CreateVulkan` for the selected backend. Reference only the packages your application can select. The selected API is available through `context.GraphicsApi`. + +## Check Capabilities + +Inspect optional features before creating the resources that require them: + +```csharp +Console.WriteLine(context.Capabilities.DeviceName); + +if (context.Capabilities.RayTracingSupported) +{ + // Acceleration structures and inline RayQuery can be used. +} + +if (context.Capabilities.MeshShadingSupported) +{ + // Mesh shading pipelines can be created. +} +``` + +Use capabilities to select an application feature or a compatible fallback. + +## Choose a Queue + +Every context exposes three command queues: + +| Property | Intended work | +|----------|---------------| +| `GraphicsQueue` | Rendering, compute, copies, and presentation | +| `ComputeQueue` | Compute and acceleration-structure work | +| `TransferQueue` | Buffer transfers and copy commands | + +Borrow a command buffer from the queue that will execute the work, such as `context.GraphicsQueue.CommandBuffer()`. Record and submit it immediately; the queue owns and recycles it. + +See [Commands](commands.md) for recording and submission. + +## Enable Validation + +Enable validation during development and subscribe before creating resources: + +```csharp +GraphicsContext context = GraphicsContext.CreateVulkan(useValidationLayer: true); + +context.ValidationMessage += static (_, args) => Console.WriteLine($"[{args.Severity}] {args.Message}"); +``` + +Set resource names when they help identify objects in diagnostics: + +```csharp +buffer.Name = "Buffer"; +``` + +Validation messages have `Error`, `Warning`, or `Info` severity. + +## Dispose Objects + +Zenith.NET resources implement `IDisposable`. After rendering has stopped, dispose application-owned resources and dispose the context last. Command buffers are queue-owned borrows for one recording and submission; never dispose or retain them. diff --git a/documents/docs/fundamentals/shaders.md b/documents/docs/fundamentals/shaders.md new file mode 100644 index 00000000..20e76cbd --- /dev/null +++ b/documents/docs/fundamentals/shaders.md @@ -0,0 +1,71 @@ +# Shaders + +Zenith.NET compiles Slang entry points for the active `GraphicsApi`, then creates `Shader` objects from the resulting descriptions. Pipeline creation combines those shader objects with workload-specific state. + +## Compile from a File + +Pass the active graphics API, the source file, and the entry-point name to `ZenithCompiler.CompileFromFile`: + +```csharp +ShaderDesc vertexDesc = ZenithCompiler.CompileFromFile(context.GraphicsApi, "Shaders/Basic.slang", "VSMain"); + +Shader vertexShader = context.CreateShader(vertexDesc); +``` + +The `name` argument identifies the Slang entry point. Compile each entry point required by the pipeline, then assign the resulting shaders to its description. + +## Compile from Source + +Use `CompileFromSource` when the Slang source is already available as a string: + +```csharp +const string source = """ +[shader("compute")] +[numthreads(8, 8, 1)] +void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ +} +"""; + +ShaderDesc computeDesc = ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "CSMain"); + +Shader computeShader = context.CreateShader(computeDesc); +``` + +Provide additional lookup directories through `searchPaths` when the shader imports other source files: + +```csharp +ShaderDesc desc = ZenithCompiler.CompileFromFile(context.GraphicsApi, "Shaders/Lighting.slang", "PSMain", ["Shaders", "Shaders/Shared"]); +``` + +## Create a Pipeline + +Assign compiled shaders to the pipeline description that matches the workload: + +```csharp +ComputePipeline pipeline = context.CreateComputePipeline(new() { ComputeShader = computeShader }); +``` + +Graphics pipelines use vertex and fragment shaders, while mesh shading pipelines use mesh and fragment shaders and may also use a task shader. For both pipeline types, the attachment formats and render state must match the render pass where the pipeline is used. + +## Use Thread-Group Size + +Compute shader reflection stores the entry point's thread-group dimensions in `ShaderDesc.ThreadGroupSize`. Use those values when converting a workload size into dispatch groups: + +```csharp +ThreadGroupSize threadGroupSize = computeShader.Desc.ThreadGroupSize; + +uint groupCountX = (width + threadGroupSize.X - 1) / threadGroupSize.X; +uint groupCountY = (height + threadGroupSize.Y - 1) / threadGroupSize.Y; + +commandBuffer.SetPipeline(pipeline); +commandBuffer.Dispatch(groupCountX, groupCountY, 1); +``` + +Round each dimension up to a complete group and handle threads outside the workload in the shader. + +## Manage Shader Lifetime + +Shader objects are required while creating a pipeline and can be disposed after pipeline creation returns. Keep the pipeline alive until every submitted command that uses it has completed. + +Use [Rasterization](../workloads/rasterization.md), [Compute](../workloads/compute.md), [Ray Tracing](../workloads/ray-tracing.md), and [Mesh Shading](../workloads/mesh-shading.md) for workload-specific pipeline descriptions and commands. \ No newline at end of file diff --git a/documents/docs/fundamentals/synchronization.md b/documents/docs/fundamentals/synchronization.md new file mode 100644 index 00000000..09a5587c --- /dev/null +++ b/documents/docs/fundamentals/synchronization.md @@ -0,0 +1,79 @@ +# Synchronization + +Synchronization makes data produced by earlier GPU work available to later work. Choose the mechanism that matches the dependency: + +| Mechanism | Use it when | +|-----------|-------------| +| `Barrier(before, after)` | Commands in one command buffer share data without changing a texture layout | +| `Transition(texture, subresource, before, after)` | A texture changes its access role | +| `Submit(waitValues...)` | A submission depends on work submitted to another queue | + +## Add a Memory Barrier + +Use `Barrier` when a later command consumes memory written by an earlier command and the resource layout does not change: + +```csharp +commandBuffer.SetPipeline(pipeline); +commandBuffer.Dispatch(groupCount, 1, 1); + +commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + +commandBuffer.SetPipeline(nextPipeline); +commandBuffer.Dispatch(nextGroupCount, 1, 1); +``` + +Common cases include storage-buffer producer/consumer chains and compute-generated indirect arguments. Select the stages that perform the producing and consuming work, and combine flags when several stages consume the result. + +Use `BarrierStages.All` only when a narrower dependency cannot describe the work. + +## Transition a Texture + +Supply the current and next layout whenever a texture changes how it is used: + +```csharp +commandBuffer.Transition(texture, default, TextureLayout.Undefined, TextureLayout.Storage); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetConstantBuffer(buffer, 0); +commandBuffer.Dispatch(groupCountX, groupCountY, 1); + +commandBuffer.Transition(texture, default, TextureLayout.Storage, TextureLayout.Sampled); +``` + +`default` selects mip level zero and array layer zero. Pass a `TextureSubresource` with the required `MipLevel` and `ArrayLayer` when another subresource is needed. + +Use `Undefined` as the source only when previous contents can be discarded. Copy, resolve, and command-buffer upload or download operations do not insert transitions. The `Texture.Upload` and `Texture.Download` convenience methods perform their declared current-to-final layout transitions. + +The principal layouts are listed in declaration order: + +| Layout | Access role | +|--------|-------------| +| `Undefined` | Previous contents are discarded | +| `Common` | General access and shared-texture presentation paths | +| `Sampled` | Sampled texture reads | +| `Storage` | Storage texture reads and writes | +| `ColorAttachment` | Color attachment access | +| `DepthStencilAttachment` | Writable depth/stencil attachment access | +| `DepthStencilReadOnly` | Read-only depth/stencil access | +| `CopySrc` / `CopyDst` | Copy source or destination | +| `ResolveSrc` / `ResolveDst` | Resolve source or destination | +| `Present` | Presentation | + +## Order Work Across Queues + +Pass a producer's `TimelineValue` to the dependent submission: + +```csharp +CommandBuffer commandBuffer = context.TransferQueue.CommandBuffer(); +commandBuffer.Upload(buffer, 0, data); +TimelineValue timelineValue = commandBuffer.Submit(); + +commandBuffer = context.ComputeQueue.CommandBuffer(); +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetConstantBuffer(buffer, 0); +commandBuffer.Dispatch(groupCountX, groupCountY, 1); + +commandBuffer.Submit(timelineValue); +``` + +Passing a producer `TimelineValue` to `Submit` orders the consumer submission after that value without blocking the CPU. Call `Wait()` only when CPU code must observe GPU results. diff --git a/documents/docs/index.md b/documents/docs/index.md index bb3c67bc..df51b4d1 100644 --- a/documents/docs/index.md +++ b/documents/docs/index.md @@ -1,102 +1,34 @@ -# Documentation +# RHI Guide -This section provides conceptual documentation to help you understand Zenith.NET's architecture and best practices. +Use this guide to learn the Zenith.NET programming model. It covers the C# objects and workflows used to create resources, record GPU work, and present results. -> [!NOTE] -> Looking for step-by-step coding guides? Check out the [Tutorials](../tutorials/index.md) section. +## Fundamentals -## Design Philosophy +Start here if you are new to Zenith.NET: -Zenith.NET abstracts DirectX 12, Metal 4, and Vulkan 1.4 under a unified API. The design follows a clear principle: **adopt the latest API versions and expose only the capabilities shared across all three backends**. This means platform-specific features are intentionally excluded to maintain a consistent cross-platform experience. +1. [Runtime](fundamentals/runtime.md) introduces the graphics context, capabilities, queues, and object lifetime. +2. [Commands](fundamentals/commands.md) shows how to record and submit work. +3. [Synchronization](fundamentals/synchronization.md) explains barriers, texture transitions, and queue dependencies. +4. [Shaders](fundamentals/shaders.md) shows how to compile Slang entry points and create shader objects. +5. [Bindless Resources](fundamentals/bindless-resources.md) shows how shaders access resources through handles. +6. [Queries](fundamentals/queries.md) shows how to collect visibility results and measure GPU work. -Each backend targets real-world device coverage: +## Resources -| Backend | Strategy | -|---------|----------| -| **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 | +- [Heaps](resources/heaps.md) covers placed resources, allocation requirements, offsets, and lifetime. +- [Buffers](resources/buffers.md) covers creation, data transfer, mapping, and views. +- [Textures](resources/textures.md) covers creation, views, uploads, layouts, resolves, and samplers. -For detailed backend selection guidance, see [Backend Selection](platform/backend-selection.md). +## Workloads -## Core Concepts +- [Rasterization](workloads/rasterization.md) covers graphics pipelines, render passes, and draw commands. +- [Compute](workloads/compute.md) covers compute pipelines and dispatch commands. +- [Ray Tracing](workloads/ray-tracing.md) covers acceleration structures and inline ray queries. +- [Mesh Shading](workloads/mesh-shading.md) covers mesh shading pipelines and dispatch commands. -### Graphics Context +## Presentation -The `GraphicsContext` is the central hub of Zenith.NET. It abstracts the underlying graphics API and provides: +- [Swap Chains](presentation/swap-chains.md) shows how to render to a window and present a frame. +- [Views](presentation/views.md) shows how to render through supported .NET UI controls. -- **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 - -Resources are bound to shaders through two types: - -| Type | Purpose | -|------|---------| -| `ResourceLayout` | Declares *what* resources a shader expects (binding slots, types) | -| `ResourceTable` | Provides *actual* resources matching a layout | - -Pipelines reference a single `ResourceLayout`, and you bind a corresponding `ResourceTable` before draw/dispatch calls. - -## 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 - -- **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 - -### Data Alignment - -Zenith.NET defines alignment constants in `GraphicsContext` 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 | - -## Next Steps - -- [Tutorials](../tutorials/index.md) - Hands-on coding examples -- [API Reference](../api/index.md) - Detailed type documentation +Follow the [Tutorials](../tutorials/index.md) to build complete examples. Use the [API Reference](../api/index.md) for exact types and signatures. diff --git a/documents/docs/platform/backend-selection.md b/documents/docs/platform/backend-selection.md deleted file mode 100644 index 6673bbb3..00000000 --- a/documents/docs/platform/backend-selection.md +++ /dev/null @@ -1,11 +0,0 @@ -# Backend Selection - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Platform and backend mapping -- Runtime detection and selection -- Checking capabilities before use -- Validation layer configuration diff --git a/documents/docs/platform/ui-frameworks.md b/documents/docs/platform/ui-frameworks.md deleted file mode 100644 index b8d8a8da..00000000 --- a/documents/docs/platform/ui-frameworks.md +++ /dev/null @@ -1,13 +0,0 @@ -# UI Framework Integration - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- `IZenithView` interface -- Avalonia integration -- .NET MAUI integration -- Windows Forms integration -- WinUI 3 and Uno Platform integration -- WPF integration diff --git a/documents/docs/presentation/swap-chains.md b/documents/docs/presentation/swap-chains.md new file mode 100644 index 00000000..6d3ff2f0 --- /dev/null +++ b/documents/docs/presentation/swap-chains.md @@ -0,0 +1,58 @@ +# Swap Chains + +A `Surface` identifies a window and its drawable size. `SwapChain.Drawable` returns the texture to render for the current frame. + +## Create a Surface + +Create the surface that matches the application's window system and native handles, such as `Surface.Win32(hwnd, width, height)`. + +Zenith.NET provides `Win32`, `Wayland`, `Xlib`, `Android`, and `Apple` surface factories. Select the factory that corresponds to the handles supplied by the window host. Use a [View integration](views.md) when a supported UI framework should manage the surface. + +## Create a Swap Chain + +Create the swap chain from the same context used for rendering: + +```csharp +SwapChain swapChain = context.CreateSwapChain(new() +{ + Surface = surface, + Format = PixelFormat.B8G8R8A8UNorm +}); +``` + +The graphics pipeline color format must match the swap-chain format. + +## Render and Present + +Borrow a graphics command buffer for each frame, record the current drawable, and wait for the submission to complete before presentation: + +```csharp +CommandBuffer commandBuffer = context.GraphicsQueue.CommandBuffer(); + +commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); +commandBuffer.BeginRenderPass([ColorAttachment.Clear(swapChain.Drawable, default)], null); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetVertexBuffer(buffer, 0, 0); +commandBuffer.Draw(vertexCount, 1, 0, 0); + +commandBuffer.EndRenderPass(); +commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + +commandBuffer.Submit().Wait(); +swapChain.Present(); +``` + +Wait for the graphics submission before calling `Present()`. Do not dispose or retain the command buffer or drawable; borrow both again for the next frame. + +## Resize + +Skip rendering while either dimension is zero. Dispose and recreate application-owned size-dependent textures, then call `swapChain.Resize(width, height)` before rendering again. + +Recreate a pipeline only when its attachment formats or sample count change. + +## Refresh a Surface + +If the window system replaces its surface handle, create a new `Surface` and pass it to `swapChain.Refresh(...)`. + +Use `Resize` when only the dimensions change. Use `Refresh` when the handle or surface type changes. diff --git a/documents/docs/presentation/views.md b/documents/docs/presentation/views.md new file mode 100644 index 00000000..ddebbb14 --- /dev/null +++ b/documents/docs/presentation/views.md @@ -0,0 +1,48 @@ +# Views + +Zenith.NET provides controls for supported .NET UI frameworks. Each control implements `IZenithView` and manages its own frame presentation. + +## Framework Packages + +| Package | Framework | +|---|---| +| `Zenith.NET.Views.WinForms` | Windows Forms | +| `Zenith.NET.Views.WPF` | WPF | +| `Zenith.NET.Views.WinUI` | WinUI 3 and Uno | +| `Zenith.NET.Views.Maui` | .NET MAUI | +| `Zenith.NET.Views.Avalonia` | Avalonia | + +Add the package for the framework used by the application. + +## Connect a View + +Assign a `GraphicsContext`, then subscribe to updates and rendering: + +```csharp +view.GraphicsContext = context; + +view.RenderRequested += (_, args) => +{ + args.CommandBuffer.BeginRenderPass([ColorAttachment.Clear(args.Drawable, default)], null); + + args.CommandBuffer.SetPipeline(pipeline); + args.CommandBuffer.Draw(vertexCount, 1, 0, 0); + + args.CommandBuffer.EndRenderPass(); +}; +``` + +Both event argument types provide `DeltaSeconds` and `TotalSeconds`. `RenderEventArgs` also provides the current `CommandBuffer` and `Drawable`. + +## Follow the Callback Contract + +The command buffer and drawable are borrowed for the duration of the synchronous `RenderRequested` callback. Within the callback: + +- Record commands into `args.CommandBuffer`. +- Treat `args.Drawable` as a `ColorAttachment`. +- End every render pass before returning. +- Do not submit, wait, dispose, or retain either object. + +The View completes and presents the frame after the callback returns. Dispose application-owned pipelines and resources separately, and keep the assigned context alive until the control has released its rendering resources. + +For graphics API selection, see [Runtime](../fundamentals/runtime.md). Use [Swap Chains](swap-chains.md) when the application manages a window without a View integration. diff --git a/documents/docs/resources/buffers.md b/documents/docs/resources/buffers.md index eca9431e..3b8a43d1 100644 --- a/documents/docs/resources/buffers.md +++ b/documents/docs/resources/buffers.md @@ -1,11 +1,72 @@ # Buffers -> [!NOTE] -> This page is under construction. +Buffers store linear data such as vertices, indices, constants, structured records, and indirect arguments. A `BufferDesc` defines the byte size, structured-element stride, permitted uses, and residency. -This topic covers: +## Create a Standalone Buffer -- Buffer types: Vertex, Index, Constant, Structured -- Creating and using `BufferView` -- Uploading data with the `Upload` method -- Map/Unmap operations +When explicit heap placement is unnecessary, define the buffer properties and create it directly from the context: + +```csharp +BufferDesc desc = new() +{ + SizeInBytes = sizeof(Element) * count, + StrideInBytes = 0, + Usages = BufferUsages.Vertex | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly +}; + +Buffer buffer = context.CreateBuffer(desc); +``` + +Set `Usages` to every operation the buffer must support and select its CPU access with `Residency`. + +`StrideInBytes` describes structured elements. Use zero for unstructured data. + +## Choose Memory Residency + +| Residency | CPU access | Typical use | +|-----------|------------|-------------| +| `GpuOnly` | Not mappable | Persistent GPU data | +| `CpuReadOnly` | Read through `Map()` | Readback data | +| `CpuWriteOnly` | Write through `Map()` | Frequently updated constants and staging data | + +Prefer `GpuOnly` unless CPU access is part of the buffer's regular use. + +## Upload and Download + +`Buffer.Upload` copies data into a buffer and completes before returning: + +```csharp +buffer.Upload(0, new() +{ + Pointer = data, + SizeInBytes = sizeof(Element) * count +}); +``` + +`Buffer.Download` follows the same pattern and writes into caller-provided memory before returning. + +Use command-buffer transfers to batch several operations: + +```csharp +CommandBuffer commandBuffer = context.TransferQueue.CommandBuffer(); +commandBuffer.Upload(buffer, 0, data); + +TimelineValue timelineValue = commandBuffer.Submit(); +``` + +Record additional transfers before `Submit()` to include them in the same submission. Pass `timelineValue` to a submission that consumes the buffers. For `CommandBuffer.Download`, keep the destination memory valid until the submission's `TimelineValue` has been waited on. + +## Map CPU-Visible Memory + +Map only buffers created with `CpuReadOnly` or `CpuWriteOnly` residency. Pair each `Map()` with `Unmap()`, and use mapping for repeated CPU access. Use `Upload` or `Download` for individual transfers. + +## Create a View + +A `BufferView` selects a byte range and structured element stride. Use `sizeof(T)` for the stride and derive the selected byte range from the element count. + +Use `BufferViewDesc.Constant`, `StorageReadOnly`, or `StorageReadWrite` to match the intended shader access. See [Bindless Resources](../fundamentals/bindless-resources.md) for shader declarations. + +See [Heaps](heaps.md) for allocation requirements and explicit buffer placement. + +Buffer views depend on their source buffer. Keep buffers and views alive until all submissions that use them have completed. Use a [memory barrier](../fundamentals/synchronization.md#add-a-memory-barrier) when later GPU work consumes buffer data written earlier in the same command stream. diff --git a/documents/docs/resources/heaps.md b/documents/docs/resources/heaps.md new file mode 100644 index 00000000..0b101976 --- /dev/null +++ b/documents/docs/resources/heaps.md @@ -0,0 +1,82 @@ +# Heaps + +A `Heap` is an explicit memory allocation. Create placed buffers and textures from it with `Heap.CreateBuffer` and `Heap.CreateTexture`, supplying each resource's byte offset. Use a heap when several compatible resources should share an allocation or when the application needs to control resource placement. + +## Query Requirements + +Define every resource description explicitly and query it before sizing the heap. The returned `SizeInBytes` is the allocation space required by the resource, and `AlignmentInBytes` is the required alignment of its offset in the heap. + +## Place Buffers + +Define the buffer description, query its requirements, then create a compatible heap and place the buffer. Offset zero satisfies the first resource's alignment requirement: + +```csharp +BufferDesc desc = new() +{ + SizeInBytes = sizeof(Element) * count, + StrideInBytes = 0, + Usages = BufferUsages.Vertex | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly +}; + +SizeAndAlignment sizeAndAlignment = context.GetSizeAndAlignment(desc); + +Heap heap = context.CreateHeap(HeapDesc.GpuOnly(sizeAndAlignment.SizeInBytes)); +Buffer buffer = heap.CreateBuffer(0, desc); +``` + +Only add another description and requirement query when placing another resource. Align each later offset to that resource's `AlignmentInBytes`, and size the heap through the final resource. + +Use the `SizeInBytes` and `AlignmentInBytes` returned by `GetSizeAndAlignment` when calculating offsets and heap size. Do not substitute `BufferDesc.SizeInBytes` for the returned `SizeInBytes`. + +## Place Textures + +Define the texture description, query its requirements, then create a compatible heap and place the texture. Offset zero satisfies the first resource's alignment requirement: + +```csharp +TextureDesc desc = new() +{ + Type = TextureType.Texture2D, + Format = PixelFormat.R8G8B8A8UNorm, + Width = width, + Height = height, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.TransferDst +}; + +SizeAndAlignment sizeAndAlignment = context.GetSizeAndAlignment(desc); + +Heap heap = context.CreateHeap(HeapDesc.GpuOnly(sizeAndAlignment.SizeInBytes)); +Texture texture = heap.CreateTexture(0, desc); +``` + +For multiple textures, align each offset to that texture's `AlignmentInBytes`. Set `HeapDesc.SizeInBytes` to at least the maximum `offset + SizeInBytes` of all placed resources. + +## Match Residency + +For a placed buffer, use the same residency in its `BufferDesc` and the containing `HeapDesc`: + +| Heap helper | Resource residency | +|-------------|--------------------| +| `HeapDesc.GpuOnly` | `MemoryResidency.GpuOnly` | +| `HeapDesc.CpuReadOnly` | `MemoryResidency.CpuReadOnly` | +| `HeapDesc.CpuWriteOnly` | `MemoryResidency.CpuWriteOnly` | + +Create texture heaps with `HeapDesc.GpuOnly`. Query each exact resource description on the context that creates the heap, use its returned size and alignment, and keep every resource range within `HeapDesc.SizeInBytes`. + +A heap does not add usages to a resource or replace required texture transitions and memory barriers. + +## Create Standalone Resources + +When explicit placement is unnecessary, create a standalone buffer or texture directly from the context. + +Standalone resources use the same descriptions, usages, layouts, and synchronization rules, but do not require a `Heap`. Continue with [Buffers](buffers.md) and [Textures](textures.md) for resource-specific workflows. + +## Manage Lifetime + +Placed resources use memory owned by the heap. After rendering has stopped using them, dispose every placed buffer and texture before disposing the heap. + +`ResourceHandle` does not own its resource. Keep each placed resource, and any explicit view used to obtain a stored handle, alive through the final submission that uses the handle. Dispose explicit views before their source resources, and update constant data when replacing a stored handle. diff --git a/documents/docs/resources/samplers.md b/documents/docs/resources/samplers.md deleted file mode 100644 index 8243386e..00000000 --- a/documents/docs/resources/samplers.md +++ /dev/null @@ -1,10 +0,0 @@ -# Samplers - -> [!NOTE] -> This page is under construction. - -This topic covers: - -- Address modes: Wrap, Clamp, Mirror, Border -- Filter options for minification, magnification, and mipmapping -- Level of Detail (LOD) configuration diff --git a/documents/docs/resources/textures.md b/documents/docs/resources/textures.md index 5013a40c..42970d39 100644 --- a/documents/docs/resources/textures.md +++ b/documents/docs/resources/textures.md @@ -1,12 +1,91 @@ # Textures -> [!NOTE] -> This page is under construction. +Textures store formatted one-, two-, or three-dimensional data. A `TextureDesc` defines the shape, format, sample count, and permitted uses. Each texture subresource has a layout supplied by the application when recording commands. -This topic covers: +## Create a Standalone Texture -- Texture types: 2D, 3D, Cube, Array -- Creating and using `TextureView` -- Pixel formats and sample counts -- MipMaps generation -- Uploading texture data +When explicit heap placement is unnecessary, create a standalone texture from the context: + +```csharp +TextureDesc desc = new() +{ + Type = TextureType.Texture2D, + Format = PixelFormat.R8G8B8A8UNorm, + Width = width, + Height = height, + Depth = 1, + MipLevels = mipLevels, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.TransferDst +}; + +Texture texture = context.CreateTexture(desc); +``` + +Other dimensional helpers include `Texture1D`, `Texture3D`, and `TextureCube`. They create sampled textures with `TransferDst` usage. Attachment helpers create sampled color or depth/stencil render targets. + +Add usages to the description before creation when a texture needs another role. For example, add `TextureUsages.Storage` before creating a storage texture. + +Usage declares what the texture may do. Layout describes how one subresource is used by recorded commands. + +## Create Attachments + +Use `TextureDesc.ColorAttachment` and `TextureDesc.DepthStencilAttachment` for render targets. Transition each attachment to its matching layout before beginning a render pass. + +## Upload Texture Data + +Provide the source pointer together with row and slice strides: + +```csharp +texture.Upload(default, TextureLayout.Undefined, TextureLayout.Sampled, default, extent, data); +``` + +`Texture.Upload` completes before returning. Supply the known current layout and required final layout; on return, the uploaded subresource is in that final layout. + +Use `CommandBuffer.Upload` with explicit transitions when several transfers should share one submission. + +## Load an Image + +`Zenith.NET.Extensions.ImageSharp` loads an image into a sampled texture with `LoadTextureFromFile` or `LoadTextureFromStream`. + +Enable `generateMipMaps` when the texture should contain a complete mip chain. + +## Select a Subresource + +A `TextureSubresource` selects one mip level and array layer. Use `default` for mip level zero and array layer zero, or initialize both fields for another subresource. + +Track the current layout of every subresource used by the application. See [Synchronization](../fundamentals/synchronization.md#transition-a-texture) for layout rules. + +## Create a Texture View + +A `TextureView` selects a type, format, mip range, or array range. Create it from the context with the matching `TextureViewDesc` helper. + +The selected range and format must be compatible with the source texture. Views do not own their source texture. + +## Create a Sampler + +Samplers define filtering and addressing independently from textures. Use a `SamplerDesc` preset when it matches the required behavior. + +Other presets include `LinearClamp`, `PointWrap`, `PointClamp`, and `Anisotropic(maxAnisotropy)`. Create a custom `SamplerDesc` for comparison sampling, border colors, or a specific LOD range. + +Pass `sampler.Handle` beside the sampled texture handle and declare matching `DescriptorHandle` and `DescriptorHandle` fields in Slang. + +## Resolve and Read Back + +Resolve a multisampled texture into a compatible single-sampled texture: + +```csharp +commandBuffer.Transition(texture, default, TextureLayout.ColorAttachment, TextureLayout.ResolveSrc); +commandBuffer.Transition(nextTexture, default, TextureLayout.Undefined, TextureLayout.ResolveDst); + +commandBuffer.ResolveTexture(texture, default, nextTexture, default); +``` + +Create resolve and download sources with `TextureUsages.TransferSrc`. Resolve destinations require `TextureUsages.TransferDst`. + +Texture copy, resolve, and command-buffer upload or download operations do not insert layout transitions. `Texture.Download` is the synchronous convenience path and performs its declared current-to-final transitions; use `CommandBuffer.Download` with explicit transitions when readback belongs to a larger submission. + +See [Heaps](heaps.md) for allocation requirements and explicit texture placement. + +Keep a texture alive while any view, handle, or submitted command refers to it. When replacing a size-dependent texture, recreate its views and update constant data that stores its handles. diff --git a/documents/docs/toc.yml b/documents/docs/toc.yml index 2f6ee1dd..66e7d6be 100644 --- a/documents/docs/toc.yml +++ b/documents/docs/toc.yml @@ -1,36 +1,44 @@ -- name: Docs +- name: RHI Guide href: index.md -- name: Core Concepts + +- name: Fundamentals items: - - name: Graphics Context - href: concepts/graphics-context.md - - name: Command Model - href: concepts/command-model.md - - name: Resource Binding - href: concepts/resource-binding.md + - name: Runtime + href: fundamentals/runtime.md + - name: Commands + href: fundamentals/commands.md + - name: Synchronization + href: fundamentals/synchronization.md + - name: Shaders + href: fundamentals/shaders.md + - name: Bindless Resources + href: fundamentals/bindless-resources.md + - name: Queries + href: fundamentals/queries.md + - name: Resources items: + - name: Heaps + href: resources/heaps.md - name: Buffers href: resources/buffers.md - name: Textures href: resources/textures.md - - name: Samplers - href: resources/samplers.md -- name: Features + +- name: Workloads items: - - name: Graphics - href: features/graphics.md + - name: Rasterization + href: workloads/rasterization.md - name: Compute - href: features/compute.md + href: workloads/compute.md - name: Ray Tracing - href: features/ray-tracing.md + href: workloads/ray-tracing.md - name: Mesh Shading - href: features/mesh-shading.md -- name: Platform + href: workloads/mesh-shading.md + +- name: Presentation items: - - name: Backend Selection - href: platform/backend-selection.md - - name: UI Framework Integration - href: platform/ui-frameworks.md -- name: Best Practices - href: best-practices.md + - name: Swap Chains + href: presentation/swap-chains.md + - name: Views + href: presentation/views.md diff --git a/documents/docs/workloads/compute.md b/documents/docs/workloads/compute.md new file mode 100644 index 00000000..a90138fe --- /dev/null +++ b/documents/docs/workloads/compute.md @@ -0,0 +1,48 @@ +# Compute + +Compute pipelines process data outside a render pass. Record compute work on the compute or graphics queue according to the surrounding workload. + +## Create a Compute Pipeline + +Define a Slang compute entry point and its thread-group size: + +```slang +[shader("compute")] +[numthreads(16, 16, 1)] +void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + // Process one element or pixel. +} +``` + +Compile the entry point and create its `Shader` as described in [Shaders](../fundamentals/shaders.md), then create the pipeline: + +```csharp +ComputePipeline pipeline = context.CreateComputePipeline(new() { ComputeShader = shader }); +``` + +## Dispatch Work + +Calculate group counts from the workload dimensions and the shader's `[numthreads]` values, rounding each dimension up to a complete group. + +Bind the pipeline and constants, then dispatch: + +```csharp +CommandBuffer commandBuffer = context.ComputeQueue.CommandBuffer(); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetConstantBuffer(buffer, 0); +commandBuffer.Dispatch(groupCountX, groupCountY, 1); + +commandBuffer.Submit(); +``` + +Guard out-of-range threads in the shader when group counts round up the workload dimensions. + +## Dispatch Indirectly + +`DispatchIndirect` reads `GroupCountX`, `GroupCountY`, and `GroupCountZ` from an `IndirectDispatchArgs` record. + +Create the argument buffer with `BufferUsages.Indirect`. If earlier GPU work writes the arguments, also add the appropriate storage usage and record a barrier before dispatch. + +See [Bindless Resources](../fundamentals/bindless-resources.md) for passing buffers and textures to the shader. See [Synchronization](../fundamentals/synchronization.md) when a dispatch consumes or produces data for other GPU work. diff --git a/documents/docs/workloads/mesh-shading.md b/documents/docs/workloads/mesh-shading.md new file mode 100644 index 00000000..f1a37286 --- /dev/null +++ b/documents/docs/workloads/mesh-shading.md @@ -0,0 +1,53 @@ +# Mesh Shading + +Mesh shading is an optional graphics path that uses mesh shader workgroups instead of vertex and index input. + +## Check Support + +Check `context.Capabilities.MeshShadingSupported` before creating a mesh shading pipeline. + +## Create the Pipeline + +Compile the mesh and fragment entry points for the active context. Compile a task entry point only when the workload uses a task stage. See [Shaders](../fundamentals/shaders.md) for the shared compilation workflow. + +Create a pipeline from a description whose attachment formats match the render pass: + +```csharp +MeshShadingPipelineDesc desc = new() +{ + TaskShader = null, + MeshShader = meshShader, + FragmentShader = fragmentShader, + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = attachmentFormats, + RenderState = renderState +}; + +MeshShadingPipeline pipeline = context.CreateMeshShadingPipeline(desc); +``` + +Set `TaskShader` to a compiled task entry point when the workload uses a task stage. + +## Dispatch Mesh Work + +Mesh dispatch runs inside a render pass: + +```csharp +commandBuffer.Transition(texture, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); +commandBuffer.BeginRenderPass([ColorAttachment.Clear(texture, default)], null); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.DispatchMesh(groupCountX, groupCountY, groupCountZ); + +commandBuffer.EndRenderPass(); +``` + +Without a task stage, the dispatch counts select mesh shader workgroups directly. When `TaskShader` is present, they select task shader workgroups, and each task workgroup determines how many mesh shader workgroups to launch. Each mesh shader workgroup determines how many vertices and primitives it emits. + +## Dispatch Indirectly + +`DispatchMeshIndirect` reads one or more `IndirectDispatchMeshArgs` records. Its counts follow the same task-versus-mesh workgroup interpretation as `DispatchMesh`. + +Create the argument buffer with `BufferUsages.Indirect`. If earlier GPU work writes the arguments, also add the matching storage usage and record a barrier before dispatch. + +See [Bindless Resources](../fundamentals/bindless-resources.md) for shader-visible mesh data and [Synchronization](../fundamentals/synchronization.md) when GPU work produces mesh data or indirect arguments. diff --git a/documents/docs/workloads/rasterization.md b/documents/docs/workloads/rasterization.md new file mode 100644 index 00000000..f8149cbb --- /dev/null +++ b/documents/docs/workloads/rasterization.md @@ -0,0 +1,62 @@ +# Rasterization + +Rasterization draws vertices and indexed geometry into color and depth/stencil attachments. A graphics pipeline combines Slang shaders, vertex input, attachment formats, and render state. + +## Compile the Shaders + +Compile the vertex and fragment entry points for the active context with `ZenithCompiler`, then create both shaders from their descriptions. See [Shaders](../fundamentals/shaders.md) for compilation from files or source strings. + +## Create the Pipeline + +Define one `InputLayout` for each vertex-buffer slot. `Add` appends an element and updates the stream stride: + +```csharp +InputLayout inputLayout = new(); +inputLayout.Add(new() +{ + Format = ElementFormat.Float4, + Semantic = ElementSemantic.Position +}); +``` + +Add the remaining elements in the same order as the shader input. + +Create a pipeline from a description whose attachment formats match the render pass: + +```csharp +GraphicsPipelineDesc desc = new() +{ + VertexShader = vertexShader, + FragmentShader = fragmentShader, + InputLayouts = [inputLayout], + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = attachmentFormats, + RenderState = renderState +}; + +GraphicsPipeline pipeline = context.CreateGraphicsPipeline(desc); +``` + +Keep the input element order, semantics, and formats aligned with the Slang vertex input. + +## Draw in a Render Pass + +Transition the attachments, begin the pass, bind the pipeline state, and draw: + +```csharp +commandBuffer.Transition(texture, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); +commandBuffer.BeginRenderPass([ColorAttachment.Clear(texture, default)], null); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.Draw(vertexCount, 1, 0, 0); + +commandBuffer.EndRenderPass(); +``` + +Choose `Load`, `Clear`, or `DontCare` for each attachment according to whether previous contents are needed. `BeginRenderPass` initializes the viewport and scissor from the attachment size. + +Set a smaller viewport and scissor after beginning the pass when rendering to only part of an attachment. + +Use `Draw` for non-indexed geometry and `DrawIndexed` for indexed geometry. `DrawIndirect` and `DrawIndexedIndirect` read commands from a buffer created with `BufferUsages.Indirect`. + +See [Bindless Resources](../fundamentals/bindless-resources.md) for shader-visible resources and [Synchronization](../fundamentals/synchronization.md) when GPU work produces indirect arguments. diff --git a/documents/docs/workloads/ray-tracing.md b/documents/docs/workloads/ray-tracing.md new file mode 100644 index 00000000..f8ef16f0 --- /dev/null +++ b/documents/docs/workloads/ray-tracing.md @@ -0,0 +1,74 @@ +# Ray Tracing + +Zenith.NET Ray Tracing uses bottom-level and top-level acceleration structures with inline Slang `RayQuery` operations. + +## Check Support + +Check `context.Capabilities.RayTracingSupported` before creating Ray Tracing resources. + +## Build a BLAS + +A bottom-level acceleration structure (BLAS) contains triangle or axis-aligned bounding-box geometry. Set `Geometries` and `BuildFlags` in its description, then record the build: + +```csharp +BottomLevelAccelerationStructureDesc desc = new() +{ + Geometries = geometries, + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace +}; + +BottomLevelAccelerationStructure resource = commandBuffer.BuildAccelerationStructure(desc); +``` + +Use `RayTracingGeometry.Aabbs` instead when the geometry is represented by bounding boxes. + +## Build a TLAS + +The top-level acceleration structure (TLAS) contains instances of existing BLAS objects. Set `Instances` and `BuildFlags` in its description, then record the build: + +```csharp +TopLevelAccelerationStructureDesc desc = new() +{ + Instances = instances, + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace +}; + +TopLevelAccelerationStructure resource = commandBuffer.BuildAccelerationStructure(desc); +``` + +Submit the build before tracing against the TLAS. Work submitted to the same queue remains ordered; when tracing on another queue, pass the build submission's `TimelineValue` to the tracing submission. + +Keep every referenced BLAS alive while the TLAS is in use. + +## Update an Acceleration Structure + +Set `AllowUpdate` on the initial build and every later in-place update, and pass the updated description to `UpdateAccelerationStructure`. + +Choose `PreferFastTrace`, `PreferFastBuild`, or `MinimizeMemory` according to how the structure is used. + +## Trace Rays in Slang + +Store the TLAS handle in constant data and declare the matching field as `DescriptorHandle` in Slang. + +For triangle geometry, initialize a `RayQuery`, trace it, and inspect the committed result: + +```slang +RayDesc ray; +ray.Origin = origin; +ray.Direction = direction; +ray.TMin = 0.001; +ray.TMax = 100000.0; + +RayQuery query; +query.TraceRayInline(*constants.Resource, RAY_FLAG_NONE, 0xFF, ray); + +while (query.Proceed()) +{ +} + +bool hit = query.CommittedStatus() != COMMITTED_NOTHING; +``` + +For procedural AABB geometry, handle each `CANDIDATE_PROCEDURAL_PRIMITIVE` during `Proceed()` and call `CommitProceduralPrimitiveHit` for an accepted intersection. + +See [Shaders](../fundamentals/shaders.md) for compiling the entry point and [Bindless Resources](../fundamentals/bindless-resources.md) for the C#/Slang handle contract. Use a [timeline dependency](../fundamentals/synchronization.md#order-work-across-queues) when building and tracing on different queues. diff --git a/documents/images/Zenith.NET-Logo.png b/documents/images/Zenith.NET-Logo.png index 5dd004f2..bd96a940 100644 Binary files a/documents/images/Zenith.NET-Logo.png and b/documents/images/Zenith.NET-Logo.png differ diff --git a/documents/images/Zenith.NET-Logo.svg b/documents/images/Zenith.NET-Logo.svg index 91836d0c..71a8ceba 100644 --- a/documents/images/Zenith.NET-Logo.svg +++ b/documents/images/Zenith.NET-Logo.svg @@ -1,45 +1,37 @@ - + + Zenith.NET - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - + + + + + + + - - - - Zenith.NET - - \ No newline at end of file + diff --git a/documents/images/Zenith.NET.png b/documents/images/Zenith.NET.png index b7862c71..68168576 100644 Binary files a/documents/images/Zenith.NET.png and b/documents/images/Zenith.NET.png differ diff --git a/documents/images/Zenith.NET.svg b/documents/images/Zenith.NET.svg index 0e2e45db..b508b02d 100644 --- a/documents/images/Zenith.NET.svg +++ b/documents/images/Zenith.NET.svg @@ -1,47 +1,29 @@ - + + Zenith.NET - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - \ No newline at end of file diff --git a/documents/images/compute-shader.png b/documents/images/compute-shader.png deleted file mode 100644 index a75663c9..00000000 Binary files a/documents/images/compute-shader.png and /dev/null differ diff --git a/documents/images/hello-triangle.png b/documents/images/hello-triangle.png deleted file mode 100644 index 259f93c2..00000000 Binary files a/documents/images/hello-triangle.png and /dev/null differ diff --git a/documents/images/indirect-drawing.png b/documents/images/indirect-drawing.png deleted file mode 100644 index 52d8ec60..00000000 Binary files a/documents/images/indirect-drawing.png and /dev/null differ diff --git a/documents/images/mesh-shading.png b/documents/images/mesh-shading.png deleted file mode 100644 index f0e49f79..00000000 Binary files a/documents/images/mesh-shading.png and /dev/null differ diff --git a/documents/images/ray-tracing.png b/documents/images/ray-tracing.png deleted file mode 100644 index c9c115b5..00000000 Binary files a/documents/images/ray-tracing.png and /dev/null differ diff --git a/documents/images/shoko.png b/documents/images/shoko.png deleted file mode 100644 index e1077eb6..00000000 Binary files a/documents/images/shoko.png and /dev/null differ diff --git a/documents/images/spinning-cube.png b/documents/images/spinning-cube.png deleted file mode 100644 index 6b981292..00000000 Binary files a/documents/images/spinning-cube.png and /dev/null differ diff --git a/documents/images/textured-quad.png b/documents/images/textured-quad.png deleted file mode 100644 index 19741c4a..00000000 Binary files a/documents/images/textured-quad.png and /dev/null differ diff --git a/documents/index.md b/documents/index.md index 1ee6696f..35b1819a 100644 --- a/documents/index.md +++ b/documents/index.md @@ -1,147 +1,163 @@ --- -title: Zenith.NET - Unified Cross-Platform GPU Programming Interface +title: Zenith.NET - Modern RHI for .NET +description: A modern rendering hardware interface for .NET with one consistent C# API across 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 +
+
+
+
+

Zenith.NETModern RHI for .NET.

+

Build graphics and compute applications with one consistent C# API for resources, pipelines, commands, and presentation.

+ +
+ + DirectX 12 + Metal 4 + Vulkan 1.4 +
+
+
+
+ Hello Triangle + 1 / 6 + + + + + + + +
+ +
-
- GitHub Stars - NuGet Version - License +
+
+
+
+ THE RHI +

One model for rendering.Designed for modern C#.

+

Start with a small set of objects and use the same workflow across supported graphics APIs.

+
+
+
+
+

Consistent C# API

+

Create resources, pipelines, commands, and swap chains through a focused object model.

+
+
+
+

Clear command flow

+

Record work in order, submit it to a queue, and track completion with timeline values.

+
+
+
+

Simple shader binding

+

Pass compact resource handles in constant data and use them as typed Slang resources.

+
+
+
+

Modern workloads

+

Use rasterization, compute, and indirect commands, with capability-gated Ray Tracing and mesh shading.

+
+
-
-
- ---- - -
-
- 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. + +
+ -
-
-
🔌
- -
-
🚀
-
- 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).

-
-
-
-
2
-
-

Create Resources

-

Define buffers, textures, shaders, and pipelines.

-
-
-
-
3
-
-

Record Commands

-

Build command buffers with draw, compute, or mesh shading dispatches.

-
-
-
-
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/ManagedReference.extension.js b/documents/templates/ManagedReference.extension.js new file mode 100644 index 00000000..64fa9aeb --- /dev/null +++ b/documents/templates/ManagedReference.extension.js @@ -0,0 +1,11 @@ +exports.postTransform = function (model) { + if (!model.namespace?.uid || !model.namespace.specName) { + return model; + } + + for (const name of model.namespace.specName) { + name.value = `${model.namespace.uid}`; + } + + return model; +}; diff --git a/documents/templates/public/main.css b/documents/templates/public/main.css index 7fdbe873..434fd288 100644 --- a/documents/templates/public/main.css +++ b/documents/templates/public/main.css @@ -1,2061 +1,3777 @@ -/** - * Zenith.NET Documentation — "Signal" Theme - * A modern, luminous design language for GPU programming docs. +a.external[href]::after { + content: none; +} + +.next-article .bi-chevron-right { + display: none; +} + +/** + * 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-900: #231e2e; + --zenith-purple-700: #512bd4; + --zenith-magenta-600: #512bd4; + --zenith-blue-500: #536dfe; + --zenith-green-500: #0f9f78; + --zenith-text: #231e2e; + --zenith-text-soft: #655f70; + --zenith-text-faint: #918a9d; + --zenith-surface: #fbfafc; + --zenith-surface-soft: #f3f1f7; + --zenith-surface-tint: #f6f1ff; + --zenith-border: #e1dce8; + --zenith-border-strong: #cec6da; + --zenith-code-surface: #f6f8fc; + --zenith-code-toolbar: #eef2f8; + --zenith-code-border: #d8deea; + --zenith-code-text: #1f2430; + --zenith-code-muted: #687386; + --zenith-code-hover: #e2e7f1; + --zenith-code-accent: #512bd4; + --zenith-code-accent-border: rgba(81, 43, 212, 0.34); + --zenith-code-success: #0f766e; + --zenith-inline-code: #512bd4; + --zenith-inline-code-background: rgba(81, 43, 212, 0.08); + --zenith-table-surface: #ffffff; + --zenith-table-header: #eef2f7; + --zenith-table-stripe: #f8fafc; + --zenith-table-hover: #f1f5fb; + --zenith-table-border: #d8deea; + --zenith-table-text: #4b5565; + --zenith-table-heading: #1f2937; + --zenith-link: #512bd4; + --zenith-link-hover: #7652e8; + --zenith-header-surface: rgba(251, 250, 252, 0.95); + --zenith-header-border: rgba(35, 30, 46, 0.12); + --zenith-nav-text: #70697c; + --zenith-nav-active: #231e2e; + --zenith-search-surface: #f2f0f5; + --zenith-search-border: #e1dce8; + --zenith-search-focus: #7150e8; + --zenith-accent: #512bd4; + --zenith-accent-soft: rgba(81, 43, 212, 0.08); + --zenith-logo-width: 115px; + --zenith-info: var(--zenith-blue-500); + --zenith-success: var(--zenith-green-500); + --zenith-warning: #c76620; + --zenith-danger: #d94f70; + --zenith-syntax-keyword: #af00db; + --zenith-syntax-literal: #0000ff; + --zenith-syntax-string: #a31515; + --zenith-syntax-type: #267f99; + --zenith-syntax-function: #795e26; + --zenith-syntax-variable: #001080; + --zenith-syntax-comment: #008000; + --zenith-syntax-number: #098658; + --zenith-syntax-meta: #9f4f00; + --zenith-syntax-symbol: #b0006d; + --zenith-flow-space: 24px; + --zenith-panel-radius: var(--zenith-radius); + --zenith-control-radius: var(--zenith-radius-small); + --zenith-copy-size: 30px; + --zenith-toolbar-height: 36px; + --zenith-code-padding-block: 12px; + --zenith-code-padding-inline: 14px; + --zenith-table-padding-block: 12px; + --zenith-table-padding-inline: 16px; + --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-radius-small: 5px; + --zenith-radius: 8px; + --zenith-header-height: 78px; + --zenith-header-bar-height: 58px; + --zenith-header-inset: 10px; + --zenith-header-inline-inset: clamp(10px, 1vw, 18px); + --zenith-content-width: 1200px; + --zenith-docs-width: 1760px; + --zenith-page-gutter: clamp(16px, 1.25vw, 40px); + --zenith-column-gap: clamp(20px, 1.5vw, 28px); + --zenith-sidebar-width: clamp(240px, 18vw, 300px); + --zenith-affix-width: 14%; + --zenith-api-sidebar-width: clamp(280px, 21vw, 340px); + --zenith-scrollbar-size: 8px; + --zenith-scrollbar-inset: 2px; + --zenith-scrollbar-edge-gap: 4px; + --zenith-scrollbar-min-thumb: 36px; + --zenith-scrollbar-thumb: color-mix(in srgb, var(--zenith-text-faint) 62%, transparent); + --zenith-scrollbar-thumb-hover: color-mix(in srgb, var(--zenith-text-soft) 82%, transparent); + --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-link); + --bs-link-color-rgb: 81, 43, 212; + --bs-link-hover-color: var(--zenith-link-hover); + --bs-link-hover-color-rgb: 118, 82, 232; + --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-purple-700: #b9a5ff; + --zenith-magenta-600: #d1c4ff; + --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-surface: #191621; + --zenith-code-toolbar: #211d2a; + --zenith-code-border: #393243; + --zenith-code-text: #e4dfea; + --zenith-code-muted: #aaa1b5; + --zenith-code-hover: #2d2737; + --zenith-code-accent: #b9a5ff; + --zenith-code-accent-border: rgba(185, 165, 255, 0.42); + --zenith-code-success: #70cdb5; + --zenith-inline-code: #b69cff; + --zenith-inline-code-background: rgba(155, 123, 255, 0.13); + --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-table-surface: #17131e; + --zenith-table-header: #211b2b; + --zenith-table-stripe: #1b1624; + --zenith-table-hover: #241d30; + --zenith-table-border: #393044; + --zenith-table-text: #c0b8ca; + --zenith-table-heading: #f1edf6; + --zenith-header-surface: rgba(16, 13, 22, 0.95); + --zenith-link: #b9a5ff; + --zenith-link-hover: #d1c4ff; + --zenith-nav-active: #f7f3ff; + --zenith-search-surface: #191421; + --zenith-search-border: #312940; + --zenith-info: #8fa2ff; + --zenith-success: #70cdb5; + --zenith-warning: #ffb16f; + --zenith-danger: #ff8fa3; + --zenith-syntax-keyword: #c586c0; + --zenith-syntax-literal: #569cd6; + --zenith-syntax-string: #ce9178; + --zenith-syntax-type: #4ec9b0; + --zenith-syntax-function: #dcdcaa; + --zenith-syntax-variable: #9cdcfe; + --zenith-syntax-comment: #6a9955; + --zenith-syntax-number: #b5cea8; + --zenith-syntax-meta: #d7ba7d; + --zenith-syntax-symbol: #c8c8c8; + --bs-body-color: var(--zenith-text); + --bs-body-bg: var(--zenith-surface); + --bs-link-color: var(--zenith-link); + --bs-link-color-rgb: 185, 165, 255; + --bs-link-hover-color: var(--zenith-link-hover); + --bs-link-hover-color-rgb: 209, 196, 255; + --bs-border-color: var(--zenith-border); } /* ========================================================================== - 2. Base + Base and typography ========================================================================== */ -body { - background-color: var(--bg); - color: var(--text-primary); - padding-top: 60px !important; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - position: relative; +html { + scroll-padding-top: calc(var(--zenith-header-height) + 24px); } - /* 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%); +@supports not selector(::-webkit-scrollbar) { + * { + scrollbar-color: var(--zenith-scrollbar-thumb) transparent; + scrollbar-width: thin; } +} -[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%); +*::-webkit-scrollbar { + width: var(--zenith-scrollbar-size); + height: var(--zenith-scrollbar-size); } -::selection { - background: var(--accent-soft-mid); - color: var(--text-primary); +*::-webkit-scrollbar-track, +*::-webkit-scrollbar-corner { + background: transparent; } -/* Full-width layout */ -main.container-xxl, -header .container-xxl { - max-width: 100% !important; - padding-left: 2rem !important; - padding-right: 2rem !important; +*::-webkit-scrollbar-thumb { + background-color: var(--zenith-scrollbar-thumb); + background-clip: padding-box; + border: var(--zenith-scrollbar-inset) solid transparent; + border-radius: 999px; } -/* ========================================================================== - 3. Navbar - ========================================================================== */ + *::-webkit-scrollbar-thumb:hover { + background-color: var(--zenith-scrollbar-thumb-hover); + } -header.bg-body, -.navbar { - --bs-bg-opacity: 0; - 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; +body { + min-width: 0; + 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; + text-rendering: optimizeLegibility; } -.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; +::selection { + background: rgba(198, 0, 189, 0.18); + color: var(--zenith-text); } -.dropdown-item { - border-radius: var(--radius-sm) !important; - transition: all var(--duration) var(--ease) !important; - color: var(--text-primary) !important; - padding: 6px 10px !important; +a { + color: var(--zenith-link); + text-decoration-color: rgba(81, 43, 212, 0.3); + text-underline-offset: 3px; + transition: color 160ms ease, text-decoration-color 160ms ease; } - .dropdown-item:hover, - .dropdown-item:focus { - background: var(--accent-soft) !important; - color: var(--accent) !important; + a:hover, + a:focus { + color: var(--zenith-link-hover); + text-decoration: none; } -.navbar #navbar, -.navbar #navbar > * { - display: flex; - align-items: center; +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; } -.navbar .navbar-nav { - display: flex; - gap: 2px; +img { + max-width: 100%; } -.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; +:focus-visible { + outline: 3px solid rgba(113, 56, 232, 0.42) !important; + outline-offset: 3px; } - .navbar .nav-link:hover { - background: var(--accent-soft) !important; - color: var(--text-primary) !important; - } - - .navbar .nav-link.active { - color: var(--accent) !important; - font-weight: 600 !important; - } +/* ========================================================================== + Global header + ========================================================================== */ -.navbar .icons { - display: flex; - align-items: center; - gap: 2px; +body > header.bg-body, +body > header { + position: fixed !important; + top: var(--zenith-header-inset); + left: 50%; + z-index: 1030; + width: min(calc(100% - (2 * var(--zenith-header-inline-inset))), var(--zenith-docs-width)); + height: var(--zenith-header-bar-height); + background: var(--zenith-header-surface) !important; + border: 1px solid var(--zenith-header-border) !important; + border-radius: var(--zenith-radius); + box-shadow: 0 8px 24px rgba(20, 18, 32, 0.08); + backdrop-filter: blur(16px) saturate(1.25); + -webkit-backdrop-filter: blur(16px) saturate(1.25); + transform: translateX(-50%); + transition: background-color 240ms ease, border-color 240ms ease, box-shadow 240ms ease; +} + + body > header.is-scrolled { + box-shadow: 0 12px 32px rgba(20, 18, 32, 0.13); + } + +[data-bs-theme="dark"] body > header.is-scrolled { + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.38); +} + +.zenith-reading-progress { + position: absolute; + right: 8px; + bottom: -1px; + left: 8px; + z-index: 2; + height: 2px; + overflow: hidden; + pointer-events: none; } - .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; + .zenith-reading-progress > span { + display: block; + width: 100%; + height: 100%; + background: var(--zenith-brand-gradient); + box-shadow: 0 0 10px rgba(81, 43, 212, 0.34); + transform: scaleX(0); + transform-origin: left center; + will-change: transform; } - .navbar .icons > a.btn:hover, - .navbar .icons .dropdown > a.btn:hover { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } - - .navbar .icons > a.btn i, - .navbar .icons .dropdown > a.btn i { - font-size: 1.05rem; - } - - /* Theme switcher dropdown — match icon button size */ - .navbar .icons .dropdown > a.btn { - width: auto !important; - height: 34px !important; - padding: 6px 10px !important; +@supports (animation-timeline: scroll()) { + .zenith-reading-progress > span { + animation: zenith-reading-progress linear both; + animation-timeline: scroll(root block); + animation-range: 0% 100%; } +} -/* Search box — smaller width and spacing from theme switcher */ -.navbar #navbar form.search { - margin-left: 8px !important; +[data-bs-theme="dark"] body > header.bg-body, +[data-bs-theme="dark"] body > header { + background: var(--zenith-header-surface) !important; + border-color: var(--zenith-border) !important; } - .navbar #navbar form.search > input { - max-width: 180px !important; - height: 34px !important; - font-size: 0.85rem !important; - } +body[data-layout="landing"] > header.bg-body, +body[data-layout="landing"] > header { + background: rgba(251, 250, 252, 0.58) !important; + border-color: rgba(35, 30, 46, 0.08) !important; + box-shadow: 0 8px 24px rgba(41, 27, 69, 0.045); +} -#logo { - height: 50px; +[data-bs-theme="dark"] body[data-layout="landing"] > header.bg-body, +[data-bs-theme="dark"] body[data-layout="landing"] > header { + background: rgba(16, 13, 22, 0.5) !important; + border-color: rgba(185, 165, 255, 0.1) !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); } -/* ========================================================================== - 4. Hero Section - ========================================================================== */ +body[data-layout="landing"] > header.is-scrolled { + background: rgba(251, 250, 252, 0.88) !important; + border-color: rgba(35, 30, 46, 0.12) !important; + box-shadow: 0 12px 32px rgba(41, 27, 69, 0.11); +} -.hero { - text-align: center; - padding: 4rem 2rem 4.5rem; +[data-bs-theme="dark"] body[data-layout="landing"] > header.is-scrolled { + background: rgba(16, 13, 22, 0.84) !important; + border-color: rgba(185, 165, 255, 0.16) !important; + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.28); } -.hero-content { - max-width: 720px; - margin: 0 auto; +.navbar { + width: 100%; + height: 100%; + min-height: 0; + 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)); + .navbar > .container-xxl { + align-items: center; + width: 100%; + height: 100%; + max-width: var(--zenith-content-width) !important; + min-height: 0; + margin: 0 auto; + padding: 0 18px !important; + } + +.navbar-brand { + position: relative; + display: inline-flex; + flex: 0 0 auto; + align-items: center; + margin-right: 10px !important; + padding: 0 !important; } -[data-bs-theme="dark"] .hero-logo { - filter: drop-shadow(0 4px 16px rgba(96,165,250,0.30)); +#logo { + display: block; + width: var(--zenith-logo-width); + height: 36px; + object-fit: contain; + object-position: left center; + transform-origin: left center; + transition: filter 220ms ease, transform 220ms var(--zenith-ease); } -.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; +.navbar-brand:hover #logo, +.navbar-brand:focus-visible #logo { + filter: drop-shadow(0 5px 10px rgba(81, 43, 212, 0.18)); + transform: translateY(-1px) scale(1.012); } -.hero-tagline { - font-size: 1.25rem; - font-weight: 500; - margin: 0 0 1rem; - color: var(--text-primary); - letter-spacing: -0.01em; +.navbar-brand:active #logo { + transform: scale(0.992); } -.hero-description { - font-size: 1.05rem; - max-width: 560px; - margin: 0 auto 2.25rem; - color: var(--text-secondary); - line-height: 1.7; +.navbar-collapse, +#navbar { + min-width: 0; } -.hero-buttons { +#navbar { display: flex; - justify-content: center; - gap: 0.75rem; - flex-wrap: wrap; + flex: 1 1 auto; + align-items: center; + justify-content: flex-end; + gap: 6px; } - .hero-buttons .btn, - .btn, - button.btn, - a.btn { - border-radius: var(--radius-sm) !important; + #navbar .navbar-nav { + display: flex; + order: 1; + flex: 0 1 auto; + align-items: center; + gap: 2px; + margin-right: auto; } - .hero-buttons .btn { + #navbar .nav-link { + position: relative; display: inline-flex; align-items: center; - justify-content: center; - padding: 0.625rem 1.5rem; - font-size: 0.9rem; + height: 36px; + padding: 0 12px !important; + color: var(--zenith-nav-text) !important; + border-radius: 0 !important; + font-size: 14px; font-weight: 600; + line-height: 1; + letter-spacing: 0; text-decoration: none; - border: none; - outline: none; - transition: all var(--duration) var(--ease); - gap: 0.375rem; + transition: color 180ms ease, background-color 180ms ease, transform 180ms var(--zenith-ease); } - .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); - } +[data-bs-theme="dark"] #navbar .nav-link { + color: var(--zenith-text-soft) !important; +} - .hero-buttons .btn-secondary { - background: var(--surface) !important; - color: var(--text-primary); - border: 1px solid var(--border-strong) !important; - box-shadow: var(--shadow-xs); - } +#navbar .nav-link::after { + content: ""; + position: absolute; + right: 12px; + bottom: 4px; + left: 12px; + height: 2px; + background: var(--zenith-accent); + opacity: 0; + transform: scaleX(0.3); + transition: opacity 160ms ease, transform 160ms ease; +} - .hero-buttons .btn-secondary:hover { - border-color: var(--accent) !important; - color: var(--accent); - transform: translateY(-1px); - box-shadow: var(--shadow-sm); - } +#navbar .nav-link:hover, +#navbar .nav-link.active { + color: var(--zenith-nav-active) !important; +} -/* ========================================================================== - 5. Features Grid - ========================================================================== */ +[data-bs-theme="dark"] #navbar .nav-link:hover, +[data-bs-theme="dark"] #navbar .nav-link.active { + color: var(--zenith-nav-active) !important; +} -.features { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1rem; - margin: 2rem 0; +#navbar .nav-link:hover::after, +#navbar .nav-link.active::after { + opacity: 1; + transform: scaleX(1); } -.feature { - padding: 1.5rem; - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--surface); - transition: all var(--duration) var(--ease); +#navbar form.search { + order: 2 !important; + width: 210px; + margin: 0 !important; } - .feature:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-md); - transform: translateY(-2px); + #navbar form.search > i { + left: 12px; + color: var(--zenith-text-faint); } - .feature h3 { - font-size: 1.05rem; - font-weight: 600; - margin: 0 0 0.5rem; - color: var(--text-primary); + #navbar form.search > input { + width: 100%; + height: 36px; + padding-left: 36px; + background: var(--zenith-search-surface); + color: var(--zenith-text); + border: 1px solid var(--zenith-search-border); + border-radius: 6px; + box-shadow: none; + font-size: 13px; } - .feature p { - font-size: 0.875rem; - color: var(--text-secondary); - margin: 0; - line-height: 1.6; + #navbar form.search.zenith-search { + position: relative; + display: flex; + width: 36px; + min-width: 36px; + height: 36px; + align-items: center; + overflow: hidden; + border-radius: 6px; + transition: width 300ms var(--zenith-ease), box-shadow 180ms ease; } -/* ========================================================================== - 6. Technology Section - ========================================================================== */ + #navbar form.search.zenith-search.is-expanded { + width: 230px; + box-shadow: 0 8px 22px rgba(20, 15, 30, 0.1); + } -.tech-section { - display: flex; - flex-direction: column; - gap: 2rem; - margin: 2rem 0; -} + #navbar form.search.zenith-search > i { + display: none; + } -.tech-group { - text-align: center; + #navbar form.search.zenith-search > input { + position: absolute; + inset: 0; + z-index: 0; + width: 100%; + padding-right: 12px; + padding-left: 38px; + opacity: 0; + pointer-events: none; + transform: translateX(8px); + transition: opacity 180ms ease, transform 300ms var(--zenith-ease), border-color 180ms ease, background-color 180ms ease; + } + + #navbar form.search.zenith-search.is-expanded > input { + opacity: 1; + pointer-events: auto; + transform: translateX(0); + } + +.zenith-search-toggle { + position: relative; + z-index: 1; + display: inline-flex; + width: 36px; + height: 36px; + flex: 0 0 36px; + align-items: center; + justify-content: center; + padding: 0; + color: var(--zenith-text-soft); + background: transparent; + border: 1px solid transparent; + border-radius: 6px; + font-size: 15px; + transition: color 180ms ease, background-color 180ms ease, border-color 180ms ease, transform 220ms var(--zenith-ease); } - .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; + .zenith-search-toggle:hover, + .zenith-search-toggle:focus-visible { + color: var(--zenith-accent); + background: transparent; + border-color: transparent; } -.tech-icons { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 0.625rem; +#navbar form.search.zenith-search.is-expanded .zenith-search-toggle { + color: var(--zenith-accent); + border-color: transparent; + transform: rotate(-8deg) scale(0.94); +} + +#navbar form.search.zenith-search.is-loading .zenith-search-toggle { + color: var(--zenith-text-faint); } -.tech-item { +.navbar .icons { + order: 3 !important; display: flex; + flex: 0 0 auto; 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); + gap: 6px; } - .tech-item:hover { - border-color: var(--accent-ring); - background: var(--accent-soft); + .navbar .icons .zenith-theme-toggle { + order: 1; } - .tech-item .tech-text { - font-size: 0.875rem; - font-weight: 600; - color: var(--accent); + .navbar .icons > a.btn { + order: 2; } -/* ========================================================================== - 7. Cards Grid - ========================================================================== */ + .navbar .icons .btn, + .navbar .icons > 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; + transition: color 180ms ease, background-color 180ms ease, transform 180ms var(--zenith-ease) !important; + } + + .zenith-search-toggle > i, + .navbar .icons .btn > i, + .navbar .icons > a.btn > i { + display: inline-flex; + width: 16px; + height: 16px; + align-items: center; + justify-content: center; + font-size: 16px; + line-height: 1; + transform-origin: center; + } -.cards { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1rem; - margin: 2rem 0; -} + .zenith-search-toggle > i::before, + .navbar .icons .btn > i::before, + .navbar .icons > a.btn > i::before { + display: block; + line-height: 1; + vertical-align: 0; + } -.card { - 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); -} + .navbar .icons .zenith-theme-toggle > i { + transition: color 180ms ease, transform 220ms var(--zenith-ease); + } - .card:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-md); - transform: translateY(-2px); + .navbar .icons .btn.zenith-theme-toggle:hover, + .navbar .icons .btn.zenith-theme-toggle:focus-visible { + color: var(--zenith-accent) !important; + background: transparent !important; + transform: none !important; } - .card h3 { - font-size: 1.05rem; - font-weight: 600; - margin: 0 0 0.5rem; - color: var(--text-primary); + .navbar .icons .zenith-theme-toggle:hover > i { + transform: rotate(12deg) scale(1.08); } - .card p { - font-size: 0.875rem; - color: var(--text-secondary); - margin: 0; - line-height: 1.6; - flex-grow: 1; + .navbar .icons .btn:hover, + .navbar .icons > a.btn:hover { + color: var(--zenith-accent) !important; + background: var(--zenith-accent-soft) !important; + transform: translateY(-1px); } - .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); + .navbar .icons > a.btn:hover, + .navbar .icons > a.btn:focus-visible { + color: var(--zenith-accent) !important; + background: transparent !important; + transform: none !important; } - .card a:hover { - color: var(--accent-hover); - } + .navbar .icons > a.btn > i { + transition: color 180ms ease, transform 220ms var(--zenith-ease); + } -/* ========================================================================== - 8. Workflow Section - ========================================================================== */ + .navbar .icons > a.btn:hover > i { + transform: scale(1.08); + } -.workflow { - display: flex; - flex-direction: column; - margin: 2rem 0; - --step-size: 44px; - --step-line: 2px; -} + .navbar .icons .zenith-theme-toggle.is-theme-leaving > i { + animation: zenith-theme-icon-leave 120ms ease-in forwards; + } -.workflow-step { - display: flex; - align-items: center; - gap: 1.25rem; - position: relative; - padding-bottom: 1.25rem; + .navbar .icons .zenith-theme-toggle.is-theme-entering > i { + animation: zenith-theme-icon-enter 260ms var(--zenith-ease) both; + } + +@keyframes zenith-theme-icon-leave { + to { + opacity: 0; + transform: rotate(70deg) scale(0.52); + } } - .workflow-step:last-child { - padding-bottom: 0; +@keyframes zenith-theme-icon-enter { + from { + opacity: 0; + transform: rotate(-70deg) scale(0.52); } - .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; - 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); + 68% { + opacity: 1; + transform: rotate(8deg) scale(1.12); + } + + to { + opacity: 1; + transform: rotate(0) scale(1); + } +} + +.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; } -.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); +.dropdown-item { + color: var(--zenith-text) !important; + border-radius: 5px !important; } - .step-content:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-sm); + .dropdown-item:hover, + .dropdown-item:focus { + color: var(--zenith-purple-700) !important; + background: rgba(113, 56, 232, 0.08) !important; } - .step-content h4 { - font-size: 0.95rem; - font-weight: 600; - margin: 0 0 0.25rem; - color: var(--text-primary); +/* ========================================================================== + Landing layout reset + ========================================================================== */ + +body[data-layout="landing"] { + overflow-x: clip; + padding-top: 0 !important; +} + + body[data-layout="landing"] > main.container-xxl { + width: 100%; + max-width: none !important; + margin: 0 !important; + padding: 0 !important; } - .step-content p { - font-size: 0.875rem; - color: var(--text-secondary); + body[data-layout="landing"] main > .content { + width: 100%; + max-width: none; margin: 0; - line-height: 1.6; + padding: 0; } -/* ========================================================================== - 9. Badges - ========================================================================== */ + body[data-layout="landing"] .actionbar, + body[data-layout="landing"] .next-article, + body[data-layout="landing"] main > .affix { + display: none !important; + } -.community-badges, -.hero-badges { - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 0.5rem; + body[data-layout="landing"] article { + width: 100%; + max-width: none; + margin: 0; + padding: 0; + } + +.landing-page { + width: 100%; + color: var(--zenith-text); } -.community-badges { - gap: 0.875rem; - margin: 2rem 0; +.landing-shell { + width: calc(100% - clamp(36px, 4vw, 64px)); + max-width: 1440px; + margin: 0 auto; } -.hero-badges { - margin-top: 1.75rem; +.landing-page p { + color: var(--zenith-text-soft); } - .community-badges a, - .hero-badges a { - transition: opacity var(--duration) var(--ease), transform var(--duration) var(--ease); - } +/* ========================================================================== + Landing hero + ========================================================================== */ - .community-badges a:hover, - .hero-badges a:hover { - transform: translateY(-1px); - opacity: 0.80; - } +.hero-copy { + min-width: 0; + padding: 24px 0; +} - .hero-badges img { - height: 22px; + .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; } -/* ========================================================================== - 10. Technology Bar - ========================================================================== */ +[data-bs-theme="dark"] .hero-copy h1 { + color: #f3f6ff; +} + +.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-bar { +.hero-actions { 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; + gap: 12px; } -.tech-bar-group { - display: flex; +.landing-button { + display: inline-flex; align-items: center; - gap: 0.5rem; + justify-content: center; + 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-bar-label { - font-size: 0.7rem; - font-weight: 600; - color: var(--text-tertiary); - text-transform: uppercase; - letter-spacing: 0.1em; +.landing-button-primary { + color: #ffffff !important; + background: var(--zenith-brand-gradient); + box-shadow: 0 8px 18px rgba(181, 12, 189, 0.22); } -.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; -} + .landing-button-primary:hover { + color: #ffffff !important; + box-shadow: 0 11px 24px rgba(181, 12, 189, 0.3); + transform: translateY(-2px); + } -.tech-bar-divider { - width: 1px; - height: 20px; - background: var(--border-strong); +.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); } -/* ========================================================================== - 11. Highlight Grid - ========================================================================== */ +[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); +} -.highlight-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.75rem; - margin: 2rem 0; +.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); } -.highlight-item { +.backend-support { 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); + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-top: 30px; } - .highlight-item:hover { - border-color: var(--border-strong); - box-shadow: var(--shadow-sm); - } +.backend-label { + margin-right: 3px; + color: var(--zenith-text-faint); + font-size: 11px; + font-weight: 650; +} -.highlight-icon { - font-size: 1.2rem; +.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; - flex-shrink: 0; - margin-top: 1px; } -.highlight-text { - font-size: 0.875rem; - color: var(--text-secondary); - line-height: 1.55; +[data-bs-theme="dark"] .backend-pill { + color: var(--zenith-text-soft); + background: rgba(24, 26, 34, 0.82); + border-color: var(--zenith-border); } - .highlight-text strong { - color: var(--text-primary); - font-weight: 600; - } - /* ========================================================================== - 12. Feature List + Documentation shell ========================================================================== */ -.feature-list { - display: flex; - flex-direction: column; - gap: 0.375rem; - margin: 2rem 0; +body:not([data-layout="landing"]) > main.container-xxl { + display: grid; + grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr) var(--zenith-affix-width); + width: 100%; + max-width: var(--zenith-docs-width) !important; + min-height: calc(100vh - var(--zenith-header-height)); + margin: 0 auto; + padding: 0 var(--zenith-page-gutter) !important; + column-gap: var(--zenith-column-gap); } -.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); +body:is([data-yaml-mime="ManagedReference"], [data-zenith-api]) > main.container-xxl { + grid-template-columns: var(--zenith-api-sidebar-width) minmax(0, 1fr) var(--zenith-api-sidebar-width); } - .feature-list-item:hover { - border-color: var(--border-strong); - background: var(--accent-soft); - } +body:not([data-layout="landing"]) > main.container-xxl > .toc-offcanvas { + width: 100% !important; + max-width: none; +} -.feature-list-title { - font-size: 0.875rem; - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; - min-width: 140px; +body:not([data-layout="landing"]) main > .content { + grid-column: 2; + min-width: 0; + width: auto; + margin-right: 0 !important; + margin-left: 0 !important; + padding: 24px 0 70px; } -.feature-list-desc { - font-size: 0.875rem; - color: var(--text-secondary); - line-height: 1.55; +body:not([data-layout="landing"]) main > .affix { + grid-column: 3; + min-width: 0; + width: 100% !important; + max-width: 100%; + padding-top: 16px; } -/* ========================================================================== - 13. CTA Section - ========================================================================== */ +body[data-yaml-mime="ManagedReference"]:not([data-search]) > main.container-xxl > .affix { + position: sticky; + top: var(--zenith-header-height); + align-self: start; + height: calc(100vh - var(--zenith-header-height)); + max-height: none; + overflow: visible; +} -.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; +.actionbar { + min-height: 34px; + margin-bottom: 18px; } - /* Subtle accent line at top */ - .cta-section::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - background: var(--gradient-brand); - } +.breadcrumb { + margin: 0; + padding: 0; + background: transparent; + font-size: 12px; +} -.cta-main { - flex: 1; +.breadcrumb-item, +.breadcrumb-item a { + color: var(--zenith-text-faint); + text-decoration: none !important; } - .cta-main h3 { - font-size: 1.375rem; - font-weight: 700; - color: var(--text-primary); - margin: 0 0 0.5rem; - letter-spacing: -0.01em; + .breadcrumb-item.active, + .breadcrumb-item a:hover { + color: var(--zenith-purple-700); } - .cta-main p { - font-size: 0.95rem; - color: var(--text-secondary); - margin: 0 0 1.25rem; - line-height: 1.6; + .breadcrumb-item + .breadcrumb-item::before { + color: var(--zenith-border-strong); } - .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); - } +body:not([data-layout="landing"]) article { + width: 100%; + margin: 0 auto; +} - .cta-main .btn-primary:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } +article > h1:first-child, +article > h1:first-of-type { + margin-top: 0; +} -.cta-links { - display: flex; - flex-direction: column; - align-items: flex-end; - justify-content: center; - margin-left: auto; - gap: 0.5rem; +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; } -.cta-link { - display: flex; - align-items: center; - 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; +article h2 { + position: relative; + margin: 42px 0 17px; + padding: 0 0 10px; + border-bottom: 1px solid var(--zenith-border); + font-size: 25px; + line-height: 1.25; } - .cta-link:hover { - border-color: var(--accent-ring); - background: var(--accent-soft); - text-decoration: none; - transform: translateY(-1px); - box-shadow: var(--shadow-sm); + article h2::after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + width: 52px; + height: 2px; + background: var(--zenith-brand-gradient); + border-radius: 2px; + transform: scaleX(1); + transform-origin: left center; + transition: transform 520ms var(--zenith-ease); } -.cta-link-icon { - font-size: 1rem; +html.zenith-motion-ready article h2.zenith-reveal:not(.is-visible)::after { + transform: scaleX(0); + transition: none; } -.cta-link-text { - font-size: 0.875rem; - font-weight: 600; - color: var(--text-primary); +html.zenith-motion-ready article h2.is-visible::after { + transform: scaleX(1); } -/* ========================================================================== - 14. Article Content - ========================================================================== */ +article h3 { + margin: 30px 0 12px; + font-size: 19px; + line-height: 1.35; +} -article { - font-size: 0.95rem; - color: var(--text-primary); - line-height: 1.75; +article h4 { + margin: 24px 0 10px; + font-size: 16px; } - 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 p, +article li, +article dd, +article td { + color: var(--zenith-text-soft); +} - 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 p { + margin-bottom: 16px; +} - article h2::before { - content: ''; - position: absolute; - left: 0; - top: 0.15em; - bottom: 0.15em; - width: 3px; - background: var(--gradient-brand); - border-radius: 2px; - } +article strong { + color: var(--zenith-text); + font-weight: 750; +} - article h3 { - font-size: 1.15rem; - font-weight: 650; - color: var(--text-primary); - margin: 2rem 0 0.625rem; - letter-spacing: -0.01em; - } +article ul, +article ol { + padding-left: 1.35rem; +} - article h4 { - font-size: 1rem; - font-weight: 600; - color: var(--text-primary); - margin: 1.5rem 0 0.5rem; - } +article li { + margin: 5px 0; +} - article p { - margin: 0 0 1rem; - color: var(--text-primary); + article li::marker { + color: var(--zenith-purple-700); } - article a { - color: var(--accent); - text-decoration: none; - transition: color var(--duration) var(--ease); - } +article img:not(.svg) { + border-radius: 7px; +} - article a:hover { - color: var(--accent-hover); - text-decoration: underline; - text-underline-offset: 2px; - } +article a:not(.btn):not(.landing-button) { + font-weight: 600; +} - article ul, - article ol { - margin: 0 0 1rem; - padding-left: 1.5rem; - } +article .anchorjs-link { + display: none !important; +} + +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 li { - margin-bottom: 0.375rem; - color: var(--text-primary); + article blockquote > :last-child { + margin-bottom: 0; } - /* 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); +code { + padding: 0.14em 0.38em; + color: var(--zenith-inline-code); + background: var(--zenith-inline-code-background); + border-radius: 4px; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 0.88em; +} + +article pre, +.codewrapper pre { + position: relative; + margin: 20px 0; + padding: 22px 24px; + overflow: auto; + color: var(--zenith-code-text); + background: var(--zenith-code-surface) !important; + border: 1px solid var(--zenith-code-border) !important; + border-radius: 6px !important; + box-shadow: none; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 13px; + line-height: 1.7; +} + +.doc-code-frame { + position: relative; + margin: var(--zenith-flow-space) 0; + overflow: hidden; + color: var(--zenith-code-text); + background: var(--zenith-code-surface); + border: 1px solid var(--zenith-code-border); + border-radius: var(--zenith-panel-radius); + transition: border-color 180ms ease, box-shadow 220ms ease; +} + + .doc-code-frame::before { + content: ""; + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 2; + height: 2px; + pointer-events: none; + background: var(--zenith-brand-gradient); + opacity: 0; + transform: scaleX(0.35); + transform-origin: left center; + transition: opacity 180ms ease, transform 360ms var(--zenith-ease); } - 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; - } + .doc-code-frame:hover, + .doc-code-frame:focus-within { + border-color: var(--zenith-code-accent-border); + box-shadow: 0 10px 26px rgba(35, 30, 46, 0.08); + } - article pre code { - display: block; - padding: 1.125rem; - margin: 0; - overflow-x: auto; - font-size: 0.8125rem; - line-height: 1.7; - background: transparent; - } +[data-bs-theme="dark"] .doc-code-frame:hover, +[data-bs-theme="dark"] .doc-code-frame:focus-within { + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22); +} - article pre code.hljs, - article pre .hljs { - background: transparent !important; - } +.doc-code-frame:hover::before, +.doc-code-frame:focus-within::before { + opacity: 0.86; + transform: scaleX(1); +} - article pre code::-webkit-scrollbar { - height: 6px; - background: transparent; - } +.doc-code-toolbar { + display: flex; + height: var(--zenith-toolbar-height); + align-items: center; + padding: 0 4px 0 12px; + color: var(--zenith-code-muted); + background: var(--zenith-code-toolbar); + border-bottom: 1px solid var(--zenith-code-border); +} - article pre code::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: 3px; - } +.doc-code-language { + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; +} - article pre code::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); - } +.doc-code-copy { + display: inline-flex; + width: var(--zenith-copy-size); + min-width: var(--zenith-copy-size); + height: var(--zenith-copy-size); + align-items: center; + justify-content: center; + margin-left: auto; + padding: 0; + color: var(--zenith-code-muted); + background: var(--zenith-code-surface); + border: 1px solid var(--zenith-code-border); + border-radius: var(--zenith-control-radius); + font-family: Aptos, "Segoe UI Variable Text", sans-serif; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease, transform 160ms var(--zenith-ease); +} - article code { - font-size: 0.8125rem; - font-family: 'Cascadia Code', 'JetBrains Mono', 'Fira Code', Consolas, monospace; + .doc-code-copy:active { + transform: scale(0.92); } - /* 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; + .doc-code-copy i { + font-size: 13px; } - article hr { - border: none; - border-top: 1px solid var(--border); - margin: 2.5rem 0; + .doc-code-copy:hover { + color: var(--zenith-code-accent); + background: var(--zenith-code-hover); + border-color: var(--zenith-code-accent-border); } - 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); + .doc-code-copy.is-copied { + color: var(--zenith-code-success); + background: rgba(15, 118, 110, 0.1); + border-color: rgba(15, 118, 110, 0.28); } - article img:not(#logo):not(.hero-logo):not([src*="shields.io"]):not([src*="badge"]):hover { - box-shadow: var(--shadow-lg); + .doc-code-copy.is-copied i { + animation: zenith-copy-confirm 320ms var(--zenith-ease) both; } -#logo, -.hero-logo, -.hero-badges img, -.community-badges img { - box-shadow: none !important; - border: none !important; - transform: none !important; -} - - #logo:hover, - .hero-badges img:hover, - .community-badges img:hover { - box-shadow: none !important; - transform: none !important; + .doc-code-copy.is-failed { + color: #b42318; + background: rgba(180, 35, 24, 0.08); + border-color: rgba(180, 35, 24, 0.24); } -article figure { - margin: 1.5rem 0; - padding: 0; +article .doc-code-frame > pre, +.codewrapper .doc-code-frame > pre { + margin: 0; + padding: var(--zenith-code-padding-block) var(--zenith-code-padding-inline); + background: transparent !important; + border: 0 !important; + border-radius: 0 !important; + box-shadow: none; } - 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; +article pre code, +.codewrapper pre code { + padding: 0; + color: inherit; + background: transparent; + border-radius: 0; + font-size: inherit; } -/* ========================================================================== - 15. Table Styles - ========================================================================== */ - -article .table-responsive, -article > table { - margin: 1.5rem 0 !important; +.shiki, +.shiki span { + color: var(--shiki-light, var(--zenith-code-text)); + background-color: transparent !important; + font-style: var(--shiki-light-font-style, inherit); + font-weight: var(--shiki-light-font-weight, inherit); + text-decoration: var(--shiki-light-text-decoration, inherit); } -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"] .shiki, +[data-bs-theme="dark"] .shiki span { + color: var(--shiki-dark, var(--zenith-code-text)); + font-style: var(--shiki-dark-font-style, inherit); + font-weight: var(--shiki-dark-font-weight, inherit); + text-decoration: var(--shiki-dark-text-decoration, inherit); } -.table-responsive { - overflow: visible !important; +.codewrapper { + margin: 18px 0; } -article thead { - background: var(--surface-sunken) !important; -} + .codewrapper .btn-copy, + pre .btn-copy { + color: var(--zenith-code-muted) !important; + background: var(--zenith-code-toolbar) !important; + border: 1px solid var(--zenith-code-border) !important; + border-radius: 5px !important; + } -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; +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-link { + color: var(--zenith-syntax-keyword); } -article thead tr th:first-child { - border-top-left-radius: var(--radius-md) !important; +.hljs-literal { + color: var(--zenith-syntax-literal); } -article thead tr th:last-child { - border-top-right-radius: var(--radius-md) !important; +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition, +.hljs-template-tag, +.hljs-template-variable { + color: var(--zenith-syntax-string); } -article tbody tr { - transition: background var(--duration) var(--ease) !important; +.hljs-title, +.hljs-name, +.hljs-type, +.hljs-attribute, +.hljs-built_in { + color: var(--zenith-syntax-type); } - article tbody tr:hover { - background: var(--accent-soft) !important; + .hljs-title.function_, + .hljs-function > .hljs-title, + .hljs-property, + .hljs-attr { + color: var(--zenith-syntax-function); } -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; +.hljs-variable, +.hljs-params, +.hljs-subst { + color: var(--zenith-syntax-variable); } -article tbody tr:first-child td { - border-top: none !important; +.hljs-comment, +.hljs-quote, +.hljs-deletion { + color: var(--zenith-syntax-comment); } -article tbody tr:last-child td:first-child { - border-bottom-left-radius: var(--radius-md) !important; +.hljs-number { + color: var(--zenith-syntax-number); } -article tbody tr:last-child td:last-child { - border-bottom-right-radius: var(--radius-md) !important; +.hljs-meta, +.hljs-meta-keyword, +.hljs-doctag { + color: var(--zenith-syntax-meta); } -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; +.hljs-symbol, +.hljs-regexp { + color: var(--zenith-syntax-symbol); } -article td a { - white-space: nowrap !important; +.hljs-operator, +.hljs-punctuation { + color: var(--zenith-code-muted); } /* ========================================================================== - 16. Status Badges + Tables, alerts, tabs, and API surfaces ========================================================================== */ -.status-yes, -.status-no { - display: inline-block; - padding: 0.15rem 0.5rem; - border-radius: var(--radius-full); - font-size: 0.75rem; - font-weight: 600; +.doc-table-frame { + position: relative; + margin: var(--zenith-flow-space) 0; + overflow: hidden; + background: var(--zenith-table-surface); + border: 1px solid var(--zenith-table-border); + border-radius: var(--zenith-panel-radius); + transition: border-color 180ms ease, box-shadow 220ms ease; } -.status-yes { - background: var(--accent-soft); - color: var(--accent); - border: 1px solid var(--accent-ring); -} + .doc-table-frame:hover, + .doc-table-frame:focus-within { + border-color: color-mix(in srgb, var(--zenith-purple-700) 34%, var(--zenith-table-border)); + box-shadow: 0 10px 24px rgba(35, 30, 46, 0.06); + } -.status-no { - background: var(--surface-sunken); - color: var(--text-tertiary); - border: 1px solid var(--border); -} + .doc-table-frame::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 2; + width: 22px; + pointer-events: none; + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--zenith-table-surface) 94%, transparent)); + opacity: 0; + transition: opacity 180ms ease; + } -/* ========================================================================== - 17. Alerts - ========================================================================== */ + .doc-table-frame.is-scrollable:not(.is-scroll-end)::after { + opacity: 1; + } -.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); + .doc-table-frame > .table-responsive { + margin: 0; + overflow-x: auto; + border: 0; + border-radius: 0; + } + +article .doc-table-frame table, +article .doc-table-frame .table { + --bs-table-color: var(--zenith-table-text); + --bs-table-bg: var(--zenith-table-surface); + --bs-table-border-color: var(--zenith-table-border); + --bs-table-striped-bg: var(--zenith-table-stripe); + --bs-table-hover-bg: var(--zenith-table-hover); + width: 100%; + min-width: 560px; + margin: 0; + color: var(--zenith-table-text); + background: var(--zenith-table-surface); + border: 0; + border-collapse: collapse; + border-radius: 0; + font-size: 14px; + line-height: 1.5; } - .alert p:last-child { - margin-bottom: 0; - } - - .alert a { - font-weight: 600; - text-decoration: underline; - text-underline-offset: 2px; + article .doc-table-frame table > :not(caption) > * { + border: 0; } - .alert a:hover { - text-decoration: none; + article .doc-table-frame table > :not(caption) > * > * { + box-shadow: 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; +article .doc-table-frame th { + padding: var(--zenith-table-padding-block) var(--zenith-table-padding-inline); + color: var(--zenith-table-heading); + background: var(--zenith-table-header); + border: 0; + border-right: 1px solid var(--zenith-table-border); + border-bottom: 1px solid var(--zenith-table-border); + font-size: 12px; + font-weight: 700; + text-align: left; + text-transform: none; + vertical-align: bottom; } - .alert-primary a { - color: var(--accent); - } - -.alert-secondary { - background: var(--surface-sunken); - border: 1px solid var(--border); - border-left-color: var(--border-strong) !important; +article .doc-table-frame td { + padding: var(--zenith-table-padding-block) var(--zenith-table-padding-inline); + background: var(--zenith-table-surface); + border: 0; + border-right: 1px solid var(--zenith-table-border); + border-bottom: 1px solid var(--zenith-table-border); + vertical-align: middle; + transition: background-color 160ms ease, color 160ms ease; } - .alert-secondary a { - color: var(--text-secondary); + article .doc-table-frame th:last-child, + article .doc-table-frame td:last-child { + border-right: 0; } -.alert-success { - background: rgba(22, 163, 74, 0.05); - border: 1px solid rgba(22, 163, 74, 0.15); - border-left-color: #16A34A !important; +article .doc-table-frame tbody tr:nth-child(even) td { + background: var(--zenith-table-stripe); } - .alert-success a { - color: #16A34A; - } +article .doc-table-frame tbody tr:last-child td { + border-bottom: 0; +} -.alert-danger { - background: rgba(220, 38, 38, 0.05); - border: 1px solid rgba(220, 38, 38, 0.15); - border-left-color: #DC2626 !important; +article .doc-table-frame tbody tr:hover td { + background: var(--zenith-table-hover); } - .alert-danger a { - color: #DC2626; - } +article .doc-table-frame tbody td:first-child { + color: var(--zenith-table-heading); + font-weight: 650; +} -.alert-warning { - background: rgba(217, 119, 6, 0.05); - border: 1px solid rgba(217, 119, 6, 0.15); - border-left-color: #D97706 !important; +article .doc-table-frame td > code:only-child { + white-space: nowrap; } - .alert-warning a { - color: #D97706; +.alert { + position: relative; + margin: var(--zenith-flow-space) 0; + padding: 16px 18px; + color: var(--zenith-text-soft); + background: var(--zenith-surface-soft); + border: 1px solid var(--zenith-border); + border-left-width: 4px; + border-radius: var(--zenith-panel-radius); + font-size: 14px; + transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms var(--zenith-ease); +} + + .alert:hover, + .alert:focus-within { + box-shadow: 0 8px 22px rgba(35, 30, 46, 0.07); + transform: translateY(-1px); } -.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"] .alert:hover, +[data-bs-theme="dark"] .alert:focus-within { + box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2); } - .alert-info a { - color: #0891B2; - } +.alert > h5, +.alert > .alert-title { + margin-bottom: 5px; + color: var(--zenith-text); + font-size: 13px; + font-weight: 850; +} -.alert-light { - background: var(--surface-sunken); - border: 1px solid var(--border); - border-left-color: var(--border-strong) !important; +.alert-info, +.alert-note { + background: color-mix(in srgb, var(--zenith-info) 8%, transparent); + border-left-color: var(--zenith-info); } - .alert-light a { - color: var(--accent); - } +.alert-tip, +.alert-success { + background: color-mix(in srgb, var(--zenith-success) 8%, transparent); + border-left-color: var(--zenith-success); +} -.alert-dark { - background: var(--surface-sunken); - border: 1px solid var(--border-strong); - border-left-color: var(--text-secondary) !important; +.alert-warning, +.alert-important { + background: color-mix(in srgb, var(--zenith-warning) 8%, transparent); + border-left-color: var(--zenith-warning); } - .alert-dark a { - color: var(--text-primary); - } +.alert-danger, +.alert-caution { + background: color-mix(in srgb, var(--zenith-danger) 8%, transparent); + border-left-color: var(--zenith-danger); +} -/* 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; +.tabGroup { + margin: var(--zenith-flow-space) 0; + border: 1px solid var(--zenith-border); + border-radius: var(--zenith-panel-radius); + overflow: hidden; } - [data-bs-theme="dark"] .alert-primary a { - color: var(--accent); + .tabGroup > ul.nav-tabs { + padding: 0 10px; + background: var(--zenith-surface-soft); + border-bottom: 1px solid var(--zenith-border); } -[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; -} + .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); + } - [data-bs-theme="dark"] .alert-success a { - color: #86EFAC; + .tabGroup > section { + padding: 18px; } -[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; +article .inheritance, +article .implements, +article .inheritedMembers, +article .derivedClasses { + color: var(--zenith-text-soft); } - [data-bs-theme="dark"] .alert-danger a { - color: #FCA5A5; - } - -[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; +article dl.typelist, +article dl.parameters, +article dl.returns { + margin: 18px 0; } - [data-bs-theme="dark"] .alert-warning a { - color: #FCD34D; - } +article dl dt { + color: var(--zenith-text); + font-weight: 750; +} -[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; +article .section { + scroll-margin-top: calc(var(--zenith-header-height) + 20px); } - [data-bs-theme="dark"] .alert-info a { - color: #67E8F9; - } +article h3[id], +article a[data-uid] + h3 { + padding-top: 5px; +} /* ========================================================================== - 18. Sidebar Navigation - ========================================================================== */ - -.sidebar, -.sidefilter { - background: transparent !important; + 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); } -.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; + #sidetoggle > div { + height: 100%; } - .affix ul li { - margin: 0 !important; - padding: 0 !important; - } - - .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; - } +.sidefilter { + padding: 18px 14px 10px 0; +} - .affix .link-body-emphasis { - color: var(--text-primary) !important; - } +.toc-filter { + position: relative; +} - .affix .link-secondary { - color: var(--text-secondary) !important; - font-size: 0.75rem !important; - font-weight: 400 !important; + .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; } - .affix ul li a:hover, - .affix ul li.active > a, - .affix ul li a.active { - background: var(--accent-soft) !important; - color: var(--accent) !important; + .toc-filter .filter-icon { + position: absolute; + top: 50%; + left: 11px; + z-index: 1; + display: inline-flex; + width: 16px; + height: 16px; + align-items: center; + justify-content: center; + color: var(--zenith-text-faint); + line-height: 1; + transform: translateY(-50%); } - .affix ul ul { - margin-left: 0.625rem !important; - padding-left: 0 !important; - border-left: none !important; - gap: 0 !important; - } +.sidetoc { + height: calc(100% - 62px); + padding: 4px 12px 30px 0; + overflow: auto; +} .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; - } + padding: 0 16px 28px; + font-size: 13px; +} - .toc .nav > li > a:hover, - .toc .nav > li > .expand-stub + a:hover { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } +#toc.zenith-scroll-host > .overflow-y-auto, +main > .affix.zenith-scroll-host > #affix { + scrollbar-width: none; +} - .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 > .overflow-y-auto { + overflow-x: hidden !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.zenith-scroll-host > .overflow-y-auto::-webkit-scrollbar, +main > .affix.zenith-scroll-host > #affix::-webkit-scrollbar { + display: none; + width: 0; + height: 0; +} - .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; - } +.zenith-scroll-host { + position: relative; +} - .toc .nav > li > .expand-stub:hover { - background: var(--accent-soft) !important; - } +.zenith-overlay-scrollbar { + position: absolute; + right: var(--zenith-scrollbar-inset); + z-index: 8; + width: var(--zenith-scrollbar-size); + opacity: 0; + pointer-events: none; + touch-action: none; + transition: opacity 160ms ease; +} - .toc .nav > li > .expand-stub::before { - font-size: 0.65rem !important; - color: var(--text-tertiary) !important; - transition: transform var(--duration) var(--ease) !important; - } +main > .affix > .zenith-overlay-scrollbar { + right: calc(-1 * (var(--zenith-scrollbar-size) + var(--zenith-scrollbar-edge-gap))); +} + +.zenith-overlay-scrollbar > span { + position: absolute; + top: 0; + right: var(--zenith-scrollbar-inset); + left: var(--zenith-scrollbar-inset); + min-height: var(--zenith-scrollbar-min-thumb); + background: var(--zenith-scrollbar-thumb); + border-radius: 999px; + cursor: grab; + transition: background-color 160ms ease; +} - .toc .nav > li.in > .expand-stub::before, - .toc .nav > li.expanded > .expand-stub::before { - transform: rotate(90deg) !important; +.zenith-scroll-host.has-scroll-range:hover > .zenith-overlay-scrollbar, +.zenith-scroll-host.has-scroll-range:focus-within > .zenith-overlay-scrollbar, +.zenith-scroll-host.has-scroll-range.is-scrolling > .zenith-overlay-scrollbar, +.zenith-overlay-scrollbar.is-dragging { + opacity: 1; + pointer-events: auto; +} + + .zenith-overlay-scrollbar > span:hover, + .zenith-overlay-scrollbar.is-dragging > span { + background: var(--zenith-scrollbar-thumb-hover); } - .toc .nav .nav { - padding-left: 0.625rem !important; - margin-left: 0.625rem !important; - border-left: 1px solid var(--border) !important; + .zenith-overlay-scrollbar.is-dragging > span { + cursor: grabbing; } - .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; - } +.zenith-overlay-scrollbar:focus-visible { + outline: 0; +} - .toc .nav .nav > li > a:hover, - .toc .nav .nav > li.active > a { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } + .zenith-overlay-scrollbar:focus-visible > span { + background: var(--zenith-scrollbar-thumb-hover); + box-shadow: 0 0 0 1px var(--zenith-accent); + } - .toc .nav .nav > li.active > a { - font-weight: 600 !important; +#toc > form.filter { + position: relative; + margin: 16px 0 18px; +} + + #toc > form.filter input { + height: 36px; + padding-left: 36px; + color: var(--zenith-text); + background: var(--zenith-search-surface); + border-color: var(--zenith-search-border); + border-radius: 6px; + box-shadow: none; + font-size: 13px; + } + + #navbar form.search > input:focus, + #toc > form.filter input:focus, + .toc-filter > input:focus, + #toc_filter_input:focus { + outline: 0 !important; + color: var(--zenith-text); + background: var(--zenith-surface); + border-color: var(--zenith-search-focus); + box-shadow: inset 0 0 0 1px rgba(81, 43, 212, 0.22); } -/* 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; -} - - .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; + #toc > form.filter i { + position: absolute; + top: 50%; + left: 12px; + z-index: 1; + display: inline-flex; + width: 16px; + height: 16px; + align-items: center; + justify-content: center; + color: var(--zenith-text-faint); + line-height: 1; + transform: translateY(-50%); } - .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; - } +.toc ul { + margin: 0; + padding-left: 0; + list-style: none; +} -#toc .filter { - position: relative !important; - margin-bottom: 0.75rem !important; +.toc li > a { + white-space: nowrap; } - #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; - } +#toc > .overflow-y-auto > ul > li > a wbr { + display: none; +} -#toc .flex-fill.overflow-y-auto { - --toc-expand-width: 16px; - padding: 0 0.25rem !important; +.toc ul ul { + margin: 3px 0 9px 10px; + padding-left: 11px; + border-left: 1px solid var(--zenith-border); } - #toc .flex-fill.overflow-y-auto ul { - list-style: none !important; - padding: 0 !important; - margin: 0 !important; - } +.toc li { + position: relative; +} - #toc .flex-fill.overflow-y-auto > ul > li { - margin-top: 1px !important; - padding: 0 !important; + .toc li > a { + position: relative; + display: block; + margin: 1px 0; + padding: 6px 9px; + color: var(--zenith-text-soft); + border-radius: 5px; + font-weight: 550; + line-height: 1.35; + text-decoration: none !important; + transition: color 160ms ease, background-color 160ms ease, box-shadow 180ms ease, transform 180ms var(--zenith-ease); } - #toc .flex-fill.overflow-y-auto > ul > li:first-child { - margin-top: 0 !important; + .toc li > a:hover { + color: var(--zenith-purple-700); + background: rgba(113, 56, 232, 0.06); } - #toc .flex-fill.overflow-y-auto li { - margin: 0 !important; - padding: 0 !important; - } - - #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 li.active:not(.expander) > a, + .toc li > a.active { + color: var(--zenith-nav-active); + background: transparent; + font-weight: 750; + box-shadow: none; + text-decoration: none !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 li.active:not(.expander) > a::before, + .toc li > a.active::before { + content: ""; + position: absolute; + top: 50%; + bottom: auto; + left: 0; + width: 2px; + height: 14px; + background: var(--zenith-purple-700); + border-radius: 2px; + transform: translateY(-50%); } - #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 li.expander > a { + color: var(--zenith-text); + font-weight: 700; + } - #toc .flex-fill.overflow-y-auto li.expander:hover > a { - color: var(--accent) !important; - } +.toc > div > ul > li:first-child > a { + color: var(--zenith-purple-700); + font-size: 11px; + font-weight: 800; + text-transform: uppercase; +} - #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 .expand-stub { + position: absolute; + top: 0; + left: 0; + z-index: 1; + display: block; + width: 14px; + height: 31px; + color: var(--zenith-text-faint); + cursor: pointer; +} - #toc .flex-fill.overflow-y-auto li .expand-stub::before { - margin-left: 0 !important; - } + .toc .expand-stub::before { + position: absolute; + top: 50%; + left: 50%; + display: block; + width: 14px; + height: 14px; + margin: 0; + line-height: 14px; + text-align: center; + transform: translate(-50%, -50%); + transform-origin: center; + transition: color 160ms ease, transform 220ms var(--zenith-ease); + } - #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 li.expanded > .expand-stub::before { + transform: translate(-50%, -50%) rotate(90deg); +} + +#affix { + position: static; + top: auto; + max-height: calc(100vh - var(--zenith-header-height) - 32px); + padding-left: 0; + overflow: auto; + border-left: 0; +} - #toc .flex-fill.overflow-y-auto li.expander > ul { - width: 100% !important; - flex-basis: 100% !important; - margin-top: 2px !important; - margin-bottom: 2px !important; + #affix::before { + content: none; + } + + #affix > h5 { + display: block; + width: 100%; + margin: 0 0 14px; + padding: 0 0 12px; + color: var(--zenith-text-faint); + border-bottom: 1px solid var(--zenith-border) !important; + font-size: 10px; + font-weight: 850; + text-transform: uppercase; + } + + #affix ul { + margin: 0; + padding: 0; + border-left: 0; + list-style: none; + } + + #affix a { + position: relative; + display: block; + overflow: hidden; + margin: 1px 0; + padding: 6px 9px; + color: var(--zenith-text-faint); + border: 0; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; + text-decoration: none !important; + text-overflow: ellipsis; + white-space: nowrap; + transition: color 160ms ease, background-color 160ms ease, box-shadow 180ms ease, transform 180ms var(--zenith-ease); + } + + #affix a.link-body-emphasis { + font-weight: 600; } - #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; + #affix a.link-secondary { + padding-left: 18px; + font-size: 11px; + font-weight: 400; } - #toc .flex-fill.overflow-y-auto li:not(.expander) > a { - display: block !important; + #affix a wbr { + display: none; } - #toc .flex-fill.overflow-y-auto li:not(.expander) > a:hover { - background: var(--accent-soft) !important; - color: var(--accent) !important; - } +body[data-yaml-mime="ManagedReference"] #affix { + position: static; + overflow-x: hidden; + overflow-y: auto; +} - #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; - } +#affix a:hover { + color: var(--zenith-purple-700); + background: rgba(113, 56, 232, 0.06); +} - #toc .flex-fill.overflow-y-auto ul ul { - position: relative !important; - margin-left: calc(var(--toc-expand-width) + 12px) !important; - padding: 0 !important; +#affix a.active, +#affix a.is-active { + color: var(--zenith-nav-active) !important; + background: transparent; + font-weight: 700; + box-shadow: none; +} + + #affix a.active::before, + #affix a.is-active::before { + content: ""; + position: absolute; + top: 50%; + bottom: auto; + left: 0; + width: 2px; + height: 14px; + background: var(--zenith-purple-700); + border-radius: 2px; + transform: translateY(-50%); } - #toc .flex-fill.overflow-y-auto ul ul li { - margin-top: 1px !important; - } +#affix a.link-secondary.active::before, +#affix a.link-secondary.is-active::before { + left: 8px; +} - #toc .flex-fill.overflow-y-auto ul ul li:first-child { - margin-top: 0 !important; - } +[data-bs-theme="dark"] .toc li > a:hover, +[data-bs-theme="dark"] #affix a:hover { + color: #d1c4ff !important; +} - #toc .flex-fill.overflow-y-auto ul ul li > a { - font-size: 0.75rem !important; - padding: 0.25rem 0.375rem !important; - } +[data-bs-theme="dark"] .toc li.active:not(.expander) > a, +[data-bs-theme="dark"] .toc li > a.active, +[data-bs-theme="dark"] #affix a.active, +[data-bs-theme="dark"] #affix a.is-active { + color: var(--zenith-nav-active) !important; +} + +.doc-code-frame pre > .code-action { + display: none !important; +} + +@media (max-width: 575.98px) { + article .doc-code-frame > pre { + padding: 12px; + } + + .doc-code-toolbar { + padding-left: 12px; + } +} /* ========================================================================== - 19. Breadcrumb Navigation + Search, footer, pagination ========================================================================== */ -#breadcrumb { - margin-bottom: 1.5rem; +#search-results { + width: 100%; + max-width: var(--zenith-docs-width) !important; + min-height: 65vh; + margin: 0 auto; + padding: 42px var(--zenith-page-gutter) !important; } -.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; +body[data-search] > main.container-xxl { + display: none !important; } -.breadcrumb-item { - display: flex; - align-items: center; - font-size: 0.8rem; - color: var(--text-secondary); +body[data-search] #search-results { + display: grid; + grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr) var(--zenith-affix-width); + column-gap: var(--zenith-column-gap); } - .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); +body:is([data-yaml-mime="ManagedReference"], [data-zenith-api])[data-search] #search-results { + grid-template-columns: var(--zenith-api-sidebar-width) minmax(0, 1fr) var(--zenith-api-sidebar-width); +} + +body[data-search] #search-results > * { + grid-column: 2; + min-width: 0; +} + +#search-results > .sr-items { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: var(--zenith-column-gap); +} + +#search-results .sr-item { + min-width: 0; + padding: 18px 20px; + background: var(--zenith-surface); + border: 1px solid var(--zenith-border); + border-radius: var(--zenith-panel-radius); + animation: zenith-reveal-in 420ms var(--zenith-ease) backwards; + transition: border-color 160ms ease, box-shadow 200ms ease, transform 200ms var(--zenith-ease); +} + + #search-results .sr-item:nth-child(2) { + animation-delay: 35ms; } - .breadcrumb-item a:hover { - color: var(--accent); - background: var(--accent-soft); - text-decoration: none; - } + #search-results .sr-item:nth-child(3) { + animation-delay: 70ms; + } - .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); + #search-results .sr-item:nth-child(4) { + animation-delay: 105ms; } - .breadcrumb-item:last-child { - color: var(--text-primary); - font-weight: 500; + #search-results .sr-item:nth-child(5) { + animation-delay: 140ms; } - .breadcrumb-item:last-child a { - color: var(--text-primary); - pointer-events: none; - } + #search-results .sr-item:nth-child(n + 6) { + animation-delay: 175ms; + } -/* ========================================================================== - 20. Hide UI Elements - ========================================================================== */ + #search-results .sr-item:hover { + border-color: rgba(113, 56, 232, 0.36); + box-shadow: var(--zenith-shadow-small); + transform: translateY(-2px); + } -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; +#search-results .item-title { + overflow-wrap: anywhere; + color: var(--zenith-purple-700); + font-size: 17px; + font-weight: 750; + text-decoration: none; } -/* ========================================================================== - 21. Search Results - ========================================================================== */ +#search-results .item-href { + overflow-wrap: anywhere; + color: var(--zenith-text-faint); + font-size: 11px; +} -body[data-search] > .search-results { - max-width: 820px; - margin: 0 auto; - padding: 2rem 1.5rem 3rem; +#search-results .item-brief { + overflow-wrap: anywhere; + color: var(--zenith-text-soft); + font-size: 14px; } -#search-results { - line-height: 1.7; +html.zenith-motion-ready .zenith-reveal.is-visible { + animation: zenith-reveal-in 720ms var(--zenith-ease) var(--zenith-reveal-delay, 0ms) backwards; +} + +html.zenith-motion-ready .zenith-reveal[data-reveal-from="left"].is-visible { + animation-name: zenith-reveal-left; } - #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); +html.zenith-motion-ready .zenith-reveal[data-reveal-from="right"].is-visible { + animation-name: zenith-reveal-right; +} + +@keyframes zenith-copy-confirm { + 0% { + transform: scale(0.72); } - #search-results > .sr-items { - display: flex; - flex-direction: column; - gap: 0.75rem; + 58% { + transform: scale(1.16); } - #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); - } + 100% { + transform: scale(1); + } +} - #search-results > .sr-items > .sr-item:hover { - border-color: var(--accent); - box-shadow: var(--shadow-sm); - } +@keyframes zenith-reveal-in { + from { + opacity: 0; + transform: translateY(18px) scale(0.985); + } - #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; - } + to { + opacity: 1; + transform: translateY(0); + } +} - #search-results > .sr-items > .sr-item > .item-title:hover { - color: var(--accent-hover); - } +@keyframes zenith-reveal-left { + from { + opacity: 0; + transform: translateX(-24px) scale(0.985); + } - #search-results > .sr-items > .sr-item > .item-href { - font-size: 0.8rem; - color: var(--text-tertiary); - margin-bottom: 0.5rem; - word-break: break-all; - } + to { + opacity: 1; + transform: translateX(0); + } +} - #search-results > .sr-items > .sr-item > .item-brief { - font-size: 0.875rem; - color: var(--text-secondary); - line-height: 1.6; - } +@keyframes zenith-reveal-right { + from { + opacity: 0; + transform: translateX(24px) scale(0.985); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes zenith-reading-progress { + from { + transform: scaleX(0); + } + + to { + transform: scaleX(1); + } +} + +.next-article { + width: 100%; + margin: 26px auto 0; + padding-top: 18px; + border-color: var(--zenith-border) !important; +} + + .next-article a { + text-decoration: none !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: 0 18px !important; + } + + body > footer a { + color: var(--zenith-purple-700); + } /* ========================================================================== - 22. Responsive Design + Responsive behavior ========================================================================== */ -@media (max-width: 768px) { - .hero { - padding: 2.5rem 1.25rem 3rem; +@media (max-width: 1180px) { + #navbar form.search { + width: 165px; } - .hero h1 { - font-size: 2.25rem; - } + .hero-copy h1 { + font-size: 46px; + } - .hero-tagline { - font-size: 1.1rem; + body:not([data-layout="landing"]) > main.container-xxl { + grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr); + column-gap: 20px; } - .hero-description { - font-size: 0.95rem; + body:is([data-yaml-mime="ManagedReference"], [data-zenith-api]) > main.container-xxl { + grid-template-columns: minmax(320px, 28%) minmax(0, 1fr); } - .hero-logo { - width: 64px; - height: 64px; + body[data-search] #search-results { + grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr); + column-gap: 20px; } - .tech-item { - padding: 0.4rem 0.75rem; + body:is([data-yaml-mime="ManagedReference"], [data-zenith-api])[data-search] #search-results { + grid-template-columns: minmax(320px, 28%) minmax(0, 1fr); } - .workflow { - --step-size: 38px; + body:not([data-layout="landing"]) main > .affix { + display: none !important; } +} - .step-number { - font-size: 0.9rem; +@media (max-width: 991.98px) { + :root { + --zenith-header-height: 70px; + --zenith-header-bar-height: 54px; + --zenith-header-inset: 8px; } - .step-content { - padding: 0.875rem 1rem; + #logo { + width: 109px; + height: 34px; } - article h1 { - font-size: 1.625rem; + .navbar-brand { + margin-right: 18px !important; } - article h2 { - font-size: 1.25rem; - padding-left: 0.75rem; - position: relative; + #navbar .nav-link { + padding: 0 8px !important; + font-size: 13px; } - 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; + #navbar .nav-link::after { + right: 8px; + left: 8px; } - article h3 { - font-size: 1.05rem; + #navbar form.search { + display: none; } - article pre { - border-radius: var(--radius-sm); + .hero-copy { + max-width: 690px; + margin: 0 auto; + text-align: center; } - .status-yes, - .status-no { - padding: 0.1rem 0.4rem; - font-size: 0.7rem; + .hero-copy h1, + .hero-lede { + margin-right: auto; + margin-left: auto; + } + + .hero-actions, + .backend-support { + justify-content: center; } - .tech-bar { - flex-direction: column; - gap: 0.75rem; - padding: 0.875rem 1.125rem; + body:not([data-layout="landing"]) > main.container-xxl { + grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr); + padding-right: 14px !important; + padding-left: 14px !important; + column-gap: 18px; } - .tech-bar-group { - flex-direction: column; - gap: 0.25rem; - text-align: center; + body:is([data-yaml-mime="ManagedReference"], [data-zenith-api]) > main.container-xxl { + grid-template-columns: minmax(320px, 34%) minmax(0, 1fr); } - .tech-bar-divider { - width: 80%; - height: 1px; + body[data-search] #search-results { + grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr); + column-gap: 18px; } - .highlight-grid { - grid-template-columns: 1fr; + body:is([data-yaml-mime="ManagedReference"], [data-zenith-api])[data-search] #search-results { + grid-template-columns: minmax(320px, 34%) minmax(0, 1fr); } +} - .highlight-item { - padding: 0.875rem; +@media (max-width: 767.98px) { + :root { + --zenith-header-height: 64px; + --zenith-header-bar-height: 64px; + --zenith-header-inset: 0px; + --zenith-header-inline-inset: 0px; } - .feature-list-item { - flex-direction: column; - gap: 0.25rem; + body > header.bg-body, + body > header { + top: 0; + left: 0; + width: 100%; + height: auto; + min-height: var(--zenith-header-height); + border: 0 !important; + border-bottom: 1px solid var(--zenith-header-border) !important; + border-radius: 0; + box-shadow: 0 1px 6px rgba(20, 28, 58, 0.04); + transform: none; + } + + .navbar > .container-xxl { + min-height: var(--zenith-header-bar-height); + } + + .navbar .navbar-toggler, + .navbar button[data-bs-toggle="collapse"] { + color: var(--zenith-text); + border: 0 !important; + border-radius: 6px !important; } - .feature-list-title { - min-width: auto; + .navbar-collapse { + position: absolute; + top: var(--zenith-header-bar-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); } - .cta-section { + #navbar { + display: flex; + align-items: stretch; flex-direction: column; - padding: 1.5rem 1.25rem; - text-align: center; + gap: 8px; } - .cta-section::before { - /* Shift gradient line to left side on mobile */ - left: 0; - right: auto; - top: 0; - bottom: 0; - width: 3px; - height: 100%; + #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%; } - .cta-main h3 { - font-size: 1.25rem; + #navbar form.search.zenith-search { + display: flex; + width: 36px; + } + + #navbar form.search.zenith-search.is-expanded { + width: 100%; + } + + .navbar .icons { + order: 3 !important; + justify-content: flex-start; } - .cta-links { - width: 100%; - align-items: stretch; + .landing-shell { + width: calc(100% - 28px); } - .cta-link { - justify-content: center; - width: 100%; - min-width: auto; + .hero-copy h1 { + font-size: 40px; + line-height: 1.08; } - .breadcrumb { - padding: 0.35rem 0.5rem; + .hero-lede { + font-size: 16px; } - .breadcrumb-item { - font-size: 0.75rem; + body:not([data-layout="landing"]) > main.container-xxl { + display: block; + padding: 0 16px !important; } - .alert { - padding: 0.75rem 0.875rem; - font-size: 0.8125rem; + body:not([data-layout="landing"]) main > .content { + padding-top: 18px; } - .actionbar { - display: flex; - align-items: center; - gap: 0.75rem; + body[data-search] #search-results { + display: block; } - .actionbar > button { - flex-shrink: 0; - margin-top: 0 !important; - margin-left: 0 !important; - } + #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: 102px; + height: 32px; + } + + .navbar > .container-xxl { + padding-right: 12px !important; + padding-left: 12px !important; + } + + .hero-copy h1 { + font-size: 34px; + } + + .hero-actions .landing-button { + width: 100%; + } + + .backend-label { + width: 100%; + } + + article h1 { + font-size: 28px; + } + + article h2 { + margin-top: 35px; + font-size: 21px; + } + + article pre, + .codewrapper pre { + padding: 18px; + 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; + } + + .zenith-reading-progress > span { + animation: none !important; + transform: scaleX(0) !important; + transition: none !important; + } + + html.zenith-motion-ready .zenith-reveal { + opacity: 1 !important; + transform: none !important; + } +} + +@media print { + body { + padding-top: 0 !important; + } + + body > header, + #sidetoggle, + main > .affix, + body > footer, + .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 + ========================================================================== */ + +.render-hero { + --render-viewport-surface: #17131f; + --render-viewport-toolbar: #211b2b; + --render-viewport-border: rgba(53, 43, 66, 0.46); + --render-viewport-text: #f1edf6; + --render-viewport-muted: #aaa1b5; + --render-viewport-shadow: 0 24px 58px rgba(0, 0, 0, 0.26), 0 3px 12px rgba(0, 0, 0, 0.18); + position: relative; + min-height: calc(620px + var(--zenith-header-height)); + overflow: hidden; + color: #ffffff; + background: #100d16; +} + +.render-hero-inner { + position: relative; + z-index: 2; + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(470px, 1.1fr); + gap: clamp(28px, 3vw, 48px); + min-height: calc(620px + var(--zenith-header-height)); + align-items: center; + padding-top: calc(48px + var(--zenith-header-height)); + padding-bottom: 48px; +} + +.render-hero .hero-copy { + width: 100%; + max-width: 650px; + margin: 0; + padding: 0; + text-align: left; + animation: none; +} + + .render-hero .hero-copy h1, + .render-hero .hero-lede, + .render-hero .hero-actions, + .render-hero .backend-support { + animation: render-hero-copy-in 720ms var(--zenith-ease) both; + } + + .render-hero .hero-copy h1 { + animation-delay: 80ms; + } + +.render-hero .hero-lede { + animation-delay: 170ms; +} + +.render-hero .hero-actions { + animation-delay: 260ms; +} + +.render-hero .backend-support { + animation-delay: 350ms; +} + +.render-viewport { + position: relative; + width: 100%; + min-width: 0; + overflow: hidden; + color: var(--render-viewport-text); + background: var(--render-viewport-surface); + border: 1px solid var(--render-viewport-border); + border-radius: 6px; + box-shadow: var(--render-viewport-shadow); + animation: render-scene-in 820ms var(--zenith-ease) 210ms both; +} + + .render-viewport::before { + content: ""; + position: absolute; + top: -1px; + right: 0; + left: 0; + z-index: 3; + height: 1px; + pointer-events: none; + background: linear-gradient(90deg, #512bd4, #7c4dff 52%, #34c9f5); + } + +.render-viewport-toolbar { + position: relative; + z-index: 2; + display: flex; + height: 38px; + align-items: center; + gap: 12px; + padding: 0 8px 0 13px; + color: var(--render-viewport-muted); + background: var(--render-viewport-toolbar); + border-bottom: 1px solid var(--render-viewport-border); + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 10px; + line-height: 1; +} + +.render-viewport-title, +.render-viewport-details { + display: inline-flex; + align-items: center; + gap: 7px; + white-space: nowrap; +} + +.render-viewport-title { + color: var(--render-viewport-text); + font-weight: 700; + text-transform: uppercase; +} + +.zenith-carousel-status { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + +.render-viewport-title > i { + color: var(--zenith-purple-700); + font-size: 12px; +} + +.render-viewport-details { + margin-left: auto; +} + +.render-viewport-open { + display: inline-flex; + width: 28px; + height: 28px; + align-items: center; + justify-content: center; + color: var(--render-viewport-muted) !important; + border-radius: 4px; + text-decoration: none !important; + transition: color 160ms ease, background-color 160ms ease; +} + + .render-viewport-open:hover, + .render-viewport-open:focus-visible { + color: var(--render-viewport-text) !important; + background: rgba(255, 255, 255, 0.08); + } + +.render-carousel-controls { + display: inline-flex; + align-items: center; + gap: 2px; +} + +.render-viewport-canvas { + position: relative; + aspect-ratio: 16 / 9; + overflow: hidden; + background: #dce6f2; +} + + .render-viewport-canvas > a { + display: block; + width: 100%; + height: 100%; + color: transparent; + text-decoration: none; + } + +.render-carousel-slide { + position: absolute; + inset: 0; + z-index: 0; + opacity: 0; + pointer-events: none; + transition: opacity 480ms ease; +} + + .render-carousel-slide.is-active { + z-index: 1; + opacity: 1; + pointer-events: auto; + } + +.render-carousel-control { + display: none; + width: 28px; + height: 28px; + align-items: center; + justify-content: center; + padding: 0; + color: var(--render-viewport-muted); + background: transparent; + border: 1px solid transparent; + border-radius: 4px; + transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease; +} + + .render-carousel-control:hover, + .render-carousel-control:focus-visible { + color: var(--render-viewport-text); + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.12); + } + +.render-carousel-progress { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 3; + display: none; + height: 3px; + overflow: hidden; + pointer-events: none; + background: rgba(16, 13, 22, 0.26); +} + + .render-carousel-progress > span { + display: block; + width: 100%; + height: 100%; + background: linear-gradient(90deg, #7847ff, #43d8ff); + box-shadow: 0 0 8px rgba(124, 77, 255, 0.42); + transform: scaleX(0); + transform-origin: left center; + } + + .render-carousel-progress > span.is-timing { + animation: render-carousel-timing 6500ms linear forwards; + animation-play-state: paused; + } + +.render-viewport.is-carousel-ready .render-carousel-control, +.render-viewport.is-carousel-ready .render-carousel-progress { + display: flex; +} + +.render-viewport.is-carousel-playing .render-carousel-progress > span.is-timing { + animation-play-state: running; +} + +.render-hero-media { + display: block; + width: 100%; + height: 100%; + max-width: none; + object-fit: cover; + object-position: center; + border: 0 !important; + border-radius: 0 !important; + outline: 0; + box-shadow: none; +} + +@media (max-width: 767.98px) { + .render-carousel-control { + width: 26px; + height: 26px; + } + + .render-carousel-control[data-carousel-toggle] { + display: none !important; + } + + .render-carousel-progress { + display: none !important; + } +} + +@media (prefers-reduced-motion: reduce) { + .render-carousel-slide, + .render-carousel-control { + transition: none !important; + } + + .render-carousel-control[data-carousel-toggle] { + display: none !important; + } + + .render-carousel-progress { + display: none !important; + } +} + +@keyframes render-carousel-timing { + from { + transform: scaleX(0); + } + + to { + transform: scaleX(1); + } +} + +.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: #43d8ff; + background: none; + white-space: nowrap; + -webkit-text-fill-color: currentColor; + } + +@media (min-width: 992px) { + .render-hero .hero-copy h1 { + font-size: clamp(41px, calc(4.4643vw - 3.2857px), 62px); + } +} + +.render-hero .hero-lede { + max-width: 620px; + margin-bottom: 30px; + padding-bottom: 2px; + color: #c5bdce; + font-size: 17px; + line-height: 1.8; + overflow: visible; +} + +.render-hero .landing-button-primary { + color: #ffffff !important; + background: #512bd4; + box-shadow: none; +} + + .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); + box-shadow: none; + backdrop-filter: blur(8px); +} + + .render-hero .landing-button-secondary:hover { + color: #ffffff !important; + background: rgba(43, 32, 57, 0.84); + border-color: rgba(67, 216, 255, 0.7); + } + +.render-hero .backend-support { + gap: 7px; + margin-top: 34px; +} + +.render-hero .backend-label { + color: #8b8298; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 9px; + line-height: 1; + letter-spacing: 0; +} + +.render-hero .backend-pill { + --backend-dot: #58b947; + --backend-dot-glow: #58b947; + --backend-dot-ring: transparent; + min-height: 25px; + gap: 7px; + color: #d2cadb; + background: rgba(16, 13, 22, 0.58); + border-color: rgba(206, 194, 221, 0.24); + border-radius: 4px; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 9px; + font-weight: 650; + backdrop-filter: blur(8px); +} + + .render-hero .backend-pill::before { + content: ""; + width: 6px; + height: 6px; + flex: 0 0 6px; + background: var(--backend-dot); + border-radius: 50%; + box-shadow: inset 0 0 0 1px var(--backend-dot-ring), 0 0 8px color-mix(in srgb, var(--backend-dot-glow) 48%, transparent); + } + +.render-hero .backend-metal { + --backend-dot: linear-gradient(135deg, #ffffff 0%, #a6afb9 48%, #e4e8ec 100%); + --backend-dot-glow: #c9d0d7; + --backend-dot-ring: rgba(74, 82, 92, 0.38); +} + +.render-hero .backend-vulkan { + --backend-dot: #b8202d; + --backend-dot-glow: #d63b45; +} + +.toc .nav > li > a:hover, +.toc .nav > li.active > a, +.toc .nav > li > a.active { + color: var(--zenith-purple-700); +} + +.toc .nav > li > a:hover { + background: rgba(81, 43, 212, 0.07); +} + +.toc .nav > li.active > a, +.toc .nav > li > a.active { + background: transparent; +} + +@keyframes render-scene-in { + from { + opacity: 0; + transform: translateX(24px) scale(0.985); + } + + to { + opacity: 1; + transform: translateX(0) scale(1); + } +} + +@keyframes render-hero-copy-in { + from { + opacity: 0; + transform: translateY(16px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 991.98px) { + .render-hero, + .render-hero-inner { + min-height: 0; + } + + .render-hero-inner { + grid-template-columns: minmax(0, 1fr); + gap: 42px; + padding-top: calc(56px + var(--zenith-header-height)); + padding-bottom: 64px; + } + + .render-hero .hero-copy { + max-width: none; + } + + .render-viewport { + width: min(760px, 100%); + margin: 0 auto; + } + + .render-hero .hero-actions, + .render-hero .backend-support { + justify-content: flex-start; + } +} + +@media (max-width: 767.98px) { + .render-hero-inner { + gap: 34px; + padding-top: calc(48px + var(--zenith-header-height)); + padding-bottom: 54px; + } + + .render-hero .hero-copy h1 { + font-size: 43px; + } +} + +@media (max-width: 575.98px) { + .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-lede { + font-size: 15px; + } + + .render-hero .backend-label { + width: 100%; + } + + .render-viewport-toolbar { + gap: 8px; + padding-left: 10px; + font-size: 9px; + } +} + +@media (max-width: 374.98px) { + .render-hero .hero-copy h1 span { + font-size: 32px; + } + + .render-viewport-details > :not(.render-viewport-position) { + display: none; + } +} + +@media (max-width: 349.98px) { + .render-hero .hero-copy h1 span { + font-size: 29px; + } +} + +/* ========================================================================== + Layout and border refinement + ========================================================================== */ + +.landing-button { + gap: 9px; +} + +@media (min-width: 768px) { + .navbar > .container-xxl { + max-width: none !important; + padding-right: 18px !important; + padding-left: 18px !important; + transform: none; + } + + .navbar-brand { + margin-right: 10px !important; + } + + #navbar { + gap: 6px; + } + + #navbar .navbar-nav { + margin-right: auto !important; + } + + #navbar form.search { + margin-left: 0 !important; + } + + .navbar .icons { + margin-left: 0 !important; + margin-right: 0 !important; + } +} + +article pre code.hljs, +.codewrapper pre code.hljs { + padding: 0 !important; + overflow: visible; + color: inherit; + background: transparent !important; +} + +/* ========================================================================== + Theme-aware landing hero + ========================================================================== */ + +:root:not([data-bs-theme="dark"]) .render-hero { + --render-viewport-surface: #ffffff; + --render-viewport-toolbar: rgba(248, 247, 250, 0.96); + --render-viewport-border: rgba(52, 43, 69, 0.15); + --render-viewport-text: #342b45; + --render-viewport-muted: #746b7e; + --render-viewport-shadow: 0 18px 46px rgba(41, 27, 69, 0.1), 0 2px 8px rgba(41, 27, 69, 0.06); + color: #231e2e; + background-color: #fbfafc; + background-image: none; +} + +:root:not([data-bs-theme="dark"]) .render-viewport-open:hover, +:root:not([data-bs-theme="dark"]) .render-viewport-open:focus-visible { + background: rgba(81, 43, 212, 0.08); +} + +[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%); +} + +: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); +} + +/* ========================================================================== + 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 .resources-heading { + max-width: 760px; + margin: 0 auto 56px; + text-align: center; +} + +.landing-page .section-kicker { + display: block; + margin-bottom: 14px; + color: var(--zenith-purple-700); + 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 .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 { + 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 { + display: block; + width: fit-content; + margin-inline: auto; + } + + .landing-page .architecture-copy h2 > span { + display: inline-block; + margin-left: -5px; + } + + .landing-page .section-heading h2 > .anchorjs-link { + display: none; + } + +.landing-page h2::after { + content: none; +} + +.landing-page .section-heading p { + max-width: 680px; + margin: 18px auto 0; + color: #6b7180; + font-size: 15px; + line-height: 1.65; +} + +[data-bs-theme="dark"] .landing-page .section-heading h2, +[data-bs-theme="dark"] .landing-page .architecture-copy h2, +[data-bs-theme="dark"] .landing-page .resources-heading h2 { + color: #f4f1fa; +} + +[data-bs-theme="dark"] .landing-page .section-heading p { + color: #aca4b7; +} + +.landing-page .feature-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 22px; +} + +.landing-page .feature-card { + display: flex; + min-height: 214px; + flex-direction: column; + padding: 26px 24px; + justify-content: flex-start; + background: rgba(255, 255, 255, 0.84); + border: 1px solid #e4e6ee; + border-radius: var(--zenith-panel-radius); + box-shadow: 0 8px 24px rgba(31, 43, 74, 0.045); + transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease; +} + + .landing-page .feature-card::before { + display: none; + } + + .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); + } + +.landing-page .feature-icon { + display: inline-flex; + width: 40px; + height: 40px; + align-items: center; + justify-content: center; + margin-bottom: 22px; + border: 0; + border-radius: var(--zenith-panel-radius); + font-size: 18px; +} + +.landing-page .feature-icon-violet { + color: #512bd4; + background: #f1edff !important; +} + +.landing-page .feature-icon-pink { + color: #d31491; + background: #fff0f8 !important; +} + +.landing-page .feature-icon-blue { + color: #1671e9; + background: #edf5ff !important; +} + +.landing-page .feature-icon-cyan { + color: #087f9c; + background: #e9faff !important; +} + +.landing-page .feature-card h3 { + margin: 0 0 8px; + padding: 0; + color: #172a4d; + font-size: 18px; + line-height: 1.3; +} + +.landing-page .feature-card p { + margin: 0; + color: #697184; + font-size: 14px; + line-height: 1.6; +} + +[data-bs-theme="dark"] .landing-page .feature-card { + background: rgba(25, 20, 34, 0.88); + border-color: #352c45; + box-shadow: none; +} + + [data-bs-theme="dark"] .landing-page .feature-card:hover { + background: #20182b; + border-color: rgba(155, 123, 255, 0.38); + box-shadow: 0 12px 30px rgba(4, 3, 7, 0.28); + } + + [data-bs-theme="dark"] .landing-page .feature-card h3 { + color: #f4f1fa; + } + + [data-bs-theme="dark"] .landing-page .feature-card p { + color: #aaa3b4; + } + +.architecture-section { + padding: 96px 0; +} + +.architecture-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: center; + gap: clamp(48px, 6vw, 88px); +} + +.architecture-copy .section-kicker { + margin-bottom: 14px; + text-align: left; +} + +.architecture-copy h2 { + max-width: 560px; +} + +.architecture-copy > p { + max-width: 610px; + margin: 20px 0 28px; + color: #626a7a; + font-size: 15px; + line-height: 1.7; +} + +.architecture-points { + display: grid; + gap: 12px; +} + + .architecture-points > a { + display: grid; + grid-template-columns: 26px minmax(0, 1fr); + gap: 12px; + align-items: center; + 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; + } + + .architecture-points > a:hover { + border-color: rgba(81, 43, 212, 0.32); + transform: translateX(3px); + } + + .architecture-points .architecture-step { + display: inline-flex; + width: 26px; + height: 26px; + align-items: center; + justify-content: center; + color: var(--zenith-purple-700); + background: rgba(81, 43, 212, 0.08); + border: 1px solid rgba(81, 43, 212, 0.18); + border-radius: 50%; + font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace; + font-size: 9px; + font-weight: 800; + line-height: 1; + } + + .architecture-points span, + .architecture-points strong, + .architecture-points small { + display: block; + } + + .architecture-points strong { + margin-bottom: 3px; + font-size: 13px; + } + + .architecture-points small { + color: #777d8c; + font-size: 11px; + line-height: 1.45; + } + +[data-bs-theme="dark"] .architecture-copy > p { + color: #aaa3b4; +} + +[data-bs-theme="dark"] .architecture-points > a { + color: #f4f1fa; + background: rgba(31, 24, 43, 0.88); + border-color: #3b304d; +} + +[data-bs-theme="dark"] .architecture-points small { + color: #9e96aa; +} + +.resources-section { + padding: 90px 0 96px; +} + +.landing-page .resources-heading { + margin-bottom: 50px; +} + + .landing-page .resources-heading h2 { + font-size: 36px; + } + +.resource-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 22px; +} + +.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; +} + + .resource-card:hover { + color: #ffffff !important; + box-shadow: 0 18px 38px rgba(31, 43, 91, 0.18); + transform: translateY(-4px); + } + +.resource-card-docs { + background: linear-gradient(135deg, #512bd4, #7542ed); +} + +.resource-card-nuget { + background: linear-gradient(135deg, #d10ba7, #ec35bd); +} + +.resource-card-community { + background: linear-gradient(135deg, #0869b8, #2689dc); +} + +.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; +} + +.resource-title { + display: block; + margin: 0 0 9px; + padding: 0; + color: #ffffff; + font-size: 18px; + font-weight: 800; +} + +.resource-card p { + margin: 0 0 24px; + color: rgba(255, 255, 255, 0.82); + font-size: 13px; + line-height: 1.6; +} + +.resource-card > strong { + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: auto; + color: #ffffff; + font-size: 13px; +} + +/* Keep every landing section on one continuous theme canvas. */ +.landing-page { + --landing-canvas: #fbfafc; + --landing-atmosphere: linear-gradient(145deg, rgba(81, 43, 212, 0.055) 0%, rgba(81, 43, 212, 0.012) 46%, rgba(22, 113, 233, 0.04) 100%); + --landing-grid-row: rgba(81, 43, 212, 0.034); + --landing-grid-column: rgba(52, 201, 245, 0.03); + --landing-footer-text: #667187; + --landing-footer-heading-primary: #512bd4; + --landing-footer-heading-secondary: #d31491; + --landing-footer-link: #60708a; + --landing-footer-link-hover: #512bd4; + --landing-footer-icon: #512bd4; + --landing-footer-icon-bg: rgba(81, 43, 212, 0.08); + --landing-footer-icon-bg-hover: rgba(81, 43, 212, 0.15); + --landing-footer-rule: rgba(24, 39, 70, 0.1); + background-color: var(--landing-canvas); + background-image: var(--landing-atmosphere), linear-gradient(var(--landing-grid-row) 1px, transparent 1px), linear-gradient(90deg, var(--landing-grid-column) 1px, transparent 1px); + background-size: 100% 100%, 64px 64px, 64px 64px; +} + +:root:not([data-bs-theme="dark"]) .landing-page .render-hero { + background-color: transparent; + background-image: none; +} + +[data-bs-theme="dark"] .landing-page { + --landing-canvas: #100d16; + --landing-atmosphere: linear-gradient(145deg, rgba(155, 123, 255, 0.055) 0%, rgba(155, 123, 255, 0.012) 46%, rgba(67, 216, 255, 0.035) 100%); + --landing-grid-row: rgba(155, 123, 255, 0.038); + --landing-grid-column: rgba(67, 216, 255, 0.03); + --landing-footer-text: #9f98ab; + --landing-footer-heading-primary: #b9a5ff; + --landing-footer-heading-secondary: #ff70c0; + --landing-footer-link: #aaa3b8; + --landing-footer-link-hover: #d1c4ff; + --landing-footer-icon: #b9a5ff; + --landing-footer-icon-bg: rgba(155, 123, 255, 0.1); + --landing-footer-icon-bg-hover: rgba(155, 123, 255, 0.18); + --landing-footer-rule: rgba(222, 213, 235, 0.1); +} + + [data-bs-theme="dark"] .landing-page .render-hero { + background-color: transparent; + background-image: none; + } + +.landing-page .features-section, +.landing-page .architecture-section, +.landing-page .resources-section { + background: transparent; +} + +.landing-footer { + padding: 78px 0 34px; + color: var(--landing-footer-text); + background: transparent; +} + +.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; +} + +.landing-footer-logo { + display: block; + width: auto; + height: 48px; +} + +.landing-footer-brand p { + max-width: 480px; + margin: 24px 0 28px; + color: var(--landing-footer-text); + font-size: 15px; + line-height: 1.7; +} + +.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; +} + + .landing-footer-github:hover { + color: var(--landing-footer-link-hover) !important; + background: var(--landing-footer-icon-bg-hover); + } + +.landing-footer-column { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 14px; +} + +.landing-footer-heading { + display: block; + margin: 0 0 10px; + padding: 0; + color: var(--landing-footer-heading-primary); + font-size: 14px; + font-weight: 800; +} + +.landing-footer-column:last-child .landing-footer-heading { + color: var(--landing-footer-heading-secondary); +} + +.landing-footer-column a { + color: var(--landing-footer-link); + font-size: 14px; + text-decoration: none; +} + + .landing-footer-column a:hover { + color: var(--landing-footer-link-hover); + } + +.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; +} + + .landing-footer-bottom > span:last-child { + display: flex; + gap: 28px; + } + + .landing-footer-bottom a { + color: var(--landing-footer-link); + text-decoration: none; + } + + .landing-footer-bottom a:hover { + color: var(--landing-footer-link-hover); + } + +@media (max-width: 991.98px) { + .landing-page .features-section, + .architecture-section, + .resources-section { + padding-top: 78px; + padding-bottom: 82px; + } + + .landing-page .feature-grid, + .resource-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .architecture-grid { + grid-template-columns: 1fr; + gap: 46px; + } + + .architecture-copy { + max-width: 760px; + width: 100%; + margin: 0 auto; + } + + .landing-footer-grid { + grid-template-columns: minmax(0, 1fr) 180px 180px; + gap: 36px; + } +} + +@media (max-width: 767.98px) { + .landing-page .section-heading, + .landing-page .resources-heading { + margin-bottom: 38px; + } + + .landing-page .section-heading h2, + .landing-page .architecture-copy h2, + .landing-page .resources-heading h2 { + font-size: 31px; + } + + .landing-page .feature-grid, + .resource-grid { + grid-template-columns: 1fr; + } + + .landing-page .feature-card { + min-height: 0; + } + + .landing-footer-grid { + grid-template-columns: 1fr 1fr; + } + + .landing-footer-brand { + grid-column: 1 / -1; + } +} + +@media (max-width: 575.98px) { + .landing-page .features-section, + .architecture-section, + .resources-section { + padding-top: 64px; + padding-bottom: 68px; + } + + .landing-page .feature-card, + .resource-card { + padding: 24px; + } + + .architecture-points > a { + padding: 13px 14px; + } + + .landing-footer { + padding-top: 62px; + } + + .landing-footer-grid { + grid-template-columns: 1fr; + gap: 42px; + padding-bottom: 44px; + } + + .landing-footer-brand, + .landing-footer-column { + grid-column: auto; + } + + .landing-footer-bottom { + align-items: flex-start; + flex-direction: column; + gap: 16px; + } } diff --git a/documents/templates/public/main.js b/documents/templates/public/main.js index fbf12aad..7ddb5fa4 100644 --- a/documents/templates/public/main.js +++ b/documents/templates/public/main.js @@ -1,4 +1,90 @@ -export default { +const shikiModuleUrl = 'https://esm.sh/shiki@4.3.1'; +const slangGrammarUrl = 'https://raw.githubusercontent.com/shader-slang/slang-vscode-extension/v2.0.10/syntaxes/slang.tmLanguage.json'; +const shikiLanguageAliases = { + bash: 'bash', + console: 'shellsession', + cs: 'csharp', + csharp: 'csharp', + json: 'json', + powershell: 'powershell', + shell: 'bash', + slang: 'slang', + text: 'text', + txt: 'text', + xml: 'xml', + yaml: 'yaml', + yml: 'yaml' +}; + +let fallbackHighlighter; +let shikiHighlighter; + +const loadShiki = () => { + shikiHighlighter ??= Promise.all([ + import(shikiModuleUrl), + fetch(slangGrammarUrl, { cache: 'force-cache' }).then(response => { + if (!response.ok) throw new Error(`Unable to load Slang grammar: HTTP ${response.status}`); + return response.json(); + }) + ]).then(([{ createHighlighter }, slangGrammar]) => { + slangGrammar.name = 'slang'; + + return createHighlighter({ + themes: ['light-plus', 'dark-plus'], + langs: ['bash', 'csharp', 'json', 'powershell', 'shellsession', 'xml', 'yaml', slangGrammar] + }); + }); + + return shikiHighlighter; +}; + +const highlightCode = async (code, language) => { + const source = code.textContent; + const pre = code.closest('pre'); + + try { + const highlighter = await loadShiki(); + const highlighted = highlighter.codeToHtml(source, { + lang: shikiLanguageAliases[language] || 'text', + themes: { light: 'light-plus', dark: 'dark-plus' }, + defaultColor: false + }); + const template = document.createElement('template'); + template.innerHTML = highlighted; + const shikiPre = template.content.querySelector('pre'); + const shikiCode = shikiPre?.querySelector('code'); + if (!pre || !shikiPre || !shikiCode) throw new Error('Shiki returned invalid markup.'); + + code.innerHTML = shikiCode.innerHTML; + code.classList.remove('hljs'); + code.dataset.highlighted = 'yes'; + code.dataset.shiki = 'true'; + pre.classList.add('shiki', 'shiki-themes', 'light-plus', 'dark-plus'); + + for (const property of shikiPre.style) { + if (property.startsWith('--shiki-')) { + pre.style.setProperty(property, shikiPre.style.getPropertyValue(property)); + } + } + } catch (error) { + console.warn('Shiki highlighting failed; using the DocFX highlighter.', error); + code.textContent = source; + code.classList.remove('hljs'); + code.removeAttribute('data-highlighted'); + + try { + fallbackHighlighter?.highlightElement(code); + } catch { + code.dataset.highlighted = 'yes'; + } + } +}; + +export default { + configureHljs: hljs => { + fallbackHighlighter = hljs; + hljs.registerAliases(['slang'], { languageName: 'cpp' }); + }, iconLinks: [ { icon: 'github', @@ -7,10 +93,884 @@ } ], start: () => { - // 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) { - td.style.whiteSpace = 'nowrap'; + const rootPath = document.querySelector('meta[name="docfx:rel"]')?.content || ''; + const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); + const observeUntil = (target, options, initialize, timeout = 5000) => { + if (initialize() || !target) return; + + let timeoutId; + const observer = new MutationObserver(() => { + if (!initialize()) return; + observer.disconnect(); + window.clearTimeout(timeoutId); + }); + observer.observe(target, options); + timeoutId = window.setTimeout(() => observer.disconnect(), timeout); + }; + document.body.toggleAttribute('data-zenith-api', /\/api(?:\/|$)/i.test(window.location.pathname)); + + const initializeThemeCycle = () => { + const themes = [ + { value: 'light', label: 'Light', icon: 'bi-sun' }, + { value: 'dark', label: 'Dark', icon: 'bi-moon' }, + { value: 'auto', label: 'Auto', icon: 'bi-circle-half' } + ]; + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)'); + const navbar = document.getElementById('navbar'); + if (!navbar) return; + + const enhanceThemeToggle = () => { + const dropdown = navbar.querySelector('.icons .dropdown'); + const currentToggle = dropdown?.querySelector('.dropdown-toggle'); + if (!dropdown || !currentToggle) return false; + + const themeToggle = document.createElement('button'); + const icon = document.createElement('i'); + let animationTimer; + + themeToggle.type = 'button'; + themeToggle.className = 'btn border-0 zenith-theme-toggle'; + icon.setAttribute('aria-hidden', 'true'); + themeToggle.append(icon); + dropdown.replaceWith(themeToggle); + + const getTheme = () => { + const storedTheme = window.localStorage.getItem('theme'); + return themes.find(theme => theme.value === storedTheme) || themes[2]; + }; + + const getNextTheme = theme => themes[(themes.indexOf(theme) + 1) % themes.length]; + + const renderTheme = theme => { + const nextTheme = getNextTheme(theme); + icon.className = `bi ${theme.icon}`; + themeToggle.dataset.theme = theme.value; + themeToggle.title = `${theme.label} theme; switch to ${nextTheme.label}`; + themeToggle.setAttribute( + 'aria-label', + `${theme.label} theme. Switch to ${nextTheme.label}.` + ); + }; + + const applyTheme = theme => { + const resolvedTheme = theme.value === 'auto' + ? (systemTheme.matches ? 'dark' : 'light') + : theme.value; + window.localStorage.setItem('theme', theme.value); + document.documentElement.dataset.bsTheme = resolvedTheme; + renderTheme(theme); + }; + + const switchTheme = () => { + const nextTheme = getNextTheme(getTheme()); + window.clearTimeout(animationTimer); + + if (reducedMotion.matches) { + applyTheme(nextTheme); + return; + } + + themeToggle.disabled = true; + themeToggle.classList.remove('is-theme-entering'); + themeToggle.classList.add('is-theme-leaving'); + animationTimer = window.setTimeout(() => { + applyTheme(nextTheme); + themeToggle.classList.remove('is-theme-leaving'); + themeToggle.classList.add('is-theme-entering'); + animationTimer = window.setTimeout(() => { + themeToggle.classList.remove('is-theme-entering'); + themeToggle.disabled = false; + }, 260); + }, 120); + }; + + themeToggle.addEventListener('click', switchTheme); + systemTheme.addEventListener('change', () => { + const theme = getTheme(); + if (theme.value === 'auto') applyTheme(theme); + }); + applyTheme(getTheme()); + return true; + }; + + observeUntil(navbar, { childList: true, subtree: true }, enhanceThemeToggle); + }; + + const initializeExpandableSearch = () => { + const form = document.querySelector('#navbar form.search'); + const input = form?.querySelector('#search-query'); + const nativeIcon = form?.querySelector(':scope > i'); + if (!form || !input || form.classList.contains('zenith-search')) return; + + const toggle = document.createElement('button'); + const icon = document.createElement('i'); + toggle.type = 'button'; + toggle.className = 'zenith-search-toggle'; + toggle.setAttribute('aria-controls', input.id); + toggle.setAttribute('aria-expanded', 'false'); + toggle.setAttribute('aria-label', 'Open search'); + toggle.title = 'Search'; + icon.className = 'bi bi-search'; + icon.setAttribute('aria-hidden', 'true'); + toggle.append(icon); + nativeIcon?.setAttribute('aria-hidden', 'true'); + form.prepend(toggle); + form.classList.add('zenith-search'); + const searchPlaceholder = input.placeholder || 'Search'; + + const updateAvailability = () => { + const ready = !input.disabled; + const expanded = form.classList.contains('is-expanded'); + form.classList.toggle('is-loading', !ready); + form.setAttribute('aria-busy', String(!ready)); + toggle.setAttribute('aria-label', expanded ? 'Close search' : 'Open search'); + toggle.title = ready ? (expanded ? 'Close search' : 'Search') : 'Search is loading'; + icon.className = 'bi bi-search'; + input.placeholder = ready ? searchPlaceholder : 'Search is loading...'; + }; + const availabilityObserver = new MutationObserver(updateAvailability); + availabilityObserver.observe(input, { attributes: true, attributeFilter: ['disabled'] }); + updateAvailability(); + let pendingFocusObserver; + + const setExpanded = (expanded, focusInput = false) => { + if (!expanded) { + pendingFocusObserver?.disconnect(); + pendingFocusObserver = undefined; + } + form.classList.toggle('is-expanded', expanded); + toggle.setAttribute('aria-expanded', String(expanded)); + toggle.setAttribute('aria-label', expanded ? 'Close search' : 'Open search'); + toggle.title = input.disabled ? 'Search is loading' : (expanded ? 'Close search' : 'Search'); + + if (!focusInput) return; + const focusWhenReady = () => { + if (input.disabled) return false; + input.focus({ preventScroll: true }); + return true; + }; + if (focusWhenReady()) return; + + pendingFocusObserver?.disconnect(); + pendingFocusObserver = new MutationObserver(() => { + if (!form.classList.contains('is-expanded')) { + pendingFocusObserver?.disconnect(); + pendingFocusObserver = undefined; + return; + } + if (!focusWhenReady()) return; + pendingFocusObserver?.disconnect(); + pendingFocusObserver = undefined; + }); + pendingFocusObserver.observe(input, { attributes: true, attributeFilter: ['disabled'] }); + }; + + const clearSearch = () => { + if (!input.value) return; + input.value = ''; + input.dispatchEvent(new Event('input', { bubbles: true })); + }; + + toggle.addEventListener('click', () => { + const expanded = form.classList.contains('is-expanded'); + if (expanded) { + clearSearch(); + setExpanded(false); + toggle.focus({ preventScroll: true }); + } else { + setExpanded(true, true); + } + }); + + input.addEventListener('focus', () => setExpanded(true)); + input.addEventListener('keydown', event => { + if (event.key !== 'Escape') return; + event.preventDefault(); + clearSearch(); + setExpanded(false); + toggle.focus({ preventScroll: true }); + }); + document.addEventListener('pointerdown', event => { + if (!form.classList.contains('is-expanded') || form.contains(event.target)) return; + if (!input.value && !document.body.hasAttribute('data-search')) setExpanded(false); + }); + + if (input.value || document.body.hasAttribute('data-search')) setExpanded(true); + }; + + const initializeReadingMotion = () => { + const header = document.querySelector('body > header'); + if (!header) return; + + const progress = document.createElement('div'); + const progressValue = document.createElement('span'); + progress.className = 'zenith-reading-progress'; + progress.setAttribute('aria-hidden', 'true'); + progress.append(progressValue); + header.append(progress); + + const hasScrollTimeline = window.CSS?.supports?.('animation-timeline: scroll()') === true; + + const updateScrollState = () => { + const scrollRange = Math.max(1, document.documentElement.scrollHeight - window.innerHeight); + const value = Math.min(1, Math.max(0, window.scrollY / scrollRange)); + if (!hasScrollTimeline) progressValue.style.transform = `scaleX(${value.toFixed(4)})`; + header.classList.toggle('is-scrolled', window.scrollY > 8); + }; + + window.addEventListener('scroll', updateScrollState, { passive: true }); + window.addEventListener('resize', updateScrollState, { passive: true }); + updateScrollState(); + }; + + const initializeOverlayScrollbars = () => { + const initialized = new WeakSet(); + const hideTimers = new WeakMap(); + const updates = new WeakMap(); + + const attach = (scroller, host) => { + if (!scroller || !host) return; + if (initialized.has(scroller)) { + updates.get(scroller)?.(); + return; + } + initialized.add(scroller); + host.classList.add('zenith-scroll-host'); + + const track = document.createElement('span'); + const thumb = document.createElement('span'); + track.className = 'zenith-overlay-scrollbar'; + track.setAttribute('role', 'scrollbar'); + track.setAttribute('aria-label', host.id === 'toc' + ? 'Table of contents scrollbar' + : 'In this article scrollbar'); + track.setAttribute('aria-orientation', 'vertical'); + track.setAttribute('aria-valuemin', '0'); + if (!scroller.id) scroller.id = `${host.id || 'zenith'}-scroll-region`; + track.setAttribute('aria-controls', scroller.id); + track.append(thumb); + host.append(track); + + const update = () => { + const hostRect = host.getBoundingClientRect(); + const scrollerRect = scroller.getBoundingClientRect(); + const trackStyle = window.getComputedStyle(track); + const scrollbarSize = Number.parseFloat( + trackStyle.getPropertyValue('--zenith-scrollbar-size') + ) || track.offsetWidth; + const edgeGap = Number.parseFloat( + trackStyle.getPropertyValue('--zenith-scrollbar-edge-gap') + ) || (scrollbarSize / 2); + const minThumbHeight = Number.parseFloat( + trackStyle.getPropertyValue('--zenith-scrollbar-min-thumb') + ) || (scrollbarSize * 4.5); + const trackHeight = Math.max(0, scroller.clientHeight - (edgeGap * 2)); + const scrollRange = Math.max(0, scroller.scrollHeight - scroller.clientHeight); + const thumbHeight = scrollRange + ? Math.max(minThumbHeight, trackHeight * (scroller.clientHeight / scroller.scrollHeight)) + : trackHeight; + const thumbRange = Math.max(0, trackHeight - thumbHeight); + const thumbOffset = scrollRange ? (scroller.scrollTop / scrollRange) * thumbRange : 0; + + track.style.top = `${scrollerRect.top - hostRect.top + edgeGap}px`; + track.style.height = `${trackHeight}px`; + track.tabIndex = scrollRange > 1 ? 0 : -1; + track.setAttribute('aria-valuemax', `${Math.round(scrollRange)}`); + track.setAttribute('aria-valuenow', `${Math.round(scroller.scrollTop)}`); + thumb.style.height = `${thumbHeight}px`; + thumb.style.transform = `translateY(${thumbOffset}px)`; + host.classList.toggle('has-scroll-range', scrollRange > 1); + }; + updates.set(scroller, update); + + const showWhileScrolling = () => { + host.classList.add('is-scrolling'); + window.clearTimeout(hideTimers.get(host)); + hideTimers.set(host, window.setTimeout(() => { + host.classList.remove('is-scrolling'); + }, 700)); + update(); + }; + + scroller.addEventListener('scroll', showWhileScrolling, { passive: true }); + window.addEventListener('resize', update, { passive: true }); + const resizeObserver = typeof ResizeObserver === 'undefined' + ? null + : new ResizeObserver(update); + resizeObserver?.observe(scroller); + resizeObserver?.observe(host); + new MutationObserver(update).observe(scroller, { childList: true, subtree: true }); + + track.addEventListener('keydown', event => { + const lineStep = scroller.clientHeight / 10; + const pageStep = scroller.clientHeight; + const scrollRange = Math.max(0, scroller.scrollHeight - scroller.clientHeight); + const target = { + ArrowUp: scroller.scrollTop - lineStep, + ArrowDown: scroller.scrollTop + lineStep, + PageUp: scroller.scrollTop - pageStep, + PageDown: scroller.scrollTop + pageStep, + Home: 0, + End: scrollRange + }[event.key]; + if (target === undefined) return; + + event.preventDefault(); + scroller.scrollTop = Math.min(scrollRange, Math.max(0, target)); + }); + + track.addEventListener('pointerdown', event => { + if (!host.classList.contains('has-scroll-range')) return; + event.preventDefault(); + + const trackRect = track.getBoundingClientRect(); + const thumbRect = thumb.getBoundingClientRect(); + const startY = event.clientY; + let startScrollTop = scroller.scrollTop; + const thumbRange = Math.max(1, trackRect.height - thumbRect.height); + const scrollRange = Math.max(0, scroller.scrollHeight - scroller.clientHeight); + + if (event.target !== thumb) { + const targetOffset = Math.min( + thumbRange, + Math.max(0, event.clientY - trackRect.top - (thumbRect.height / 2)) + ); + scroller.scrollTop = (targetOffset / thumbRange) * scrollRange; + startScrollTop = scroller.scrollTop; + } + + track.classList.add('is-dragging'); + track.setPointerCapture(event.pointerId); + const move = moveEvent => { + scroller.scrollTop = startScrollTop + + ((moveEvent.clientY - startY) / thumbRange) * scrollRange; + }; + const stop = () => { + track.classList.remove('is-dragging'); + track.removeEventListener('pointermove', move); + track.removeEventListener('pointerup', stop); + track.removeEventListener('pointercancel', stop); + }; + track.addEventListener('pointermove', move); + track.addEventListener('pointerup', stop); + track.addEventListener('pointercancel', stop); + }); + update(); + }; + + const initialize = () => { + const tocScroller = document.querySelector('#toc > .overflow-y-auto'); + attach(tocScroller, document.querySelector('#toc')); + + const affix = document.querySelector('#affix'); + attach(affix, affix?.parentElement); + }; + + const observer = new MutationObserver(initialize); + for (const host of document.querySelectorAll('#toc, main > .affix')) { + observer.observe(host, { childList: true, subtree: true }); + } + initialize(); + }; + + const copyText = async (text) => { + try { + await navigator.clipboard.writeText(text); + return; + } catch { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + document.body.append(textarea); + textarea.select(); + + const copied = document.execCommand('copy'); + textarea.remove(); + if (!copied) throw new Error('Copy failed'); + } + }; + + const copyResetTimers = new WeakMap(); + const setCopyButtonState = (button, state) => { + const defaultLabel = button.dataset.copyLabel || button.getAttribute('aria-label') || 'Copy'; + const icon = button.querySelector('i'); + const feedback = { + idle: { icon: 'bi bi-copy', label: defaultLabel }, + copied: { icon: 'bi bi-check2', label: 'Copied' }, + failed: { icon: 'bi bi-x-lg', label: 'Copy failed' } + }[state]; + + button.dataset.copyLabel = defaultLabel; + button.classList.toggle('is-copied', state === 'copied'); + button.classList.toggle('is-failed', state === 'failed'); + button.setAttribute('aria-label', feedback.label); + button.title = feedback.label; + if (icon) icon.className = feedback.icon; + + const activeTimer = copyResetTimers.get(button); + if (activeTimer) window.clearTimeout(activeTimer); + + if (state === 'idle') { + copyResetTimers.delete(button); + } else { + copyResetTimers.set(button, window.setTimeout(() => { + copyResetTimers.delete(button); + setCopyButtonState(button, 'idle'); + }, 2000)); + } + }; + + const copyWithFeedback = async (button, text) => { + try { + await copyText(text); + setCopyButtonState(button, 'copied'); + } catch { + setCopyButtonState(button, 'failed'); + } + }; + + const brand = document.querySelector('.navbar-brand'); + if (brand && !brand.hasAttribute('aria-label')) { + brand.setAttribute('aria-label', 'Zenith.NET home'); + } + initializeThemeCycle(); + initializeExpandableSearch(); + + const languageNames = { + bash: 'Shell', + csharp: 'C#', + cs: 'C#', + console: 'Console', + json: 'JSON', + powershell: 'PowerShell', + shell: 'Shell', + slang: 'Slang', + xml: 'XML', + yaml: 'YAML', + yml: 'YAML' + }; + + const createCodeFrame = pre => { + const code = pre.querySelector('code'); + if (!code) return null; + + const languageClass = [...code.classList].find(name => name.startsWith('language-') || name.startsWith('lang-')); + const language = languageClass?.replace(/^language-|^lang-/, '').toLowerCase() || 'text'; + const existingFrame = pre.closest('.doc-code-frame'); + if (existingFrame) return { code, language }; + const frame = document.createElement('div'); + const toolbar = document.createElement('div'); + const languageLabel = document.createElement('span'); + const copy = document.createElement('button'); + + frame.className = 'doc-code-frame'; + toolbar.className = 'doc-code-toolbar'; + languageLabel.className = 'doc-code-language'; + languageLabel.textContent = languageNames[language] || language.toUpperCase(); + copy.type = 'button'; + copy.className = 'doc-code-copy'; + copy.setAttribute('aria-label', 'Copy code block'); + copy.innerHTML = ''; + setCopyButtonState(copy, 'idle'); + + pre.parentNode.insertBefore(frame, pre); + frame.append(toolbar, pre); + toolbar.append(languageLabel); + toolbar.append(copy); + pre.querySelector(':scope > .code-action')?.remove(); + + copy.addEventListener('click', async () => { + await copyWithFeedback(copy, code.textContent); + }); + + return { code, language }; + }; + + for (const pre of document.querySelectorAll('body:not([data-layout="landing"]) article pre')) { + const controls = createCodeFrame(pre); + if (controls) void highlightCode(controls.code, controls.language); + } + + for (const table of document.querySelectorAll('body:not([data-layout="landing"]) article table')) { + if (table.closest('.doc-table-frame')) continue; + + const frame = document.createElement('div'); + frame.className = 'doc-table-frame'; + const responsive = table.closest('.table-responsive'); + const target = responsive || table; + target.parentNode.insertBefore(frame, target); + frame.append(target); + } + + const initializeTableScrollCues = () => { + const frames = [...document.querySelectorAll('.doc-table-frame')]; + if (!frames.length) return; + + const observedScrollers = new WeakSet(); + const updates = new WeakMap(); + const resizeObserver = typeof ResizeObserver === 'undefined' + ? null + : new ResizeObserver(entries => { + for (const entry of entries) updates.get(entry.target)?.(); + }); + + const initializeFrame = frame => { + const scroller = frame.querySelector('.table-responsive'); + if (!scroller || observedScrollers.has(scroller)) return false; + observedScrollers.add(scroller); + + const update = () => { + const maxScroll = Math.max(0, scroller.scrollWidth - scroller.clientWidth); + frame.classList.toggle('is-scrollable', maxScroll > 1); + frame.classList.toggle('is-scroll-end', scroller.scrollLeft >= maxScroll - 1); + }; + + scroller.addEventListener('scroll', update, { passive: true }); + if (!resizeObserver) window.addEventListener('resize', update, { passive: true }); + updates.set(scroller, update); + resizeObserver?.observe(scroller); + + const table = scroller.querySelector('table'); + if (table) { + updates.set(table, update); + resizeObserver?.observe(table); + } + update(); + return true; + }; + + const pendingFrames = new Set(frames); + const initializePendingFrames = () => { + for (const frame of pendingFrames) { + if (initializeFrame(frame)) pendingFrames.delete(frame); + } + if (!pendingFrames.size) mutationObserver.disconnect(); + }; + const mutationObserver = new MutationObserver(initializePendingFrames); + + for (const frame of pendingFrames) { + mutationObserver.observe(frame, { childList: true, subtree: true }); + } + initializePendingFrames(); + }; + + const initializeTutorialCarousel = () => { + const carousel = document.querySelector('[data-tutorial-carousel]'); + if (!carousel) return; + + const slides = [...carousel.querySelectorAll('.render-carousel-slide')]; + const title = carousel.querySelector('[data-carousel-title]'); + const position = carousel.querySelector('[data-carousel-position]'); + const status = carousel.querySelector('[data-carousel-status]'); + const progress = carousel.querySelector('[data-carousel-progress]'); + const openLink = carousel.querySelector('[data-carousel-open]'); + const toggleButton = carousel.querySelector('[data-carousel-toggle]'); + const previousButton = carousel.querySelector('[data-carousel-prev]'); + const nextButton = carousel.querySelector('[data-carousel-next]'); + const mobileViewport = window.matchMedia('(max-width: 767.98px)'); + const autoPlayDuration = 6500; + let activeIndex = Math.max(0, slides.findIndex(slide => slide.classList.contains('is-active'))); + let autoPlayTimer = 0; + let autoPlayRemaining = autoPlayDuration; + let autoPlayStartedAt = 0; + let isAutoPlayPaused = false; + let isVisible = true; + let touchStartX = 0; + let touchStartY = 0; + + if (slides.length < 2 || !title || !position || !progress || !openLink || !toggleButton || !previousButton || !nextButton) return; + + const prepareSlide = index => { + const image = slides[index]?.querySelector('img[data-src]'); + if (!image?.dataset.src) return; + + image.src = image.dataset.src; + image.removeAttribute('data-src'); + }; + + const stopAutoPlay = () => { + window.clearTimeout(autoPlayTimer); + autoPlayTimer = 0; + if (autoPlayStartedAt) { + autoPlayRemaining = Math.max(0, autoPlayRemaining - (performance.now() - autoPlayStartedAt)); + autoPlayStartedAt = 0; + } + carousel.classList.remove('is-carousel-playing'); + }; + + const canAutoPlay = () => !isAutoPlayPaused && + !reducedMotion.matches && + !mobileViewport.matches && + !document.hidden && + isVisible && + !carousel.matches(':hover') && + !carousel.matches(':focus-within'); + + const scheduleAutoPlay = () => { + if (!canAutoPlay()) { + stopAutoPlay(); + return; + } + if (autoPlayTimer) return; + + autoPlayStartedAt = performance.now(); + carousel.classList.add('is-carousel-playing'); + autoPlayTimer = window.setTimeout(() => { + autoPlayTimer = 0; + autoPlayStartedAt = 0; + autoPlayRemaining = autoPlayDuration; + showSlide(activeIndex + 1); + }, Math.max(16, autoPlayRemaining)); + }; + + const showSlide = (index, announce = false) => { + stopAutoPlay(); + activeIndex = (index + slides.length) % slides.length; + prepareSlide(activeIndex); + prepareSlide((activeIndex + 1) % slides.length); + + for (const [slideIndex, slide] of slides.entries()) { + const isActive = slideIndex === activeIndex; + slide.classList.toggle('is-active', isActive); + slide.setAttribute('aria-hidden', String(!isActive)); + if (isActive) { + slide.setAttribute('aria-current', 'true'); + } else { + slide.removeAttribute('aria-current'); + } + slide.tabIndex = isActive ? 0 : -1; + } + + const activeSlide = slides[activeIndex]; + const activeTitle = activeSlide.dataset.carouselTitle || `Tutorial ${activeIndex + 1}`; + title.textContent = activeTitle; + position.textContent = `${activeIndex + 1} / ${slides.length}`; + openLink.href = activeSlide.href; + openLink.setAttribute('aria-label', `Open ${activeTitle} tutorial`); + if (announce && status) { + status.textContent = `${activeTitle}, slide ${activeIndex + 1} of ${slides.length}`; + } + + autoPlayRemaining = autoPlayDuration; + progress.classList.remove('is-timing'); + void progress.offsetWidth; + progress.classList.add('is-timing'); + scheduleAutoPlay(); + }; + + const updateToggleButton = () => { + const label = isAutoPlayPaused ? 'Start automatic slide rotation' : 'Pause automatic slide rotation'; + toggleButton.setAttribute('aria-label', label); + toggleButton.title = label; + toggleButton.innerHTML = ``; + }; + + toggleButton.addEventListener('click', () => { + isAutoPlayPaused = !isAutoPlayPaused; + updateToggleButton(); + if (isAutoPlayPaused) { + stopAutoPlay(); + } else { + autoPlayRemaining = autoPlayDuration; + scheduleAutoPlay(); + } + }); + previousButton.addEventListener('click', () => showSlide(activeIndex - 1, true)); + nextButton.addEventListener('click', () => showSlide(activeIndex + 1, true)); + + carousel.addEventListener('keydown', event => { + if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; + + event.preventDefault(); + showSlide(activeIndex + (event.key === 'ArrowRight' ? 1 : -1), true); + }); + carousel.addEventListener('mouseenter', stopAutoPlay); + carousel.addEventListener('mouseleave', scheduleAutoPlay); + carousel.addEventListener('focusin', stopAutoPlay); + carousel.addEventListener('focusout', () => window.requestAnimationFrame(scheduleAutoPlay)); + carousel.addEventListener('touchstart', event => { + const touch = event.changedTouches[0]; + touchStartX = touch.clientX; + touchStartY = touch.clientY; + stopAutoPlay(); + }, { passive: true }); + carousel.addEventListener('touchend', event => { + const touch = event.changedTouches[0]; + const deltaX = touch.clientX - touchStartX; + const deltaY = touch.clientY - touchStartY; + if (Math.abs(deltaX) >= 44 && Math.abs(deltaX) > Math.abs(deltaY)) { + showSlide(activeIndex + (deltaX < 0 ? 1 : -1), true); + } else { + scheduleAutoPlay(); + } + }, { passive: true }); + + document.addEventListener('visibilitychange', scheduleAutoPlay); + reducedMotion.addEventListener('change', scheduleAutoPlay); + mobileViewport.addEventListener('change', scheduleAutoPlay); + + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(entries => { + isVisible = entries[0]?.isIntersecting ?? true; + scheduleAutoPlay(); + }, { threshold: 0.2 }); + observer.observe(carousel); + } + + carousel.classList.add('is-carousel-ready'); + showSlide(activeIndex); + }; + + const initializeRevealMotion = () => { + const selectors = document.body.matches('[data-layout="landing"]') + ? [ + '.section-heading', + '.feature-card', + '.architecture-copy', + '.resources-heading', + '.resource-card', + '.landing-footer-grid > *' + ] + : [ + 'article h1:first-of-type', + 'article h2', + 'article .alert', + 'article .doc-code-frame', + 'article .doc-table-frame', + 'article .tabGroup' + ]; + const targets = [...document.querySelectorAll(selectors.join(','))] + .filter(element => element.getClientRects().length); + if (!targets.length || reducedMotion.matches) { + targets.forEach(element => element.classList.add('is-visible')); + return; + } + + for (const [index, target] of targets.entries()) { + target.classList.add('zenith-reveal'); + target.style.setProperty('--zenith-reveal-delay', `${Math.min(index % 4, 3) * 45}ms`); + if (document.body.matches('[data-layout="landing"]')) { + const direction = target.matches('.architecture-grid > .architecture-copy:first-child') + ? 'left' + : target.matches('.architecture-grid > .architecture-copy:last-child') + ? 'right' + : 'up'; + target.dataset.revealFrom = direction; + } + } + document.documentElement.classList.add('zenith-motion-ready'); + + const pending = new Set(targets); + const revealVisibleTargets = () => { + const revealBoundary = window.innerHeight * 0.92; + for (const target of pending) { + const rect = target.getBoundingClientRect(); + if (rect.top >= revealBoundary || rect.bottom <= 0) continue; + + target.classList.add('is-visible'); + pending.delete(target); + } + + if (!pending.size) { + window.removeEventListener('scroll', revealVisibleTargets); + window.removeEventListener('resize', revealVisibleTargets); + } + }; + + window.addEventListener('scroll', revealVisibleTargets, { passive: true }); + window.addEventListener('resize', revealVisibleTargets, { passive: true }); + revealVisibleTargets(); + }; + + initializeReadingMotion(); + initializeOverlayScrollbars(); + initializeTableScrollCues(); + initializeTutorialCarousel(); + + const initializeAffix = () => { + const links = [...document.querySelectorAll('#affix a[href^="#"]')]; + const targetGroups = new Map(); + const targetIndexes = new Map(); + + for (const link of links) { + const targetId = decodeURIComponent(link.hash.slice(1)); + if (!targetGroups.has(targetId)) { + targetGroups.set(targetId, [...document.querySelectorAll(`[id="${CSS.escape(targetId)}"]`)]); + } + } + + const affixLinks = links + .map(link => { + const targetId = decodeURIComponent(link.hash.slice(1)); + const targetIndex = targetIndexes.get(targetId) || 0; + const targets = targetGroups.get(targetId) || []; + const target = targets[targetIndex] || targets[0]; + targetIndexes.set(targetId, targetIndex + 1); + + if (target && targetIndex > 0) { + const uniqueId = `${targetId}--${targetIndex + 1}`; + target.id = uniqueId; + link.setAttribute('href', `#${uniqueId}`); + } + + return { link, target }; + }) + .filter(item => { + const isVisible = item.target?.getClientRects().length; + if (!isVisible) item.link.closest('li')?.style.setProperty('display', 'none'); + return isVisible; + }); + if (!affixLinks.length) return false; + + const updateAffix = () => { + const scrollPaddingTop = Number.parseFloat( + window.getComputedStyle(document.documentElement).scrollPaddingTop + ) || 0; + let current = affixLinks[0]; + let start = 0; + let end = affixLinks.length - 1; + + while (start <= end) { + const middle = Math.floor((start + end) / 2); + const item = affixLinks[middle]; + const scrollMarginTop = Number.parseFloat( + window.getComputedStyle(item.target).scrollMarginTop + ) || 0; + const targetOffset = scrollPaddingTop + scrollMarginTop + 1; + + if (item.target.getBoundingClientRect().top <= targetOffset) { + current = item; + start = middle + 1; + } else { + end = middle - 1; + } + } + + for (const item of affixLinks) { + item.link.classList.toggle('is-active', item === current); + } + }; + + window.addEventListener('scroll', updateAffix, { passive: true }); + updateAffix(); + return true; + }; + + const normalizePath = path => path.replace(/\/index\.html$/, '/'); + const siteRootPath = normalizePath(new URL(rootPath || './', window.location.href).pathname); + const currentPath = normalizePath(window.location.pathname); + for (const link of document.querySelectorAll('#navbar .nav-link')) { + const linkPath = normalizePath(new URL(link.href, window.location.href).pathname); + const isHome = linkPath === siteRootPath && currentPath === siteRootPath; + const isSection = linkPath !== siteRootPath && currentPath.startsWith(linkPath); + const isActive = isHome || isSection; + link.classList.toggle('active', isActive); + if (isActive) { + link.setAttribute('aria-current', 'page'); + } else { + link.removeAttribute('aria-current'); } } @@ -19,10 +979,10 @@ dl.style.display = 'none'; } - // Hide protected and override members from API pages + // Hide protected members from API pages for (const code of document.querySelectorAll('.codewrapper pre code')) { const text = code.textContent.trim(); - if (!/^protected\s/.test(text) && !/\boverride\b/.test(text)) continue; + if (!/^(?:private\s+)?protected\b/.test(text)) continue; const wrapper = code.closest('.codewrapper'); if (!wrapper) continue; @@ -70,25 +1030,78 @@ } } + observeUntil( + document.querySelector('main > .affix'), + { childList: true, subtree: true }, + initializeAffix + ); + + initializeRevealMotion(); + // Search results: navigate in same tab with back button support - document.addEventListener('click', (e) => { - const link = e.target.closest('#search-results .sr-item a'); + document.addEventListener('click', event => { + const link = event.target instanceof Element + ? event.target.closest('#search-results .sr-item a') + : null; if (!link) return; + if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || + event.shiftKey || event.altKey || link.hasAttribute('download') || + (link.target && link.target !== '_self')) return; - e.preventDefault(); + event.preventDefault(); const query = document.getElementById('search-query')?.value || ''; - history.replaceState({ search: true, query }, ''); + history.replaceState({ search: true, query, scrollY: window.scrollY }, ''); window.location.href = link.href; }); - window.addEventListener('popstate', (e) => { - if (e.state?.search) { - const input = document.getElementById('search-query'); - if (input) { - input.value = e.state.query; - input.dispatchEvent(new Event('input', { bubbles: true })); - } - } - }); + const restoreSearchState = (state) => { + if (!state?.search) return; + + const input = document.getElementById('search-query'); + if (!input) return; + + input.value = state.query || ''; + input.dispatchEvent(new Event('input', { bubbles: true })); + + if (!Number.isFinite(state.scrollY)) return; + + const restoreScroll = () => { + if (!document.body.hasAttribute('data-search')) return false; + + const maxScroll = Math.max(0, document.documentElement.scrollHeight - window.innerHeight); + if (maxScroll < state.scrollY) return false; + + window.scrollTo(0, state.scrollY); + return true; + }; + + if (restoreScroll()) return; + + const observer = new MutationObserver(() => { + if (restoreScroll()) observer.disconnect(); + }); + observer.observe(document.body, { attributes: true, childList: true, subtree: true }); + window.setTimeout(() => { + restoreScroll(); + observer.disconnect(); + }, 2000); + }; + + window.addEventListener('popstate', (e) => restoreSearchState(e.state)); + + const restoreInitialSearchState = () => { + if (!history.state?.search) return; + + const input = document.getElementById('search-query'); + if (!input) return; + + observeUntil(input, { attributes: true, attributeFilter: ['disabled'] }, () => { + if (input.disabled) return; + restoreSearchState(history.state); + return true; + }); + }; + + restoreInitialSearchState(); } -} \ No newline at end of file +} diff --git a/documents/tutorials/advanced/mesh-shading.md b/documents/tutorials/advanced/mesh-shading.md deleted file mode 100644 index 19440d74..00000000 --- a/documents/tutorials/advanced/mesh-shading.md +++ /dev/null @@ -1,694 +0,0 @@ -# Mesh Shading - -In this tutorial, you'll render 1,000 procedural UV spheres using the mesh shader pipeline with GPU-driven frustum culling. This demonstrates the modern mesh shading approach where geometry is generated and culled entirely on the GPU. - -> [!NOTE] -> This tutorial requires a GPU with mesh shading support (e.g., NVIDIA Turing+, AMD RDNA 2+, or Apple M3+). - -## Overview - -This tutorial covers: - -- Creating a **mesh shading pipeline** with amplification, mesh, and pixel stages -- Generating **procedural sphere geometry** (vertices and triangles) on the CPU -- Implementing **GPU-driven frustum culling** in the amplification shader -- Using `groupshared` memory and atomic operations for visible instance compaction -- Extracting **frustum planes** from the view-projection matrix -- Dispatching mesh groups with `DispatchMesh` - -## Key Concepts - -### Mesh Shader Pipeline - -The mesh shader pipeline replaces the traditional vertex/geometry pipeline: - -| Stage | Role | Thread Group Size | -|-------|------|-------------------| -| **Amplification** | Decides which mesh groups to spawn (culling) | 32 | -| **Mesh** | Outputs vertices and triangles per group | 120 | -| **Pixel** | Standard fragment shading | — | - -### Frustum Culling - -The amplification shader tests each instance's bounding sphere against 6 frustum planes. Only visible instances are passed to mesh shader groups via a payload: - -``` -Payload { InstanceIndices[ASGroupSize] } - -// Amplification: -visible = !IsFrustumCulled(position, radius) -if (visible) payload.InstanceIndices[atomicAdd(count)] = instanceIndex -DispatchMesh(visibleCount, 1, 1, payload) -``` - -## The Renderer Class - -Create the file `Renderers/MeshShadingRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal unsafe class MeshShadingRenderer : IRenderer -{ - private const uint ASGroupSize = 32; - private const uint MeshGroupSize = 120; - private const uint GridSize = 10; - private const uint TotalInstances = GridSize * GridSize * GridSize; - private const uint DispatchGroupCount = (TotalInstances + ASGroupSize - 1) / ASGroupSize; - - private const string ShaderSource = """ - static const uint GridSize = 10; - static const uint TotalInstances = GridSize * GridSize * GridSize; - static const float InstanceSpacing = 2.5; - static const uint ASGroupSize = 32; - static const float BoundingSphereRadius = 0.5; - - static const uint SphereVertexCount = 62; - static const uint SphereTriangleCount = 120; - static const float GridOffset = float(GridSize - 1) * 0.5 * InstanceSpacing; - - struct Vertex - { - private float4 PositionAndPadding; - - private float4 NormalAndPadding; - - property float3 Position - { - get { - return PositionAndPadding.xyz; - } - } - - property float3 Normal - { - get { - return NormalAndPadding.xyz; - } - } - }; - - struct Triangle - { - private uint4 IndicesAndPadding; - - property uint3 Indices - { - get { - return IndicesAndPadding.xyz; - } - } - }; - - struct Payload - { - uint InstanceIndices[ASGroupSize]; - }; - - struct VertexOutput - { - float4 Position : SV_POSITION; - - float3 WorldNormal : WORLDNORMAL; - - float3 Color : COLOR; - }; - - struct Constants - { - float4x4 ViewProjection; - - float4 FrustumPlanes[6]; - - private float4 TimeAndLightDirection; - - property float Time - { - get { - return TimeAndLightDirection.x; - } - } - - property float3 LightDirection - { - get { - return TimeAndLightDirection.yzw; - } - } - }; - - void DecomposeInstanceID(uint id, out uint x, out uint y, out uint z) - { - x = id % GridSize; - y = (id / GridSize) % GridSize; - z = id / (GridSize * GridSize); - } - - float3 InstancePosition(uint id) - { - uint x, y, z; - DecomposeInstanceID(id, x, y, z); - return float3(x, y, z) * InstanceSpacing - GridOffset; - } - - float3 InstanceColor(uint id) - { - uint x, y, z; - DecomposeInstanceID(id, x, y, z); - return float3(x, y, z) / float(GridSize - 1); - } - - bool IsFrustumCulled(float3 center, float radius) - { - for (uint i = 0; i < 6; i++) - { - float4 plane = constants.FrustumPlanes[i]; - if (dot(plane.xyz, center) + plane.w < -radius) - { - return true; - } - } - return false; - } - - ConstantBuffer constants; - StructuredBuffer vertices; - StructuredBuffer indices; - - groupshared Payload s_payload; - groupshared uint s_visibleCount; - - [shader("amplification")] - [numthreads(ASGroupSize, 1, 1)] - void ASMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID) - { - uint instanceIndex = groupID * ASGroupSize + groupThreadID; - - bool visible = false; - if (instanceIndex < TotalInstances) - { - float3 worldPos = InstancePosition(instanceIndex); - visible = !IsFrustumCulled(worldPos, BoundingSphereRadius); - } - - if (groupThreadID == 0) - { - s_visibleCount = 0; - } - - GroupMemoryBarrierWithGroupSync(); - - if (visible) - { - uint offset; - InterlockedAdd(s_visibleCount, 1, offset); - s_payload.InstanceIndices[offset] = instanceIndex; - } - - GroupMemoryBarrierWithGroupSync(); - - DispatchMesh(s_visibleCount, 1, 1, s_payload); - } - - [shader("mesh")] - [numthreads(120, 1, 1)] - [outputtopology("triangle")] - void MSMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID, in payload Payload meshPayload, - OutputVertices outVertices, OutputIndices outIndices) - { - uint instanceIndex = meshPayload.InstanceIndices[groupID]; - float3 instancePos = InstancePosition(instanceIndex); - float3 color = InstanceColor(instanceIndex); - - SetMeshOutputCounts(SphereVertexCount, SphereTriangleCount); - - if (groupThreadID < SphereVertexCount) - { - Vertex v = vertices[groupThreadID]; - float3 worldPos = v.Position + instancePos; - - VertexOutput output; - output.Position = mul(float4(worldPos, 1.0), constants.ViewProjection); - output.WorldNormal = v.Normal; - output.Color = color; - - outVertices[groupThreadID] = output; - } - - if (groupThreadID < SphereTriangleCount) - { - outIndices[groupThreadID] = indices[groupThreadID].Indices; - } - } - - [shader("pixel")] - float4 PSMain(VertexOutput input) : SV_TARGET - { - float3 lightDir = normalize(constants.LightDirection); - float3 normal = normalize(input.WorldNormal); - float ndotl = max(dot(normal, lightDir), 0.0); - - float3 ambient = input.Color * 0.15; - float3 diffuse = input.Color * ndotl * 0.85; - - return float4(ambient + diffuse, 1.0); - } - """; - - private readonly Buffer vertexBuffer; - private readonly Buffer indexBuffer; - private readonly Buffer constantsBuffer; - private readonly ResourceLayout resourceLayout; - private readonly ResourceTable resourceTable; - private readonly MeshShadingPipeline pipeline; - - private float totalTime; - - public MeshShadingRenderer() - { - if (!App.Context.Capabilities.MeshShadingSupported) - { - throw new NotSupportedException("Mesh shading is not supported on this device."); - } - - const int lonSegments = 12; - const int latSegments = 6; - const float radius = 0.5f; - - List sphereVertices = []; - List sphereTriangles = []; - - sphereVertices.Add(new() { Position = new(0, radius, 0), Normal = Vector3.UnitY }); - - for (int lat = 1; lat < latSegments; lat++) - { - float phi = MathF.PI * lat / latSegments; - float sinPhi = MathF.Sin(phi); - float cosPhi = MathF.Cos(phi); - - for (int lon = 0; lon < lonSegments; lon++) - { - float theta = 2.0f * MathF.PI * lon / lonSegments; - Vector3 normal = new(sinPhi * MathF.Cos(theta), cosPhi, sinPhi * MathF.Sin(theta)); - - sphereVertices.Add(new() { Position = normal * radius, Normal = normal }); - } - } - - sphereVertices.Add(new() { Position = new(0, -radius, 0), Normal = -Vector3.UnitY }); - - for (int lon = 0; lon < lonSegments; lon++) - { - uint next = (uint)((lon + 1) % lonSegments); - - sphereTriangles.Add(new() { Index0 = 0, Index1 = (uint)(1 + lon), Index2 = 1 + next }); - } - - for (int lat = 0; lat < latSegments - 2; lat++) - { - for (int lon = 0; lon < lonSegments; lon++) - { - uint next = (uint)((lon + 1) % lonSegments); - uint tl = (uint)(1 + (lat * lonSegments) + lon); - uint tr = (uint)(1 + (lat * lonSegments)) + next; - uint bl = (uint)(1 + ((lat + 1) * lonSegments) + lon); - uint br = (uint)(1 + ((lat + 1) * lonSegments)) + next; - - sphereTriangles.Add(new() { Index0 = tl, Index1 = bl, Index2 = tr }); - sphereTriangles.Add(new() { Index0 = tr, Index1 = bl, Index2 = br }); - } - } - - uint bottomPole = (uint)(sphereVertices.Count - 1); - uint lastRing = 1 + ((latSegments - 2) * lonSegments); - - for (int lon = 0; lon < lonSegments; lon++) - { - uint next = (uint)((lon + 1) % lonSegments); - - sphereTriangles.Add(new() { Index0 = bottomPole, Index1 = lastRing + next, Index2 = lastRing + (uint)lon }); - } - - Vertex[] vertexData = [.. sphereVertices]; - Triangle[] triangleData = [.. sphereTriangles]; - - vertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vertex) * vertexData.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.ShaderResource - }); - vertexBuffer.Upload(vertexData, 0); - - indexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Triangle) * triangleData.Length), - StrideInBytes = (uint)sizeof(Triangle), - Flags = BufferUsageFlags.ShaderResource - }); - indexBuffer.Upload(triangleData, 0); - - constantsBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - 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] - }); - - using Shader ampShader = App.Context.LoadShaderFromSource(ShaderSource, "ASMain", ShaderStageFlags.Amplification); - using Shader meshShader = App.Context.LoadShaderFromSource(ShaderSource, "MSMain", ShaderStageFlags.Mesh); - using Shader pixelShader = App.Context.LoadShaderFromSource(ShaderSource, "PSMain", ShaderStageFlags.Pixel); - - pipeline = App.Context.CreateMeshShadingPipeline(new() - { - RenderStates = new() - { - RasterizerState = RasterizerStates.CullBack, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Amplification = ampShader, - Mesh = meshShader, - Pixel = pixelShader, - ResourceLayout = resourceLayout, - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output, - AmplificationThreadGroupSizeX = ASGroupSize, - AmplificationThreadGroupSizeY = 1, - AmplificationThreadGroupSizeZ = 1, - MeshThreadGroupSizeX = MeshGroupSize, - MeshThreadGroupSizeY = 1, - MeshThreadGroupSizeZ = 1 - }); - } - - public void Update(double deltaTime) - { - totalTime += (float)deltaTime; - - float angle = totalTime * 0.3f; - - Vector3 cameraPos = new(35.0f * MathF.Sin(angle), 20.0f * MathF.Sin(totalTime * 0.2f), 35.0f * MathF.Cos(angle)); - - Matrix4x4 view = Matrix4x4.CreateLookAt(cameraPos, Vector3.Zero, Vector3.UnitY); - Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)App.Width / App.Height, 0.1f, 200.0f); - Matrix4x4 viewProjection = view * projection; - - constantsBuffer.Upload([new Constants() - { - ViewProjection = viewProjection, - FrustumPlane0 = NormalizePlane(new(viewProjection.M11 + viewProjection.M14, viewProjection.M21 + viewProjection.M24, viewProjection.M31 + viewProjection.M34, viewProjection.M41 + viewProjection.M44)), - FrustumPlane1 = NormalizePlane(new(viewProjection.M14 - viewProjection.M11, viewProjection.M24 - viewProjection.M21, viewProjection.M34 - viewProjection.M31, viewProjection.M44 - viewProjection.M41)), - FrustumPlane2 = NormalizePlane(new(viewProjection.M12 + viewProjection.M14, viewProjection.M22 + viewProjection.M24, viewProjection.M32 + viewProjection.M34, viewProjection.M42 + viewProjection.M44)), - FrustumPlane3 = NormalizePlane(new(viewProjection.M14 - viewProjection.M12, viewProjection.M24 - viewProjection.M22, viewProjection.M34 - viewProjection.M32, viewProjection.M44 - viewProjection.M42)), - FrustumPlane4 = NormalizePlane(new(viewProjection.M13, viewProjection.M23, viewProjection.M33, viewProjection.M43)), - FrustumPlane5 = NormalizePlane(new(viewProjection.M14 - viewProjection.M13, viewProjection.M24 - viewProjection.M23, viewProjection.M34 - viewProjection.M33, viewProjection.M44 - viewProjection.M43)), - Time = totalTime, - LightDirection = -Vector3.Normalize(cameraPos) - }], 0); - } - - public void Render() - { - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - commandBuffer.BeginRenderPass(App.FrameBuffer, new() - { - ColorValues = [new(0.05f, 0.05f, 0.08f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }, resourceTable); - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - commandBuffer.DispatchMesh(DispatchGroupCount, 1, 1); - - commandBuffer.EndRenderPass(); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - } - - public void Dispose() - { - pipeline.Dispose(); - resourceTable.Dispose(); - resourceLayout.Dispose(); - constantsBuffer.Dispose(); - indexBuffer.Dispose(); - vertexBuffer.Dispose(); - } - - private static Vector4 NormalizePlane(Vector4 plane) - { - return plane / new Vector3(plane.X, plane.Y, plane.Z).Length(); - } -} - -[StructLayout(LayoutKind.Explicit, Size = 32)] -file struct Vertex -{ - [FieldOffset(0)] - public Vector3 Position; - - [FieldOffset(16)] - public Vector3 Normal; -} - -[StructLayout(LayoutKind.Explicit, Size = 16)] -file struct Triangle -{ - [FieldOffset(0)] - public uint Index0; - - [FieldOffset(4)] - public uint Index1; - - [FieldOffset(8)] - public uint Index2; -} - -[StructLayout(LayoutKind.Explicit, Size = 176)] -file struct Constants -{ - [FieldOffset(0)] - public Matrix4x4 ViewProjection; - - [FieldOffset(64)] - public Vector4 FrustumPlane0; - - [FieldOffset(80)] - public Vector4 FrustumPlane1; - - [FieldOffset(96)] - public Vector4 FrustumPlane2; - - [FieldOffset(112)] - public Vector4 FrustumPlane3; - - [FieldOffset(128)] - public Vector4 FrustumPlane4; - - [FieldOffset(144)] - public Vector4 FrustumPlane5; - - [FieldOffset(160)] - public float Time; - - [FieldOffset(164)] - public Vector3 LightDirection; -} -``` - -## Running the Tutorial - -Run the application and select **7. Mesh Shading** from the menu: - -```bash -dotnet run -``` - -## Result - -![Mesh Shading](../../images/mesh-shading.png) - -## Code Breakdown - -### Procedural Sphere Geometry - -The sphere is generated as a UV sphere with 12 longitude and 6 latitude segments, producing 62 vertices and 120 triangles: - -```csharp -sphereVertices.Add(new() { Position = new(0, radius, 0), Normal = Vector3.UnitY }); - -for (int lat = 1; lat < latSegments; lat++) -{ - float phi = MathF.PI * lat / latSegments; - float sinPhi = MathF.Sin(phi); - float cosPhi = MathF.Cos(phi); - - for (int lon = 0; lon < lonSegments; lon++) - { - float theta = 2.0f * MathF.PI * lon / lonSegments; - Vector3 normal = new(sinPhi * MathF.Cos(theta), cosPhi, sinPhi * MathF.Sin(theta)); - - sphereVertices.Add(new() { Position = normal * radius, Normal = normal }); - } -} - -sphereVertices.Add(new() { Position = new(0, -radius, 0), Normal = -Vector3.UnitY }); -``` - -The vertex and index data are stored in `StructuredBuffer` resources (not vertex/index buffers), since mesh shaders read geometry data directly. - -### Mesh Shading Pipeline - -The pipeline configuration specifies thread group sizes for both amplification and mesh stages: - -```csharp -pipeline = App.Context.CreateMeshShadingPipeline(new() -{ - RenderStates = new() - { - RasterizerState = RasterizerStates.CullBack, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Amplification = ampShader, - Mesh = meshShader, - Pixel = pixelShader, - ResourceLayout = resourceLayout, - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output, - AmplificationThreadGroupSizeX = ASGroupSize, - AmplificationThreadGroupSizeY = 1, - AmplificationThreadGroupSizeZ = 1, - MeshThreadGroupSizeX = MeshGroupSize, - MeshThreadGroupSizeY = 1, - MeshThreadGroupSizeZ = 1 -}); -``` - -### Amplification Shader (Culling) - -The amplification shader tests each instance against the camera frustum and only dispatches mesh groups for visible instances: - -```csharp -[shader("amplification")] -[numthreads(ASGroupSize, 1, 1)] -void ASMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID) -{ - uint instanceIndex = groupID * ASGroupSize + groupThreadID; - - bool visible = false; - if (instanceIndex < TotalInstances) - { - float3 worldPos = InstancePosition(instanceIndex); - visible = !IsFrustumCulled(worldPos, BoundingSphereRadius); - } - - if (groupThreadID == 0) - { - s_visibleCount = 0; - } - - GroupMemoryBarrierWithGroupSync(); - - if (visible) - { - uint offset; - InterlockedAdd(s_visibleCount, 1, offset); - s_payload.InstanceIndices[offset] = instanceIndex; - } - - GroupMemoryBarrierWithGroupSync(); - - DispatchMesh(s_visibleCount, 1, 1, s_payload); -} -``` - -**Key steps:** -1. Each thread checks one instance against 6 frustum planes -2. Visible instances are compacted into a `groupshared` payload using `InterlockedAdd` -3. `DispatchMesh` spawns only as many mesh groups as there are visible instances - -### Frustum Plane Extraction - -Frustum planes are extracted from the view-projection matrix on the CPU: - -```csharp -FrustumPlane0 = NormalizePlane(new(viewProjection.M11 + viewProjection.M14, viewProjection.M21 + viewProjection.M24, viewProjection.M31 + viewProjection.M34, viewProjection.M41 + viewProjection.M44)), -``` - -| Plane | Extraction | -|-------|-----------| -| Left | Row 4 + Row 1 | -| Right | Row 4 - Row 1 | -| Bottom | Row 4 + Row 2 | -| Top | Row 4 - Row 2 | -| Near | Row 3 | -| Far | Row 4 - Row 3 | - -### Constants Layout - -The `Constants` struct packs all per-frame data into 176 bytes: - -```csharp -[StructLayout(LayoutKind.Explicit, Size = 176)] -file struct Constants -{ - [FieldOffset(0)] - public Matrix4x4 ViewProjection; - - [FieldOffset(64)] - public Vector4 FrustumPlane0; - - [FieldOffset(80)] - public Vector4 FrustumPlane1; - - [FieldOffset(96)] - public Vector4 FrustumPlane2; - - [FieldOffset(112)] - public Vector4 FrustumPlane3; - - [FieldOffset(128)] - public Vector4 FrustumPlane4; - - [FieldOffset(144)] - public Vector4 FrustumPlane5; - - [FieldOffset(160)] - public float Time; - - [FieldOffset(164)] - public Vector3 LightDirection; -} -``` - -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. - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [MeshShadingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/MeshShadingRenderer.cs) diff --git a/documents/tutorials/advanced/ray-tracing.md b/documents/tutorials/advanced/ray-tracing.md deleted file mode 100644 index 4b24c3a9..00000000 --- a/documents/tutorials/advanced/ray-tracing.md +++ /dev/null @@ -1,949 +0,0 @@ -# Ray Tracing - -In this tutorial, you'll build a real-time ray tracer using hardware-accelerated ray tracing. The scene features three colored spheres on a checkerboard floor with an animated orbiting camera, soft shadows, reflections, and ACES tone mapping — all driven by a compute shader using `RayQuery`. - -> [!NOTE] -> This tutorial requires a GPU with ray tracing support (e.g., NVIDIA RTX, AMD RDNA 2+, or Apple M1+). - -## Overview - -This tutorial covers: - -- Building **Bottom-Level** and **Top-Level Acceleration Structures** (BLAS/TLAS) -- Using **triangle geometry** for the floor and **procedural AABBs** for spheres -- Tracing rays with `RayQuery` in a compute shader -- Implementing **soft shadows**, **reflections**, and **Fresnel** effects -- Applying **ACES tone mapping** for cinematic color grading -- Dynamically **resizing** the output texture on window resize - -## Key Concepts - -### Acceleration Structure Hierarchy - -Ray tracing uses a two-level acceleration structure: - -| Level | Purpose | Content | -|-------|---------|---------| -| **BLAS** (Bottom-Level) | Geometry containers | Triangle meshes or procedural AABBs | -| **TLAS** (Top-Level) | Scene graph | References to BLAS instances with transforms | - -This tutorial builds two BLAS: -- **Floor BLAS**: A triangle mesh (2 triangles forming a 100×100 quad) -- **Sphere BLAS**: 3 procedural AABBs (bounding boxes for sphere intersection) - -Both are combined into one TLAS for the scene. - -### RayQuery - -Instead of using a dedicated ray tracing pipeline, Zenith.NET uses `RayQuery` in compute shaders. This inline approach traces rays within any shader stage: - -``` -RayQuery query; -query.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, ray); - -while (query.Proceed()) -{ - // Handle procedural intersections -} - -if (query.CommittedStatus() == COMMITTED_TRIANGLE_HIT) -{ - // Handle triangle hit -} -``` - -## The Renderer Class - -Create the file `Renderers/RayTracingRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal unsafe class RayTracingRenderer : IRenderer -{ - private const uint ThreadGroupSize = 16; - - private const string ShaderSource = """ - static const float RayEpsilon = 0.001; - static const float TwoPi = 6.2831853; - static const uint SphereCount = 3; - static const uint ShadowSamples = 6; - static const uint ReflectionSamples = 4; - static const float ShadowMin = 0.3; - static const float SunRadius = 0.04; - static const float SphereRoughness = 0.05; - static const float SphereF0 = 0.15; - static const float FloorFadeStart = 8.0; - static const float FloorFadeRange = 20.0; - - static const float3 FloorNormal = float3(0.0, 1.0, 0.0); - static const float3 LightDir = float3(0.6667, 0.6667, -0.3333); - static const float3 LightColor = float3(1.0, 0.98, 0.95); - static const float3 AmbientColor = float3(0.15, 0.15, 0.2); - - struct Constants - { - private float4 PositionAndPadding; - - property float3 Position - { - get { - return PositionAndPadding.xyz; - } - } - }; - - struct Sphere - { - private float4 CenterAndRadius; - - private float4 ColorAndPadding; - - property float3 Center - { - get { - return CenterAndRadius.xyz; - } - } - - property float Radius - { - get { - return CenterAndRadius.w; - } - } - - property float3 Color - { - get { - return ColorAndPadding.xyz; - } - } - }; - - RaytracingAccelerationStructure scene; - ConstantBuffer constants; - StructuredBuffer spheres; - RWTexture2D outputTexture; - - float3 SampleSky(float3 direction) - { - float t = 0.5 * (direction.y + 1.0); - float3 horizon = float3(0.7, 0.85, 1.0); - float3 zenith = float3(0.3, 0.5, 1.0); - float3 sky = lerp(horizon, zenith, saturate(t)); - - float sunDot = dot(direction, LightDir); - sky += LightColor * smoothstep(0.995, 0.999, sunDot) * 3.0; - - return sky; - } - - float3 ACESFilm(float3 x) - { - x *= 1.6; - float3 a = x * (x * 2.51 + 0.03); - float3 b = x * (x * 2.43 + 0.59) + 0.14; - float3 result = saturate(a / b); - - float luma = dot(result, float3(0.2126, 0.7152, 0.0722)); - result = saturate(lerp(float3(luma, luma, luma), result, 1.5)); - - return result; - } - - float SchlickFresnel(float cosTheta, float f0) - { - return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0); - } - - float3 ShadeCheckerboard(float3 hitPoint, float3 normal, float3 rayDirection, bool softShadow, out float shadow) - { - float2 fw = max(abs(fwidth_approx(hitPoint.xz)), 0.001); - float2 fractPos = fract(hitPoint.xz) - 0.5; - float2 filtered = clamp(fractPos / fw, -0.5, 0.5); - float checker = 0.5 - 0.5 * filtered.x * filtered.y; - float3 baseColor = lerp(float3(0.787, 0.787, 0.787), float3(0.1, 0.1, 0.1), checker); - - float NdotL = max(dot(normal, LightDir), 0.0); - float3 shadowOrigin = hitPoint + normal * RayEpsilon; - shadow = softShadow ? lerp(ShadowMin, 1.0, TraceSoftShadow(shadowOrigin, LightDir, hitPoint.xz * 100.0)) : - (TraceShadowRay(shadowOrigin, LightDir) ? ShadowMin : 1.0); - - float3 litColor = baseColor * AmbientColor + baseColor * LightColor * NdotL * shadow; - - float ao = 1.0; - for (uint i = 0; i < SphereCount; i++) - { - float3 toSphere = spheres[i].Center - hitPoint; - float horizDist = length(toSphere.xz); - float r = spheres[i].Radius; - float occl = saturate(1.0 - horizDist / (r * 2.0)); - float hFactor = saturate(1.0 - toSphere.y / (r * 3.0)); - ao -= occl * hFactor * 0.4; - } - - litColor *= max(ao, 0.3); - - float dist = length(hitPoint.xz); - float fade = saturate((dist - FloorFadeStart) / FloorFadeRange); - return lerp(litColor, SampleSky(rayDirection), fade); - } - - float3 ShadeSphere(float3 hitPoint, float3 normal, float3 sphereColor, float3 viewDir, bool softShadow) - { - float NdotL = max(dot(normal, LightDir), 0.0); - - float3 halfDir = normalize(LightDir + viewDir); - float spec = pow(max(dot(normal, halfDir), 0.0), 64.0); - - float3 shadowOrigin = hitPoint + normal * RayEpsilon; - float shadow = softShadow ? lerp(ShadowMin, 1.0, TraceSoftShadow(shadowOrigin, LightDir, hitPoint.xz * 100.0)) : - (TraceShadowRay(shadowOrigin, LightDir) ? ShadowMin : 1.0); - - float3 diffuse = sphereColor * LightColor * NdotL * shadow; - float3 specular = LightColor * spec * shadow; - float3 ambient = sphereColor * AmbientColor; - - return ambient + diffuse + specular; - } - - float3 TraceReflection(float3 origin, float3 direction) - { - RayDesc reflectRay; - reflectRay.Origin = origin; - reflectRay.Direction = direction; - reflectRay.TMin = RayEpsilon; - reflectRay.TMax = 1000.0; - - float3 sphereNormal = float3(0.0); - float3 sphereColor = float3(0.0); - - RayQuery query; - query.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, reflectRay); - - while (query.Proceed()) - { - if (query.CandidateType() == CANDIDATE_PROCEDURAL_PRIMITIVE) - { - uint sphereIndex = query.CandidatePrimitiveIndex(); - Sphere sphere = spheres[sphereIndex]; - - float3 ro = query.CandidateObjectRayOrigin(); - float3 rd = query.CandidateObjectRayDirection(); - - float t = IntersectSphere(ro, rd, sphere); - - if (t >= query.RayTMin() && t <= query.CommittedRayT()) - { - float3 hitPoint = ro + rd * t; - sphereNormal = normalize(hitPoint - sphere.Center); - sphereColor = sphere.Color; - query.CommitProceduralPrimitiveHit(t); - } - } - } - - if (query.CommittedStatus() == COMMITTED_TRIANGLE_HIT) - { - float3 hitPoint = reflectRay.Origin + reflectRay.Direction * query.CommittedRayT(); - float unused; - return ShadeCheckerboard(hitPoint, FloorNormal, reflectRay.Direction, false, unused); - } - else if (query.CommittedStatus() == COMMITTED_PROCEDURAL_PRIMITIVE_HIT) - { - float3 hitPoint = reflectRay.Origin + reflectRay.Direction * query.CommittedRayT(); - float3 viewDir = normalize(origin - hitPoint); - return ShadeSphere(hitPoint, sphereNormal, sphereColor, viewDir, false); - } - else - { - return SampleSky(direction); - } - } - - float IntersectSphere(float3 origin, float3 direction, Sphere sphere) - { - float3 oc = origin - sphere.Center; - - float b = dot(oc, direction); - float c = dot(oc, oc) - sphere.Radius * sphere.Radius; - float discriminant = b * b - c; - - if (discriminant > 0.0) - { - float sqrtD = sqrt(discriminant); - float t1 = -b - sqrtD; - - if (t1 > 0.0) - { - return t1; - } - - float t2 = -b + sqrtD; - - if (t2 > 0.0) - { - return t2; - } - } - - return -1.0; - } - - float2 fwidth_approx(float2 p) - { - float2 dx = float2(0.02, 0.0); - float2 dy = float2(0.0, 0.02); - return abs(fract(p + dx) - fract(p)) + abs(fract(p + dy) - fract(p)); - } - - float Hash(float2 p) - { - float3 p3 = fract(float3(p.xyx) * 0.1031); - p3 += dot(p3, p3.yzx + 33.33); - return fract((p3.x + p3.y) * p3.z); - } - - bool TraceShadowRay(float3 origin, float3 direction) - { - RayDesc shadowRay; - shadowRay.Origin = origin; - shadowRay.Direction = direction; - shadowRay.TMin = RayEpsilon; - shadowRay.TMax = 1000.0; - - RayQuery shadowQuery; - shadowQuery.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, shadowRay); - - while (shadowQuery.Proceed()) - { - if (shadowQuery.CandidateType() == CANDIDATE_PROCEDURAL_PRIMITIVE) - { - uint sphereIndex = shadowQuery.CandidatePrimitiveIndex(); - Sphere sphere = spheres[sphereIndex]; - - float3 ro = shadowQuery.CandidateObjectRayOrigin(); - float3 rd = shadowQuery.CandidateObjectRayDirection(); - - float t = IntersectSphere(ro, rd, sphere); - - if (t >= shadowQuery.RayTMin() && t <= shadowQuery.CommittedRayT()) - { - shadowQuery.CommitProceduralPrimitiveHit(t); - } - } - } - - return shadowQuery.CommittedStatus() != COMMITTED_NOTHING; - } - - float TraceSoftShadow(float3 origin, float3 direction, float2 pixelSeed) - { - float3 tangent = normalize(cross(direction, float3(0.0, 1.0, 0.0))); - float3 bitangent = cross(direction, tangent); - - float lit = 0.0; - for (uint i = 0; i < ShadowSamples; i++) - { - float h = Hash(pixelSeed + float2(float(i) * 7.13, float(i) * 3.71)); - float angle = (float(i) + h) * (TwoPi / float(ShadowSamples)); - float radius = sqrt(Hash(pixelSeed + float2(float(i) * 11.07, 0.0))) * SunRadius; - float3 jitteredDir = normalize(direction + tangent * cos(angle) * radius + bitangent * sin(angle) * radius); - - if (!TraceShadowRay(origin, jitteredDir)) - { - lit += 1.0; - } - } - - return lit / float(ShadowSamples); - } - - float3 TraceRoughReflection(float3 origin, float3 reflectDir, float3 normal, float roughness, float2 pixelSeed) - { - float3 tangent = normalize(cross(reflectDir, normal)); - float3 bitangent = cross(reflectDir, tangent); - - float3 accum = float3(0.0); - for (uint i = 0; i < ReflectionSamples; i++) - { - float h1 = Hash(pixelSeed + float2(float(i) * 5.17, float(i) * 9.23)); - float h2 = Hash(pixelSeed + float2(float(i) * 13.37, float(i) * 2.91)); - float angle = h1 * TwoPi; - float radius = sqrt(h2) * roughness; - float3 jitteredDir = normalize(reflectDir + tangent * cos(angle) * radius + bitangent * sin(angle) * radius); - accum += TraceReflection(origin, jitteredDir); - } - - return accum / float(ReflectionSamples); - } - - float3 ShadeFloor(float3 hitPoint, float3 rayDir, float3 cameraPos) - { - float shadow; - float3 directColor = ShadeCheckerboard(hitPoint, FloorNormal, rayDir, true, shadow); - - float3 viewDir = normalize(cameraPos - hitPoint); - float3 halfDir = normalize(LightDir + viewDir); - float floorSpec = pow(max(dot(FloorNormal, halfDir), 0.0), 128.0); - float specDist = length(hitPoint.xz); - float specFade = 1.0 - saturate((specDist - FloorFadeStart) / FloorFadeRange); - directColor += LightColor * floorSpec * 0.4 * specFade * shadow; - - float3 reflectDir = reflect(rayDir, FloorNormal); - float3 reflectColor = TraceReflection(hitPoint + FloorNormal * RayEpsilon, reflectDir); - float fresnel = SchlickFresnel(max(dot(FloorNormal, viewDir), 0.0), 0.02); - - return lerp(directColor, reflectColor, fresnel); - } - - float3 ShadePrimarySphere(float3 hitPoint, float3 rayDir, float3 cameraPos, float3 normal, float3 sphereColor) - { - float3 viewDir = normalize(cameraPos - hitPoint); - - float3 directColor = ShadeSphere(hitPoint, normal, sphereColor, viewDir, true); - - float3 reflectDir = reflect(rayDir, normal); - float3 reflectColor = TraceRoughReflection(hitPoint + normal * RayEpsilon, reflectDir, normal, SphereRoughness, hitPoint.xz * 100.0); - float fresnel = SchlickFresnel(max(dot(normal, viewDir), 0.0), SphereF0); - - return lerp(directColor, reflectColor, fresnel); - } - - [numthreads(16, 16, 1)] - void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) - { - uint2 pixelCoord = dispatchThreadID.xy; - - uint width, height; - outputTexture.GetDimensions(width, height); - - if (pixelCoord.x >= width || pixelCoord.y >= height) - { - return; - } - - float2 uv = (float2(pixelCoord) + 0.5) / float2(width, height); - float2 ndc = uv * 2.0 - 1.0; - ndc.y = -ndc.y; - - float aspectRatio = float(width) / float(height); - float fov = tan(radians(45.0) * 0.5); - - float3 cameraPos = constants.Position; - float3 cameraTarget = float3(0.0, 0.5, 0.0); - float3 cameraUp = float3(0.0, 1.0, 0.0); - - float3 forward = normalize(cameraTarget - cameraPos); - float3 right = normalize(cross(forward, cameraUp)); - float3 up = cross(right, forward); - - float3 rayDir = normalize(forward + ndc.x * aspectRatio * fov * right + ndc.y * fov * up); - - RayDesc ray; - ray.Origin = cameraPos; - ray.Direction = rayDir; - ray.TMin = RayEpsilon; - ray.TMax = 1000.0; - - float3 sphereHitNormal = float3(0.0); - float3 sphereHitColor = float3(0.0); - - RayQuery query; - query.TraceRayInline(scene, RAY_FLAG_NONE, 0xFF, ray); - - while (query.Proceed()) - { - if (query.CandidateType() == CANDIDATE_PROCEDURAL_PRIMITIVE) - { - uint sphereIndex = query.CandidatePrimitiveIndex(); - Sphere sphere = spheres[sphereIndex]; - - float3 ro = query.CandidateObjectRayOrigin(); - float3 rd = query.CandidateObjectRayDirection(); - - float t = IntersectSphere(ro, rd, sphere); - - if (t >= query.RayTMin() && t <= query.CommittedRayT()) - { - float3 hitPoint = ro + rd * t; - - sphereHitNormal = normalize(hitPoint - sphere.Center); - sphereHitColor = sphere.Color; - - query.CommitProceduralPrimitiveHit(t); - } - } - } - - float3 color; - - if (query.CommittedStatus() == COMMITTED_TRIANGLE_HIT) - { - float3 hitPoint = ray.Origin + ray.Direction * query.CommittedRayT(); - color = ShadeFloor(hitPoint, rayDir, cameraPos); - } - else if (query.CommittedStatus() == COMMITTED_PROCEDURAL_PRIMITIVE_HIT) - { - float3 hitPoint = ray.Origin + ray.Direction * query.CommittedRayT(); - color = ShadePrimarySphere(hitPoint, rayDir, cameraPos, sphereHitNormal, sphereHitColor); - } - else - { - color = SampleSky(rayDir); - } - - color = ACESFilm(color); - - outputTexture[pixelCoord] = float4(color, 1.0); - } - """; - - private readonly Buffer floorVertexBuffer; - private readonly Buffer floorIndexBuffer; - private readonly Buffer aabbBuffer; - private readonly BottomLevelAccelerationStructure floorBlas; - private readonly BottomLevelAccelerationStructure sphereBlas; - private readonly TopLevelAccelerationStructure tlas; - private readonly Buffer constantsBuffer; - private readonly Buffer sphereBuffer; - private readonly ResourceLayout resourceLayout; - private readonly ComputePipeline pipeline; - - private Texture? outputTexture; - private ResourceTable? resourceTable; - private float totalTime; - - public RayTracingRenderer() - { - if (!App.Context.Capabilities.RayTracingSupported) - { - throw new NotSupportedException("Ray tracing is not supported on this device."); - } - - Vector3[] floorVertices = - [ - new(-50.0f, 0.0f, -50.0f), - new( 50.0f, 0.0f, -50.0f), - new( 50.0f, 0.0f, 50.0f), - new(-50.0f, 0.0f, 50.0f) - ]; - uint[] floorIndices = [0, 1, 2, 0, 2, 3]; - - floorVertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vector3) * floorVertices.Length), - StrideInBytes = (uint)sizeof(Vector3), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.AccelerationStructure - }); - floorVertexBuffer.Upload(floorVertices, 0); - - floorIndexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(uint) * floorIndices.Length), - StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.Index | BufferUsageFlags.AccelerationStructure - }); - floorIndexBuffer.Upload(floorIndices, 0); - - Sphere[] spheres = - [ - new() { Center = new(-2.0f, 1.0f, 1.0f), Radius = 1.0f, Color = new(0.8f, 0.2f, 0.2f) }, - new() { Center = new( 2.0f, 1.2f, -1.0f), Radius = 1.2f, Color = new(0.2f, 0.4f, 0.8f) }, - new() { Center = new( 0.0f, 0.6f, -3.0f), Radius = 0.6f, Color = new(0.9f, 0.7f, 0.2f) } - ]; - - Vector3[] aabbs = new Vector3[spheres.Length * 2]; - for (int i = 0; i < spheres.Length; i++) - { - aabbs[i * 2] = spheres[i].Center - new Vector3(spheres[i].Radius); - aabbs[(i * 2) + 1] = spheres[i].Center + new Vector3(spheres[i].Radius); - } - - aabbBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vector3) * aabbs.Length), - StrideInBytes = (uint)(sizeof(Vector3) * 2), - Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure - }); - aabbBuffer.Upload(aabbs, 0); - - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - floorBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc - { - Geometries = - [ - new() - { - Type = RayTracingGeometryType.Triangles, - Triangles = new() - { - VertexBuffer = floorVertexBuffer, - VertexFormat = PixelFormat.R32G32B32Float, - VertexCount = (uint)floorVertices.Length, - VertexStrideInBytes = (uint)sizeof(Vector3), - IndexBuffer = floorIndexBuffer, - IndexFormat = IndexFormat.UInt32, - IndexCount = (uint)floorIndices.Length, - Transform = Matrix4x4.Identity - }, - Flags = RayTracingGeometryFlags.Opaque - } - ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace - }); - - sphereBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc - { - Geometries = - [ - new() - { - Type = RayTracingGeometryType.AABBs, - AABBs = new() - { - Buffer = aabbBuffer, - Count = (uint)spheres.Length, - StrideInBytes = (uint)(sizeof(Vector3) * 2) - }, - Flags = RayTracingGeometryFlags.Opaque - } - ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace - }); - - tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc - { - Instances = - [ - new() - { - AccelerationStructure = floorBlas, - ID = 0, - Mask = 0xFF, - Transform = Matrix4x4.Identity, - Flags = RayTracingInstanceFlags.None - }, - new() - { - AccelerationStructure = sphereBlas, - ID = 1, - Mask = 0xFF, - Transform = Matrix4x4.Identity, - Flags = RayTracingInstanceFlags.None - } - ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace - }); - - commandBuffer.Submit(waitForCompletion: true); - - constantsBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite - }); - - sphereBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Sphere) * spheres.Length), - StrideInBytes = (uint)sizeof(Sphere), - Flags = BufferUsageFlags.ShaderResource - }); - 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, - ThreadGroupSizeX = ThreadGroupSize, - ThreadGroupSizeY = ThreadGroupSize, - ThreadGroupSizeZ = 1 - }); - } - - public void Update(double deltaTime) - { - totalTime += (float)deltaTime; - - float angle = totalTime * 0.3f; - - constantsBuffer.Upload([new Constants() - { - Position = new(12.0f * MathF.Sin(angle), 4.0f + MathF.Sin(totalTime * 0.2f), -12.0f * MathF.Cos(angle)) - }], 0); - } - - public void Render() - { - outputTexture ??= App.Context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = PixelFormat.B8G8R8A8UNorm, - 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() - { - Layout = resourceLayout, - Resources = [tlas, constantsBuffer, sphereBuffer, outputTexture] - }); - - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - - uint dispatchX = (App.Width + ThreadGroupSize - 1) / ThreadGroupSize; - uint dispatchY = (App.Height + ThreadGroupSize - 1) / ThreadGroupSize; - - commandBuffer.Dispatch(dispatchX, dispatchY, 1); - - commandBuffer.CopyTexture(outputTexture, - default, - default, - App.FrameBuffer.Desc.ColorAttachments[0].Target, - default, - default, - new() { Width = App.Width, Height = App.Height, Depth = 1 }); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - resourceTable?.Dispose(); - resourceTable = null; - - outputTexture?.Dispose(); - outputTexture = null; - } - - public void Dispose() - { - resourceTable?.Dispose(); - outputTexture?.Dispose(); - - pipeline.Dispose(); - resourceLayout.Dispose(); - sphereBuffer.Dispose(); - constantsBuffer.Dispose(); - tlas.Dispose(); - sphereBlas.Dispose(); - floorBlas.Dispose(); - aabbBuffer.Dispose(); - floorIndexBuffer.Dispose(); - floorVertexBuffer.Dispose(); - } -} - -[StructLayout(LayoutKind.Explicit, Size = 16)] -file struct Constants -{ - [FieldOffset(0)] - public Vector3 Position; -} - -[StructLayout(LayoutKind.Explicit, Size = 32)] -file struct Sphere -{ - [FieldOffset(0)] - public Vector3 Center; - - [FieldOffset(12)] - public float Radius; - - [FieldOffset(16)] - public Vector3 Color; -} -``` - -## Running the Tutorial - -Run the application and select **6. Ray Tracing** from the menu: - -```bash -dotnet run -``` - -## Result - -![Ray Tracing](../../images/ray-tracing.png) - -## Code Breakdown - -### Acceleration Structures - -The floor uses triangle geometry, while spheres use procedural AABBs: - -```csharp -floorBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc -{ - Geometries = - [ - new() - { - Type = RayTracingGeometryType.Triangles, - Triangles = new() - { - VertexBuffer = floorVertexBuffer, - VertexFormat = PixelFormat.R32G32B32Float, - VertexCount = (uint)floorVertices.Length, - VertexStrideInBytes = (uint)sizeof(Vector3), - IndexBuffer = floorIndexBuffer, - IndexFormat = IndexFormat.UInt32, - IndexCount = (uint)floorIndices.Length, - Transform = Matrix4x4.Identity - }, - Flags = RayTracingGeometryFlags.Opaque - } - ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace -}); -``` - -For procedural geometry, AABBs (axis-aligned bounding boxes) are provided as min/max pairs. The actual intersection is computed in the shader: - -```csharp -Vector3[] aabbs = new Vector3[spheres.Length * 2]; - -for (int i = 0; i < spheres.Length; i++) -{ - aabbs[i * 2] = spheres[i].Center - new Vector3(spheres[i].Radius); - aabbs[(i * 2) + 1] = spheres[i].Center + new Vector3(spheres[i].Radius); -} -``` - -### TLAS Assembly - -The top-level structure combines both BLAS instances: - -```csharp -tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc -{ - Instances = - [ - new() - { - AccelerationStructure = floorBlas, - ID = 0, - Mask = 0xFF, - Transform = Matrix4x4.Identity, - Flags = RayTracingInstanceFlags.None - }, - new() - { - AccelerationStructure = sphereBlas, - ID = 1, - Mask = 0xFF, - Transform = Matrix4x4.Identity, - Flags = RayTracingInstanceFlags.None - } - ], - Flags = AccelerationStructureBuildFlags.PreferFastTrace -}); -``` - -### Resource Layout - -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 } - ) -}); -``` - -### Animated Camera - -The camera orbits the scene, creating a cinematic flythrough: - -```csharp -public void Update(double deltaTime) -{ - totalTime += (float)deltaTime; - - float angle = totalTime * 0.3f; - - constantsBuffer.Upload([new Constants() - { - Position = new(12.0f * MathF.Sin(angle), 4.0f + MathF.Sin(totalTime * 0.2f), -12.0f * MathF.Cos(angle)) - }], 0); -} -``` - -The camera position traces a circle of radius 12 with vertical bobbing. - -### Dynamic Resize - -The output texture and resource table are recreated when the window resizes: - -```csharp -public void Resize(uint width, uint height) -{ - resourceTable?.Dispose(); - resourceTable = null; - - outputTexture?.Dispose(); - outputTexture = null; -} -``` - -Using nullable fields with `??=` in `Render()` provides lazy reallocation: - -```csharp -outputTexture ??= App.Context.CreateTexture(new() { ... }); -resourceTable ??= App.Context.CreateResourceTable(new() { ... }); -``` - -### Shader Rendering Techniques - -The shader implements several rendering techniques: - -| Technique | Function | Description | -|-----------|----------|-------------| -| Sky gradient | `SampleSky` | Horizon-to-zenith color blend with sun disk | -| Soft shadows | `TraceSoftShadow` | Jittered shadow rays simulating area light | -| Reflections | `TraceReflection` / `TraceRoughReflection` | Single and multi-sample reflection rays | -| Fresnel | `SchlickFresnel` | Angle-dependent reflectivity | -| Tone mapping | `ACESFilm` | ACES filmic curve with saturation boost | -| Checkerboard | `ShadeCheckerboard` | Anti-aliased procedural floor pattern | -| Sphere AO | Per-sphere loop | Contact-based ambient occlusion on floor | - -## Next Steps - -- [Mesh Shading](mesh-shading.md) - Use the modern mesh shader pipeline with GPU-driven culling - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [RayTracingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/RayTracingRenderer.cs) diff --git a/documents/tutorials/getting-started/application-host.md b/documents/tutorials/getting-started/application-host.md new file mode 100644 index 00000000..a0e3a224 --- /dev/null +++ b/documents/tutorials/getting-started/application-host.md @@ -0,0 +1,291 @@ +# Application Host + +Every workload guide runs inside the same application host. The host owns the window and graphics context, creates the swap chain, records the outer frame transitions, and presents textures produced by compute workloads. + +This page explains that contract before the guides begin creating workload resources. Platform integration is host infrastructure and remains in the complete reference source rather than the walkthrough. + +## Responsibilities + +The shared host is responsible for: + +- selecting a supported graphics API for the current operating system; +- creating a window surface and swap chain; +- providing the active `GraphicsContext` and framebuffer dimensions; +- obtaining one graphics command buffer per frame; +- transitioning the swap-chain drawable into and out of attachment layout; +- forwarding update, render, and resize events to the active workload; +- submitting work, waiting for completion, and presenting the drawable; +- loading shared assets and presenting offscreen textures. + +The workload renderer remains responsible for its own buffers, textures, pipelines, commands, transitions, and disposal. + +## Renderer contract + +Create `IRenderer.cs`: + +```csharp +namespace ZenithTutorials; + +internal interface IRenderer : IDisposable +{ + void Update(double deltaTime); + + void Render(CommandBuffer commandBuffer, Texture drawable); + + void Resize(uint width, uint height); +} +``` + +The host calls `Update` before rendering, passes the current swap-chain texture to `Render`, and reports framebuffer size changes through `Resize`. Extending `IDisposable` makes each guide state its GPU ownership explicitly. + +## Create the graphics context + +`App` exposes one shared graphics context. Its static constructor selects DirectX 12 on Windows, Metal 4 on macOS, or Vulkan 1.4 on Linux. + +The complete `App.cs` contains the required imports and declares `App` as an `unsafe` static class because its upload helpers use pointers. + +```csharp +static App() +{ + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsLinux()) + { + throw new PlatformNotSupportedException("The tutorials support Windows, macOS, and Linux."); + } + + if (OperatingSystem.IsWindows()) + { + Context = GraphicsContext.CreateDirectX12(useValidationLayer: true); + } + else if (OperatingSystem.IsMacOS()) + { + Context = GraphicsContext.CreateMetal(useValidationLayer: true); + } + else + { + Context = GraphicsContext.CreateVulkan(useValidationLayer: true); + } + + Context.ValidationMessage += static (_, args) => Console.WriteLine($"[{args.Severity}] {args.Message}"); +} +``` + +Validation remains enabled throughout the guides. Validation messages are written to the terminal and should remain free of errors when a workload is running correctly. + +The host also publishes the shared color format and current framebuffer dimensions: + +```csharp +public static GraphicsContext Context { get; } + +public static PixelFormat ColorFormat => PixelFormat.B8G8R8A8UNorm; + +public static uint Width => (uint)(window?.FramebufferSize.X ?? 0); + +public static uint Height => (uint)(window?.FramebufferSize.Y ?? 0); +``` + +Framebuffer dimensions, rather than logical window dimensions, are used for textures, viewports, and projection aspect ratios. This matters on high-density displays. + +## Create the window and swap chain + +Silk.NET creates a window without an OpenGL context because Zenith.NET owns the native graphics API: + +```csharp +window = Window.Create(WindowOptions.Default with +{ + API = GraphicsAPI.None, + Title = "Zenith.NET Tutorials" +}); + +window.Initialize(); +window.Center(); +``` + +The host creates a Zenith.NET `Surface` from the window. That platform integration is contained in `App.cs` and `CocoaHelper.cs`; workload renderers depend only on the resulting swap chain. The surface and shared color format define that swap chain: + +```csharp +swapChain = Context.CreateSwapChain(new() +{ + Surface = surface, + Format = ColorFormat +}); +``` + +## Follow one frame + +The outer frame has the same shape for every workload: + +```mermaid +flowchart LR + A[Update workload] --> B[Acquire command buffer] + B --> C[Drawable to ColorAttachment] + C --> D[Record workload] + D --> E[Drawable to Present] + E --> F[Submit and wait] + F --> G[Present swap chain] +``` + +The render callback records those operations in order: + +```csharp +window.Render += _ => +{ + if (Width is 0 || Height is 0) + { + return; + } + + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + + renderer.Render(commandBuffer, swapChain.Drawable); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit().Wait(); + + swapChain.Present(); +}; +``` + +The workload starts with a drawable already in `ColorAttachment` layout and must leave it in that layout. The host performs the final transition to `Present`. + +`Submit().Wait()` deliberately permits only one frame of work at a time. That makes resource lifetime and CPU uploads easier to follow, but it is not a production frame scheduler. Applications that keep multiple frames in flight need per-frame resources and explicit reuse synchronization. + +## Update and resize + +The update callback forwards elapsed seconds: + +```csharp +window.Update += delta => +{ + if (Width is 0 || Height is 0) + { + return; + } + + renderer.Update(delta); +}; +``` + +The resize callback lets the workload release size-dependent resources before resizing the swap chain: + +```csharp +window.Resize += _ => +{ + if (Width is 0 || Height is 0) + { + return; + } + + renderer.Resize(Width, Height); + swapChain.Resize(Width, Height); +}; +``` + +Zero-size checks cover minimized windows. A workload normally sets a depth or output texture to `null` during `Resize`; its next `Render` call creates a replacement at the new framebuffer size. + +## Upload shared data + +The guides use one helper for small static and CPU-updated buffers: + +```csharp +public static Buffer LoadBuffer(T[] data, BufferUsages usages) where T : unmanaged +{ + Buffer buffer = Context.CreateBuffer(new() + { + SizeInBytes = (uint)(sizeof(T) * data.Length), + StrideInBytes = (uint)sizeof(T), + Usages = usages, + Residency = MemoryResidency.CpuWriteOnly + }); + + fixed (T* pointer = data) + { + buffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(T) * data.Length) + }); + } + + return buffer; +} +``` + +`SizeInBytes` controls allocation size, while `StrideInBytes` describes one array element. `BufferUsages` states how later commands or shaders consume the buffer. The helper uses `CpuWriteOnly` residency because these compact guides favor direct uploads over a staging system. + +## Present offscreen textures + +Compute Shader and Ray Tracing do not render directly into the swap-chain drawable. They write an offscreen texture, transition it to `Sampled`, and pass it to `App.PresentTexture`. + +`TexturePresenter` owns a sampler, a two-handle constant buffer, and a fullscreen-triangle graphics pipeline: + +The complete presenter is also declared `unsafe` because it uploads a pointer to its local `Constants` value. + +| Resource | Role | +| --- | --- | +| Sampler | Samples the input texture with linear filtering and clamp addressing | +| Constant buffer | Carries sampled-texture and sampler handles to Slang | +| Graphics pipeline | Draws a fullscreen triangle into the current drawable | + +The presenter calculates a destination rectangle, uploads the two handles, and records a small render pass: + +```csharp +Constants constants = new() +{ + Image = texture.SampledHandle, + Sampler = sampler.Handle +}; + +constantBuffer.Upload(0, new() +{ + Pointer = (nint)(&constants), + SizeInBytes = (uint)sizeof(Constants) +}); +``` + +```csharp +commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], null); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetViewports([new() { X = x, Y = y, Width = width, Height = height, MaxDepth = 1.0f }]); +commandBuffer.SetScissors([new() { X = x, Y = y, Width = width, Height = height }]); +commandBuffer.SetConstantBuffer(constantBuffer, 0); + +commandBuffer.Draw(3, 1, 0, 0); + +commandBuffer.EndRenderPass(); +``` + +Its vertex shader derives a fullscreen triangle from `SV_VertexID`, so no vertex buffer is required. Workload guides treat this presenter as host infrastructure and focus on producing the sampled texture correctly. + +## Resource lifetime + +The `using TRenderer renderer = new();` declaration scopes the active renderer to the `try` body, so it is disposed when that body exits. The `finally` block then releases the host-owned presenter, swap chain, window, and graphics context: + +```csharp +finally +{ + texturePresenter?.Dispose(); + swapChain?.Dispose(); + window?.Dispose(); + + Context.Dispose(); +} +``` + +The sample disposes temporary shader objects after pipeline creation. Buffers and textures remain alive until every submitted GPU command that references them has completed. A top-level acceleration structure is a stronger ownership example: keep every BLAS it instances alive until the TLAS is no longer used. + +## Complete source + +Use the complete host implementation as the shared starting point for the workload guides: + +- [App.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/App.cs) +- [IRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/IRenderer.cs) +- [CocoaHelper.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/CocoaHelper.cs) +- [TexturePresenter.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/TexturePresenter.cs) +- [PresentTexture.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/PresentTexture.slang) +- [Program.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Program.cs) + +The reference `Program.cs` presents a menu for all six completed renderers. Continue with [Hello Triangle](../guides/hello-triangle.md) to create the first workload renderer and understand the first menu entry. diff --git a/documents/tutorials/getting-started/hello-triangle.md b/documents/tutorials/getting-started/hello-triangle.md deleted file mode 100644 index 1bc4f4b3..00000000 --- a/documents/tutorials/getting-started/hello-triangle.md +++ /dev/null @@ -1,322 +0,0 @@ -# Hello Triangle - -In this tutorial, you'll create a renderer that draws a single colored triangle on screen. This is the classic starting point for graphics programming — establishing the graphics pipeline, defining vertex data, and issuing a draw call. - -## Overview - -This tutorial covers: - -- Defining a **Slang shader** with vertex and pixel stages -- Creating a **vertex buffer** with position and color data -- Configuring an **input layout** to describe vertex attributes -- Building a **graphics pipeline** with render states -- Recording and submitting **command buffers** to render a frame - -## The Renderer Class - -Create the file `Renderers/HelloTriangleRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal unsafe class HelloTriangleRenderer : IRenderer -{ - private const string ShaderSource = """ - struct VSInput - { - float3 Position : POSITION0; - - float4 Color : COLOR0; - }; - - struct PSInput - { - float4 Position : SV_POSITION; - - float4 Color : COLOR; - }; - - PSInput VSMain(VSInput input) - { - PSInput output; - output.Position = float4(input.Position, 1.0); - output.Color = input.Color; - - return output; - } - - float4 PSMain(PSInput input) : SV_TARGET - { - return input.Color; - } - """; - - private readonly Buffer vertexBuffer; - private readonly GraphicsPipeline pipeline; - - public HelloTriangleRenderer() - { - Vertex[] vertices = - [ - new(new( 0.0f, 0.5f, 0.0f), new(1.0f, 0.0f, 0.0f, 1.0f)), - new(new( 0.5f, -0.5f, 0.0f), new(0.0f, 1.0f, 0.0f, 1.0f)), - new(new(-0.5f, -0.5f, 0.0f), new(0.0f, 0.0f, 1.0f, 1.0f)), - ]; - - vertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite - }); - vertexBuffer.Upload(vertices, 0); - - InputLayout inputLayout = new(); - inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); - inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color }); - - using Shader vertexShader = App.Context.LoadShaderFromSource(ShaderSource, "VSMain", ShaderStageFlags.Vertex); - using Shader pixelShader = App.Context.LoadShaderFromSource(ShaderSource, "PSMain", ShaderStageFlags.Pixel); - - pipeline = App.Context.CreateGraphicsPipeline(new() - { - RenderStates = new() - { - RasterizerState = RasterizerStates.CullNone, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceLayout = null, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output - }); - } - - public void Update(double deltaTime) - { - } - - public void Render() - { - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - commandBuffer.BeginRenderPass(App.FrameBuffer, new() - { - ColorValues = [new(0.1f, 0.1f, 0.1f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }); - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); - commandBuffer.Draw(3, 1, 0, 0); - - commandBuffer.EndRenderPass(); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - } - - public void Dispose() - { - pipeline.Dispose(); - vertexBuffer.Dispose(); - } -} - -[StructLayout(LayoutKind.Sequential)] -file struct Vertex(Vector3 position, Vector4 color) -{ - public Vector3 Position = position; - - public Vector4 Color = color; -} -``` - -## Running the Tutorial - -Run the application and select **1. Hello Triangle** from the menu: - -```bash -dotnet run -``` - -## Result - -![Hello Triangle](../../images/hello-triangle.png) - -## Code Breakdown - -### Shader - -The shader is written inline as a Slang source string. It defines two stages: - -```csharp -private const string ShaderSource = """ - struct VSInput - { - float3 Position : POSITION0; - - float4 Color : COLOR0; - }; - - struct PSInput - { - float4 Position : SV_POSITION; - - float4 Color : COLOR; - }; - - PSInput VSMain(VSInput input) - { - PSInput output; - output.Position = float4(input.Position, 1.0); - output.Color = input.Color; - - return output; - } - - float4 PSMain(PSInput input) : SV_TARGET - { - return input.Color; - } - """; -``` - -- **VSMain**: Converts the 3D position to clip space and passes the color through -- **PSMain**: Outputs the interpolated vertex color - -### Vertex Data - -Three vertices define the triangle with red, green, and blue colors: - -```csharp -Vertex[] vertices = -[ - new(new( 0.0f, 0.5f, 0.0f), new(1.0f, 0.0f, 0.0f, 1.0f)), - new(new( 0.5f, -0.5f, 0.0f), new(0.0f, 1.0f, 0.0f, 1.0f)), - new(new(-0.5f, -0.5f, 0.0f), new(0.0f, 0.0f, 1.0f, 1.0f)), -]; -``` - -The `Vertex` struct is defined as a `file`-scoped type with sequential layout: - -```csharp -[StructLayout(LayoutKind.Sequential)] -file struct Vertex(Vector3 position, Vector4 color) -{ - public Vector3 Position = position; - - public Vector4 Color = color; -} -``` - -### Vertex Buffer - -The buffer is created with `Vertex | MapWrite` flags. `MapWrite` enables CPU-side uploads: - -```csharp -vertexBuffer = App.Context.CreateBuffer(new() -{ - SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite -}); -vertexBuffer.Upload(vertices, 0); -``` - -### Input Layout - -The input layout tells the pipeline how to interpret vertex data. The order must match the shader's `VSInput`: - -```csharp -InputLayout inputLayout = new(); -inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); -inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color }); -``` - -### Graphics Pipeline - -The pipeline binds everything together — shaders, render states, input layout, and output format: - -```csharp -pipeline = App.Context.CreateGraphicsPipeline(new() -{ - RenderStates = new() - { - RasterizerState = RasterizerStates.CullNone, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceLayout = null, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output -}); -``` - -| Property | Value | Purpose | -|----------|-------|---------| -| `RasterizerState` | `CullNone` | No face culling (both sides visible) | -| `DepthStencilState` | `Default` | Standard depth testing | -| `BlendState` | `Opaque` | No transparency | -| `ResourceLayout` | `null` | No bound resources needed | -| `PrimitiveTopology` | `TriangleList` | Every 3 vertices form a triangle | - -### Rendering - -Each frame, a command buffer records the draw commands: - -```csharp -CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - -commandBuffer.BeginRenderPass(App.FrameBuffer, new() -{ - ColorValues = [new(0.1f, 0.1f, 0.1f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All -}); - -commandBuffer.SetPipeline(pipeline); -commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); -commandBuffer.Draw(3, 1, 0, 0); - -commandBuffer.EndRenderPass(); - -commandBuffer.Submit(waitForCompletion: true); -``` - -`Draw(3, 1, 0, 0)` draws 3 vertices, 1 instance, starting at vertex 0 and instance 0. - -Note that `BeginRenderPass` does not pass a `ResourceTable` because this renderer has no bound resources. - -### Resource Cleanup - -All GPU resources must be disposed in reverse order of creation: - -```csharp -public void Dispose() -{ - pipeline.Dispose(); - vertexBuffer.Dispose(); -} -``` - -## Next Steps - -- [Textured Quad](textured-quad.md) - Add textures, index buffers, and samplers - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [HelloTriangleRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/HelloTriangleRenderer.cs) diff --git a/documents/tutorials/getting-started/prerequisites.md b/documents/tutorials/getting-started/prerequisites.md deleted file mode 100644 index 90ca0bbb..00000000 --- a/documents/tutorials/getting-started/prerequisites.md +++ /dev/null @@ -1,460 +0,0 @@ -# Prerequisites - -Before starting the tutorials, you need to set up the project and create the shared framework code that all tutorials will use. - -## Development Environment - -- .NET 10.0 SDK or later -- A GPU with DirectX 12, Metal 4, or Vulkan 1.4 support -- Visual Studio 2026, VS Code, or JetBrains Rider - -> [!NOTE] -> These tutorials target desktop platforms: Windows, macOS, and Linux. - -## Creating the Project - -```bash -dotnet new console -n ZenithTutorials -cd ZenithTutorials -``` - -### Required Packages - -```bash -dotnet add package Zenith.NET.DirectX12 -dotnet add package Zenith.NET.Metal -dotnet add package Zenith.NET.Vulkan -dotnet add package Zenith.NET.Extensions.ImageSharp -dotnet add package Zenith.NET.Extensions.Slang -dotnet add package Silk.NET.Windowing -dotnet add package Silk.NET.Input -``` - -### Project Configuration - -Your `.csproj` should look like this: - -```xml - - - - Exe - net10.0 - enable - enable - true - - - - - - - - - - - - - - - PreserveNewest - - - - -``` - -> [!NOTE] -> `AllowUnsafeBlocks` is required because the tutorials use `sizeof` with custom structs for GPU buffer sizing. - -## Project Structure - -``` -ZenithTutorials/ -├── Program.cs -├── App.cs -├── IRenderer.cs -├── BindingHelper.cs -├── CocoaHelper.cs -├── Usings.cs -├── Assets/ -│ └── shoko.png -└── Renderers/ - ├── HelloTriangleRenderer.cs - ├── TexturedQuadRenderer.cs - ├── SpinningCubeRenderer.cs - ├── ComputeShaderRenderer.cs - ├── IndirectDrawingRenderer.cs - ├── RayTracingRenderer.cs - └── MeshShadingRenderer.cs -``` - -### Asset File - -Save the following image as `Assets/shoko.png` in your project (right-click → Save As): - -![shoko.png](../../images/shoko.png) - -## Framework Code - -The following files provide the shared infrastructure for all tutorials. Copy each file into your project. - -### Usings.cs - -```csharp -global using System.Numerics; -global using System.Runtime.CompilerServices; -global using System.Runtime.InteropServices; -global using Zenith.NET; -global using Zenith.NET.Extensions.ImageSharp; -global using Zenith.NET.Extensions.Slang; -global using Buffer = Zenith.NET.Buffer; -``` - -### IRenderer.cs - -All tutorial renderers implement this interface: - -```csharp -namespace ZenithTutorials; - -internal interface IRenderer : IDisposable -{ - void Update(double deltaTime); - - void Render(); - - void Resize(uint width, uint height); -} -``` - -| Method | Called | Purpose | -|--------|-------|---------| -| `Update` | Every frame | Update logic (animations, transforms) | -| `Render` | Every frame | Issue GPU commands | -| `Resize` | On window resize | Recreate size-dependent resources | -| `Dispose` | On exit | Clean up GPU resources | - -### App.cs - -The application framework manages window creation, graphics context initialization, and the render loop: - -```csharp -using Silk.NET.Windowing; -using Zenith.NET.DirectX12; -using Zenith.NET.Metal; -using Zenith.NET.Vulkan; - -namespace ZenithTutorials; - -internal static class App -{ - private static readonly IWindow window; - private static readonly SwapChain swapChain; - - static App() - { - if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsLinux()) - { - throw new PlatformNotSupportedException("This application only supports Windows, macOS, and Linux."); - } - - if (OperatingSystem.IsWindows()) - { - Context = GraphicsContext.CreateDirectX12(useValidationLayer: true); - } - else if (OperatingSystem.IsMacOS()) - { - Context = GraphicsContext.CreateMetal(useValidationLayer: true); - } - else - { - Context = GraphicsContext.CreateVulkan(useValidationLayer: true); - } - - Context.ValidationMessage += static (sender, args) => Console.WriteLine($"[{args.Source} - {args.Severity}] {args.Message}"); - - window = Window.Create(WindowOptions.Default with - { - API = GraphicsAPI.None, - Title = "Zenith Tutorials", - Size = new(1280, 720) - }); - window.Initialize(); - window.Center(); - - Surface surface; - if (OperatingSystem.IsWindows()) - { - surface = Surface.Win32(window.Native!.Win32!.Value.Hwnd, Width, Height); - } - else if (OperatingSystem.IsMacOS()) - { - surface = Surface.Apple(CocoaHelper.CreateLayer(window.Native!.Cocoa!.Value), Width, Height); - } - else - { - surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, Width, Height); - } - - swapChain = Context.CreateSwapChain(new() { Surface = surface, ColorTargetFormat = PixelFormat.B8G8R8A8UNorm, DepthStencilTargetFormat = PixelFormat.D32FloatS8UInt }); - } - - public static GraphicsContext Context { get; } - - public static uint Width => (uint)window.FramebufferSize.X; - - public static uint Height => (uint)window.FramebufferSize.Y; - - public static FrameBuffer FrameBuffer => swapChain.FrameBuffer; - - public static void Run() where TRenderer : IRenderer, new() - { - try - { - using TRenderer renderer = new(); - - window.Update += delta => - { - if (Width is 0 || Height is 0) - { - return; - } - - renderer.Update(delta); - }; - - window.Render += delta => - { - if (Width is 0 || Height is 0) - { - return; - } - - renderer.Render(); - swapChain.Present(); - }; - - window.Resize += size => - { - if (Width is 0 || Height is 0) - { - return; - } - - renderer.Resize(Width, Height); - swapChain.Resize(Width, Height); - }; - - window.Run(); - } - finally - { - swapChain.Dispose(); - window.Dispose(); - - Context.Dispose(); - } - } -} -``` - -`App` provides: - -| Member | Description | -|--------|-------------| -| `Context` | The `GraphicsContext` for the current platform | -| `Width` / `Height` | Current framebuffer dimensions | -| `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: - -```csharp -namespace ZenithTutorials; - -internal static partial class CocoaHelper -{ - private const string LibObjC = "/usr/lib/libobjc.A.dylib"; - - [LibraryImport(LibObjC, EntryPoint = "objc_getClass")] - private static partial nint GetClass([MarshalAs(UnmanagedType.LPUTF8Str)] string name); - - [LibraryImport(LibObjC, EntryPoint = "sel_registerName")] - private static partial nint Selector([MarshalAs(UnmanagedType.LPUTF8Str)] string name); - - [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] - private static partial nint Send(nint receiver, nint selector); - - [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] - private static partial nint Send(nint receiver, nint selector, [MarshalAs(UnmanagedType.I1)] bool arg); - - [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] - private static partial nint Send(nint receiver, nint selector, nint arg); - - public static nint CreateLayer(nint cocoa) - { - nint layer = Send(GetClass("CAMetalLayer"), Selector("layer")); - Send(layer, Selector("retain")); - - nint view = Send(cocoa, Selector("contentView")); - Send(view, Selector("setWantsLayer:"), true); - Send(view, Selector("setLayer:"), layer); - - return layer; - } -} -``` - -> [!NOTE] -> On Windows and Linux, this file is not used but must be present to compile. - -### Program.cs - -The entry point provides an interactive tutorial selector: - -```csharp -using ZenithTutorials; -using ZenithTutorials.Renderers; - -(string Name, Action Run)[] tutorials = -[ - ("Hello Triangle", App.Run), - ("Textured Quad", App.Run), - ("Spinning Cube", App.Run), - ("Compute Shader", App.Run), - ("Indirect Drawing", App.Run), - ("Ray Tracing", App.Run), - ("Mesh Shading", App.Run) -]; - -for (int i = 0; i < tutorials.Length; i++) -{ - Console.WriteLine($"{i + 1}. {tutorials[i].Name}"); -} - -Console.Write("Select a tutorial to run: "); - -if (int.TryParse(Console.ReadKey().KeyChar.ToString(), out int choice) && choice >= 1 && choice <= tutorials.Length) -{ - Console.WriteLine($"\nRunning '{tutorials[choice - 1].Name}' tutorial..."); - - tutorials[choice - 1].Run(); -} -``` - -> [!TIP] -> If you are following the tutorials sequentially, comment out renderers you haven't implemented yet to avoid build errors. - -## Next Steps - -With the framework in place, you're ready to start the first tutorial: - -- [Hello Triangle](hello-triangle.md) - Render your first triangle with a graphics pipeline - -## Source Code - -> [!TIP] -> View the complete tutorial project on GitHub: [ZenithTutorials](https://github.com/qian-o/ZenithTutorials) diff --git a/documents/tutorials/getting-started/project-setup.md b/documents/tutorials/getting-started/project-setup.md new file mode 100644 index 00000000..065e9e12 --- /dev/null +++ b/documents/tutorials/getting-started/project-setup.md @@ -0,0 +1,134 @@ +# Project Setup + +This page creates the .NET project used throughout the guides. It installs the current Zenith.NET packages, enables the low-level C# features used for GPU uploads, and configures shader and texture assets for the build output. + +The runnable reference implementation is maintained separately in [ZenithTutorials](https://github.com/qian-o/ZenithTutorials). You do not need to clone that repository to follow the guides. + +## Requirements + +Install the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) and a current graphics driver. Verify the SDK from a terminal: + +```console +dotnet --list-sdks +``` + +Zenith.NET selects a supported graphics API for the current operating system: + +| Operating system | Graphics API | +| --- | --- | +| Windows | DirectX 12 | +| macOS | Metal 4 | +| Linux | Vulkan 1.4 | + +Ray tracing and mesh shading are optional device capabilities. The corresponding guides check support at runtime before creating those resources. + +## Create the project + +Create a .NET 10 console application and enter its directory: + +```console +dotnet new console --framework net10.0 --name ZenithTutorials +cd ZenithTutorials +``` + +Add the windowing package, the Zenith.NET graphics packages, and the ImageSharp extension. No version is specified, so the CLI selects the latest compatible package available from the configured NuGet sources. + +```console +dotnet package add Silk.NET.Windowing +dotnet package add Zenith.NET +dotnet package add Zenith.NET.DirectX12 +dotnet package add Zenith.NET.Metal +dotnet package add Zenith.NET.Vulkan +dotnet package add Zenith.NET.Extensions.ImageSharp +``` + +All three graphics packages can remain in one project. The application host creates only the graphics context selected for the current operating system. + +## Configure the project + +Open `ZenithTutorials.csproj`. Keep the package references generated by the CLI and make sure the main property group contains these settings: + +```xml + + Exe + net10.0 + enable + enable + true + +``` + +Several guides upload unmanaged structures by pointer, so `AllowUnsafeBlocks` is required. Nullable analysis remains enabled for resources that are created lazily or recreated after a resize. + +`AllowUnsafeBlocks` permits unsafe code to compile; the type or method containing each pointer expression must still be declared `unsafe`. The reference host and the renderers that upload structures by pointer include that declaration in their complete source files. + +Shaders and textures are opened at runtime from the application directory. Add this item group after the package references: + +```xml + + + PreserveNewest + + +``` + +`PreserveNewest` copies changed assets without rewriting every file on every build. + +## Create the source layout + +Create the following folders and files as the guides introduce them: + +```text +ZenithTutorials/ +├── Assets/ +│ ├── Shaders/ +│ └── Textures/ +├── Renderers/ +├── App.cs +├── CocoaHelper.cs +├── IRenderer.cs +├── Program.cs +├── TexturePresenter.cs +├── Usings.cs +└── ZenithTutorials.csproj +``` + +The application host is shared by every guide. Each workload adds one renderer under `Renderers` and one Slang source file under `Assets/Shaders`. + +These guides are walkthroughs of the current reference implementation rather than file-by-file listings. Each code fence is a contiguous excerpt from that implementation. Use the **Complete source** links at the end of a page when assembling the runnable file; the surrounding text explains why the excerpt exists and how it participates in the workload. + +## Add shared namespaces + +Create `Usings.cs`: + +```csharp +global using System.Numerics; +global using System.Runtime.CompilerServices; +global using System.Runtime.InteropServices; +global using Zenith.NET; +global using Zenith.NET.Extensions.ImageSharp; +global using Buffer = Zenith.NET.Buffer; +``` + +The alias on the final line resolves the name shared by `System.Buffer` and `Zenith.NET.Buffer`. `System.Numerics` supplies the vectors and matrices used by both the CPU data structures and the camera calculations. + +## Build the empty project + +Build once before adding the host: + +```console +dotnet build +``` + +A successful build confirms that the SDK and packages restore correctly. No graphics device or window is created during `dotnet build`; those operations begin when the application runs. + +Continue with [Application Host](application-host.md) to add the shared window, graphics context, swap chain, and frame loop. + +## Reference project + +The finished project configuration and shared usings are available in the tutorial repository: + +- [ZenithTutorials.csproj](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/ZenithTutorials.csproj) +- [Usings.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Usings.cs) + +The reference repository uses local project references so it can track Zenith.NET development. For a standalone reader project, keep the NuGet package references created above. diff --git a/documents/tutorials/getting-started/spinning-cube.md b/documents/tutorials/getting-started/spinning-cube.md deleted file mode 100644 index b1fae9b3..00000000 --- a/documents/tutorials/getting-started/spinning-cube.md +++ /dev/null @@ -1,350 +0,0 @@ -# Spinning Cube - -In this tutorial, you'll render a spinning 3D cube with per-vertex colors. This introduces constant buffers for uploading transformation matrices, and per-frame updates for animation. - -## Overview - -This tutorial covers: - -- Building **Model/View/Projection** transformation matrices -- Creating and updating a **constant buffer** each frame -- Using **back-face culling** and **depth testing** for 3D rendering -- Animating object rotation over time in the `Update` loop - -## The Renderer Class - -Create the file `Renderers/SpinningCubeRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal unsafe class SpinningCubeRenderer : IRenderer -{ - private const string ShaderSource = """ - struct VSInput - { - float3 Position : POSITION0; - - float4 Color : COLOR0; - }; - - struct PSInput - { - float4 Position : SV_POSITION; - - float4 Color : COLOR; - }; - - struct Constants - { - float4x4 Model; - - float4x4 View; - - float4x4 Projection; - }; - - ConstantBuffer constants; - - PSInput VSMain(VSInput input) - { - float4x4 mvp = mul(mul(constants.Model, constants.View), constants.Projection); - - PSInput output; - output.Position = mul(float4(input.Position, 1.0), mvp); - output.Color = input.Color; - - return output; - } - - float4 PSMain(PSInput input) : SV_TARGET - { - return input.Color; - } - """; - - private readonly Buffer vertexBuffer; - private readonly Buffer indexBuffer; - private readonly Buffer constantsBuffer; - private readonly ResourceLayout resourceLayout; - private readonly ResourceTable resourceTable; - private readonly GraphicsPipeline pipeline; - - private float rotationAngle; - - public SpinningCubeRenderer() - { - Vertex[] vertices = - [ - new(new(-0.5f, -0.5f, 0.5f), new(1.0f, 0.0f, 0.0f, 1.0f)), - new(new( 0.5f, -0.5f, 0.5f), new(0.0f, 1.0f, 0.0f, 1.0f)), - new(new( 0.5f, 0.5f, 0.5f), new(0.0f, 0.0f, 1.0f, 1.0f)), - new(new(-0.5f, 0.5f, 0.5f), new(1.0f, 1.0f, 0.0f, 1.0f)), - new(new(-0.5f, -0.5f, -0.5f), new(1.0f, 0.0f, 1.0f, 1.0f)), - new(new( 0.5f, -0.5f, -0.5f), new(0.0f, 1.0f, 1.0f, 1.0f)), - new(new( 0.5f, 0.5f, -0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new(-0.5f, 0.5f, -0.5f), new(0.5f, 0.5f, 0.5f, 1.0f)) - ]; - - uint[] indices = - [ - 0, 1, 2, 0, 2, 3, - 5, 4, 7, 5, 7, 6, - 4, 0, 3, 4, 3, 7, - 1, 5, 6, 1, 6, 2, - 3, 2, 6, 3, 6, 7, - 4, 5, 1, 4, 1, 0 - ]; - - vertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite - }); - vertexBuffer.Upload(vertices, 0); - - indexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(uint) * indices.Length), - StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.Index | BufferUsageFlags.MapWrite - }); - indexBuffer.Upload(indices, 0); - - constantsBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - 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] - }); - - InputLayout inputLayout = new(); - inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); - inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color }); - - using Shader vertexShader = App.Context.LoadShaderFromSource(ShaderSource, "VSMain", ShaderStageFlags.Vertex); - using Shader pixelShader = App.Context.LoadShaderFromSource(ShaderSource, "PSMain", ShaderStageFlags.Pixel); - - pipeline = App.Context.CreateGraphicsPipeline(new() - { - RenderStates = new() - { - RasterizerState = RasterizerStates.CullBack, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceLayout = resourceLayout, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output - }); - } - - public void Update(double deltaTime) - { - rotationAngle += (float)deltaTime; - - Matrix4x4 model = Matrix4x4.CreateRotationY(rotationAngle) * Matrix4x4.CreateRotationX(rotationAngle * 0.5f); - Matrix4x4 view = Matrix4x4.CreateLookAt(new(0, 0, 3), Vector3.Zero, Vector3.UnitY); - Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)App.Width / App.Height, 0.1f, 100.0f); - - constantsBuffer.Upload([new Constants() { Model = model, View = view, Projection = projection }], 0); - } - - public void Render() - { - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - commandBuffer.BeginRenderPass(App.FrameBuffer, new() - { - ColorValues = [new(0.1f, 0.1f, 0.1f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }, resourceTable); - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); - commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); - commandBuffer.DrawIndexed(36, 1, 0, 0, 0); - - commandBuffer.EndRenderPass(); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - } - - public void Dispose() - { - pipeline.Dispose(); - resourceTable.Dispose(); - resourceLayout.Dispose(); - constantsBuffer.Dispose(); - indexBuffer.Dispose(); - vertexBuffer.Dispose(); - } -} - -[StructLayout(LayoutKind.Sequential)] -file struct Vertex(Vector3 position, Vector4 color) -{ - public Vector3 Position = position; - - public Vector4 Color = color; -} - -[StructLayout(LayoutKind.Explicit, Size = 192)] -file struct Constants -{ - [FieldOffset(0)] - public Matrix4x4 Model; - - [FieldOffset(64)] - public Matrix4x4 View; - - [FieldOffset(128)] - public Matrix4x4 Projection; -} -``` - -## Running the Tutorial - -Run the application and select **3. Spinning Cube** from the menu: - -```bash -dotnet run -``` - -## Result - -![Spinning Cube](../../images/spinning-cube.png) - -## Code Breakdown - -### Shader - -The vertex shader computes the Model-View-Projection transform: - -```csharp -private const string ShaderSource = """ - struct Constants - { - float4x4 Model; - - float4x4 View; - - float4x4 Projection; - }; - - ConstantBuffer constants; - - PSInput VSMain(VSInput input) - { - float4x4 mvp = mul(mul(constants.Model, constants.View), constants.Projection); - - PSInput output; - output.Position = mul(float4(input.Position, 1.0), mvp); - output.Color = input.Color; - - return output; - } - """; -``` - -`ConstantBuffer` gives the shader access to the CPU-uploaded matrices. - -### Constant Buffer - -A constant buffer is created for the MVP matrices, updated every frame: - -```csharp -constantsBuffer = App.Context.CreateBuffer(new() -{ - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite -}); -``` - -The `Constants` struct uses explicit layout to match HLSL/Slang packing rules: - -```csharp -[StructLayout(LayoutKind.Explicit, Size = 192)] -file struct Constants -{ - [FieldOffset(0)] - public Matrix4x4 Model; - - [FieldOffset(64)] - public Matrix4x4 View; - - [FieldOffset(128)] - public Matrix4x4 Projection; -} -``` - -Each `Matrix4x4` is 64 bytes (4x4 floats), giving a total size of 192 bytes. - -### Animation - -The `Update` method accumulates time and builds transformation matrices: - -```csharp -public void Update(double deltaTime) -{ - rotationAngle += (float)deltaTime; - - Matrix4x4 model = Matrix4x4.CreateRotationY(rotationAngle) * Matrix4x4.CreateRotationX(rotationAngle * 0.5f); - Matrix4x4 view = Matrix4x4.CreateLookAt(new(0, 0, 3), Vector3.Zero, Vector3.UnitY); - Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)App.Width / App.Height, 0.1f, 100.0f); - - constantsBuffer.Upload([new Constants() { Model = model, View = view, Projection = projection }], 0); -} -``` - -| Matrix | Purpose | -|--------|---------| -| **Model** | Combined Y and X rotation, creating a tumbling effect | -| **View** | Camera at `(0, 0, 3)` looking at the origin | -| **Projection** | Perspective with 45-degree FOV | - -### Render States - -The pipeline now uses `CullBack` instead of `CullNone`: - -```csharp -RasterizerState = RasterizerStates.CullBack, -DepthStencilState = DepthStencilStates.Default, -``` - -Back-face culling discards triangles facing away from the camera, which is essential for 3D rendering performance. - -## Next Steps - -- [Compute Shader](../intermediate/compute-shader.md) - Process textures on the GPU with compute pipelines - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [SpinningCubeRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/SpinningCubeRenderer.cs) diff --git a/documents/tutorials/getting-started/textured-quad.md b/documents/tutorials/getting-started/textured-quad.md deleted file mode 100644 index eafffde4..00000000 --- a/documents/tutorials/getting-started/textured-quad.md +++ /dev/null @@ -1,343 +0,0 @@ -# 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. - -## Overview - -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 - -## The Renderer Class - -Create the file `Renderers/TexturedQuadRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal unsafe class TexturedQuadRenderer : IRenderer -{ - private const string ShaderSource = """ - struct VSInput - { - float3 Position : POSITION0; - - float2 TexCoord : TEXCOORD0; - }; - - struct PSInput - { - float4 Position : SV_POSITION; - - float2 TexCoord : TEXCOORD; - }; - - Texture2D texture; - SamplerState sampler; - - PSInput VSMain(VSInput input) - { - PSInput output; - output.Position = float4(input.Position, 1.0); - output.TexCoord = input.TexCoord; - - return output; - } - - float4 PSMain(PSInput input) : SV_TARGET - { - return texture.Sample(sampler, input.TexCoord); - } - """; - - 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; - - public TexturedQuadRenderer() - { - Vertex[] vertices = - [ - new(new(-0.5f, 0.5f, 0.0f), new(0.0f, 0.0f)), - new(new( 0.5f, 0.5f, 0.0f), new(1.0f, 0.0f)), - new(new( 0.5f, -0.5f, 0.0f), new(1.0f, 1.0f)), - new(new(-0.5f, -0.5f, 0.0f), new(0.0f, 1.0f)) - ]; - - uint[] indices = [0, 1, 2, 0, 2, 3]; - - vertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite - }); - vertexBuffer.Upload(vertices, 0); - - indexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(uint) * indices.Length), - StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.Index | BufferUsageFlags.MapWrite - }); - indexBuffer.Upload(indices, 0); - - texture = App.Context.LoadTextureFromFile(Path.Combine(AppContext.BaseDirectory, "Assets", "shoko.png"), generateMipMaps: true); - - sampler = App.Context.CreateSampler(new() - { - U = AddressMode.Clamp, - V = AddressMode.Clamp, - W = AddressMode.Clamp, - Filter = Filter.MinLinearMagLinearMipLinear, - 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] - }); - - InputLayout inputLayout = new(); - inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); - inputLayout.Add(new() { Format = ElementFormat.Float2, Semantic = ElementSemantic.TexCoord }); - - using Shader vertexShader = App.Context.LoadShaderFromSource(ShaderSource, "VSMain", ShaderStageFlags.Vertex); - using Shader pixelShader = App.Context.LoadShaderFromSource(ShaderSource, "PSMain", ShaderStageFlags.Pixel); - - pipeline = App.Context.CreateGraphicsPipeline(new() - { - RenderStates = new() - { - RasterizerState = RasterizerStates.CullNone, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceLayout = resourceLayout, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output - }); - } - - public void Update(double deltaTime) - { - } - - public void Render() - { - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - commandBuffer.BeginRenderPass(App.FrameBuffer, new() - { - ColorValues = [new(0.1f, 0.1f, 0.1f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }, resourceTable); - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); - commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); - commandBuffer.DrawIndexed(6, 1, 0, 0, 0); - - commandBuffer.EndRenderPass(); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - } - - public void Dispose() - { - pipeline.Dispose(); - resourceTable.Dispose(); - resourceLayout.Dispose(); - sampler.Dispose(); - texture.Dispose(); - indexBuffer.Dispose(); - vertexBuffer.Dispose(); - } -} - -[StructLayout(LayoutKind.Sequential)] -file struct Vertex(Vector3 position, Vector2 texCoord) -{ - public Vector3 Position = position; - - public Vector2 TexCoord = texCoord; -} -``` - -## Running the Tutorial - -Run the application and select **2. Textured Quad** from the menu: - -```bash -dotnet run -``` - -## Result - -![Textured Quad](../../images/textured-quad.png) - -## Code Breakdown - -### Shader - -The pixel shader samples a texture using UV coordinates: - -```csharp -private const string ShaderSource = """ - struct VSInput - { - float3 Position : POSITION0; - - float2 TexCoord : TEXCOORD0; - }; - - struct PSInput - { - float4 Position : SV_POSITION; - - float2 TexCoord : TEXCOORD; - }; - - Texture2D texture; - SamplerState sampler; - - PSInput VSMain(VSInput input) - { - PSInput output; - output.Position = float4(input.Position, 1.0); - output.TexCoord = input.TexCoord; - - return output; - } - - float4 PSMain(PSInput input) : SV_TARGET - { - return texture.Sample(sampler, input.TexCoord); - } - """; -``` - -`Texture2D` and `SamplerState` are declared as global resources. The pixel shader uses `texture.Sample(sampler, uv)` to fetch filtered texel colors. - -### Index Buffer - -Instead of duplicating vertices, an index buffer references shared vertices: - -```csharp -Vertex[] vertices = -[ - new(new(-0.5f, 0.5f, 0.0f), new(0.0f, 0.0f)), - new(new( 0.5f, 0.5f, 0.0f), new(1.0f, 0.0f)), - new(new( 0.5f, -0.5f, 0.0f), new(1.0f, 1.0f)), - new(new(-0.5f, -0.5f, 0.0f), new(0.0f, 1.0f)) -]; - -uint[] indices = [0, 1, 2, 0, 2, 3]; -``` - -Two triangles (indices `0,1,2` and `0,2,3`) share vertices 0 and 2 to form the quad. - -### Texture and Sampler - -The texture is loaded from a file with mipmaps generated automatically: - -```csharp -texture = App.Context.LoadTextureFromFile(Path.Combine(AppContext.BaseDirectory, "Assets", "shoko.png"), generateMipMaps: true); - -sampler = App.Context.CreateSampler(new() -{ - U = AddressMode.Clamp, - V = AddressMode.Clamp, - W = AddressMode.Clamp, - Filter = Filter.MinLinearMagLinearMipLinear, - MaxLod = uint.MaxValue -}); -``` - -| Property | Value | Purpose | -|----------|-------|---------| -| `AddressMode.Clamp` | U, V, W | Clamp UVs to `[0,1]` — no texture wrapping | -| `Filter` | `MinLinearMagLinearMipLinear` | Trilinear filtering for smooth sampling | -| `MaxLod` | `uint.MaxValue` | Allow all mipmap levels | - -### Resource Binding - -Resources are exposed to shaders through a layout and 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] -}); -``` - -`BindingHelper.Bindings` assigns the correct binding indices per backend. `StageFlags` controls which shader stages can access each resource. - -### Rendering - -The render pass now receives the `resourceTable`, and uses indexed drawing: - -```csharp -commandBuffer.BeginRenderPass(App.FrameBuffer, new() -{ - ColorValues = [new(0.1f, 0.1f, 0.1f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All -}, resourceTable); - -commandBuffer.SetPipeline(pipeline); -commandBuffer.SetResourceTable(resourceTable); -commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); -commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); -commandBuffer.DrawIndexed(6, 1, 0, 0, 0); -``` - -`DrawIndexed(6, 1, 0, 0, 0)` draws 6 indices (2 triangles), 1 instance. - -## Next Steps - -- [Spinning Cube](spinning-cube.md) - Add 3D transformations with constant buffers - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [TexturedQuadRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/TexturedQuadRenderer.cs) diff --git a/documents/tutorials/guides/compute-shader.md b/documents/tutorials/guides/compute-shader.md new file mode 100644 index 00000000..66b1f54d --- /dev/null +++ b/documents/tutorials/guides/compute-shader.md @@ -0,0 +1,253 @@ +# Compute Shader + +Compute Shader produces an image without a render pass. A compute pipeline reads a sampled texture, converts one pixel per thread to grayscale, and writes a storage texture. The host then presents that output with its shared fullscreen-triangle pipeline. + +The grayscale conversion makes the result easy to inspect, but the guide focuses on Zenith.NET resource handles, compute dispatch, and texture layout transitions rather than color-science derivations. + +## Result + +![An image converted to grayscale by a Zenith.NET compute shader](https://raw.githubusercontent.com/qian-o/ZenithTutorials/master/ZenithTutorials/Assets/Screenshots/compute-shader.png) + +The source image is processed once at its original dimensions. It is displayed at those dimensions while the framebuffer is at least as large as the image. Threads outside the image bounds are discarded when the dimensions are not exact multiples of the thread-group size. + +## Frame overview + +```mermaid +flowchart LR + A{Already processed?} -- No --> B[Output to Storage] + B --> C[Bind compute pipeline and constants] + C --> D[Dispatch thread groups] + D --> E[Output to Sampled] + E --> F[Present texture] + A -- Yes --> F +``` + +The output is static, so the compute pass runs only during the first frame. Later frames reuse the sampled result. + +## Resource map + +| Resource | Usage | Access | +| --- | --- | --- | +| Input texture | `Sampled` | Compute shader reads pixels | +| Output texture | `Storage` and `Sampled` | Compute shader writes; presenter reads | +| Constant buffer | `Constant` | Width, height, and two resource handles | +| Compute shader | `CSMain` | One thread per output pixel | +| Compute pipeline | Compute dispatch | Binds the shader executable | + +The output texture needs both usages at creation time because it changes roles after the dispatch. + +## Load the input texture + +Place an image at `Assets/Textures/shoko.png`. The project configuration from Project Setup copies it next to the executable. The ImageSharp extension loads it into a sampled Zenith.NET texture: + +```csharp +inputTexture = App.LoadTexture("shoko.png", false); +``` + +The final argument disables mipmap generation because this workload reads exact source pixels with `Texture2D.Load`. + +`App.LoadTexture` completes the upload before returning and leaves the loaded mip in `Sampled` layout. The compute pass therefore needs no input-texture transition; only the output texture changes layout during `Render`. + +## Create the output texture + +Create a floating-point texture with the same dimensions as the source: + +```csharp +outputTexture = App.Context.CreateTexture(new() +{ + Type = TextureType.Texture2D, + Format = PixelFormat.R32G32B32A32Float, + Width = inputTexture.Desc.Width, + Height = inputTexture.Desc.Height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.Storage +}); +``` + +`R32G32B32A32Float` matches the shader's writable `float4` element type. `Storage` permits writes during compute; `Sampled` permits reads by `TexturePresenter` afterward. + +## Data contract + +The constant buffer passes image dimensions and descriptor handles: + +```slang +struct Constants +{ + uint Width; + + uint Height; + + DescriptorHandle Input; + + DescriptorHandle> Output; +}; + +ConstantBuffer constants; +``` + +The declaration after the structure binds that contract as the constant-buffer view read by `CSMain`. + +The C# layout matches the two four-byte integers followed by two eight-byte handles: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 256)] +file struct Constants +{ + [FieldOffset(0)] + public uint Width; + + [FieldOffset(4)] + public uint Height; + + [FieldOffset(8)] + public ResourceHandle Input; + + [FieldOffset(16)] + public ResourceHandle Output; +} +``` + +| Offset | C# value | Slang view | +| ---: | --- | --- | +| 0 | `inputTexture.Desc.Width` | `uint Width` | +| 4 | `inputTexture.Desc.Height` | `uint Height` | +| 8 | `inputTexture.SampledHandle` | `DescriptorHandle` | +| 16 | `outputTexture.StorageHandle` | `DescriptorHandle>` | + +The structure reserves a 256-byte constant-buffer allocation while its semantic fields occupy the leading bytes. The offsets, rather than the C# field names, define the shared binary contract. + +Initialize and upload one value: + +```csharp +Constants constants = new() +{ + Width = inputTexture.Desc.Width, + Height = inputTexture.Desc.Height, + Input = inputTexture.SampledHandle, + Output = outputTexture.StorageHandle +}; + +constantBuffer = App.LoadBuffer([constants], BufferUsages.Constant); +``` + +## Shader interface + +The shader declares a $16 \times 16$ thread group: + +```slang +[shader("compute")] +[numthreads(16, 16, 1)] +void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadID.xy; + + if (pixel.x >= constants.Width || pixel.y >= constants.Height) + { + return; + } + + float4 color = constants.Input.Load(int3(pixel, 0)); + float3 linear = pow(color.rgb, 2.2); + float grayscale = dot(linear, float3(0.2126, 0.7152, 0.0722)); + grayscale = pow(grayscale, 1.0 / 2.2); + + constants.Output[pixel] = float4(grayscale, grayscale, grayscale, color.a); +} +``` + +`SV_DispatchThreadID` is the global thread coordinate across all dispatched groups. The bounds check is essential because group counts round up to cover partial groups at the image edges. + +The middle lines approximate conversion to linear light, calculate luminance, and convert back for display. That effect can be replaced without changing the pipeline or resource flow; the important interface is one sampled read and one storage write at the same pixel coordinate. + +## Create the compute pipeline + +Compile the compute entry point and create its pipeline: + +```csharp +using Shader computeShader = App.LoadShader("ComputeShader.slang", "CSMain"); + +computePipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); +``` + +A compute pipeline has no attachment formats or rasterizer state. It executes outside a render pass. + +## Dispatch the workload + +Before the first dispatch, transition the output from its undefined initial contents into writable storage layout: + +```csharp +commandBuffer.Transition(outputTexture, default, TextureLayout.Undefined, TextureLayout.Storage); + +commandBuffer.SetPipeline(computePipeline); +commandBuffer.SetConstantBuffer(constantBuffer, 0); +commandBuffer.Dispatch((inputTexture.Desc.Width + ThreadGroupSize - 1) / ThreadGroupSize, (inputTexture.Desc.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1); +``` + +For a dimension $D$ and group size $G$, integer ceiling division is: + +$$ +\left\lceil \frac{D}{G} \right\rceil = \frac{D + G - 1}{G} +$$ + +After all storage writes, transition the texture for sampled reads: + +```csharp +commandBuffer.Transition(outputTexture, default, TextureLayout.Storage, TextureLayout.Sampled); + +processed = true; +``` + +The command buffer preserves this order: transition, dispatch, then transition. The presenter consumes the texture only after it reaches `Sampled` layout. + +## Present the output + +Every frame, including the first, hands the sampled result to the shared presenter: + +```csharp +App.PresentTexture(commandBuffer, drawable, outputTexture, false); +``` + +Passing `false` preserves the texture's original size and centers it when the framebuffer is at least as large in both dimensions. In a smaller framebuffer, the presenter clamps each viewport dimension to the available size. The presenter records the graphics render pass that writes the swap-chain drawable. + +## Lifetime + +This image does not depend on the framebuffer size, so `Resize` is empty. Dispose each renderer-owned resource after the final submitted frame has completed: + +```csharp +public void Dispose() +{ + computePipeline.Dispose(); + constantBuffer.Dispose(); + outputTexture.Dispose(); + inputTexture.Dispose(); +} +``` + +## Inspect the result + +Run the host and select **Compute Shader**. Confirm that: + +- the complete source image appears in grayscale; +- no unprocessed strip appears along the right or bottom edge; +- resizing to a framebuffer at least as large as the image preserves its original display size; +- validation reports no storage/sampled usage or layout errors. + +An unprocessed edge usually indicates floor division or a missing bounds check. A blank result often indicates an incorrect descriptor handle or a missing `Storage` to `Sampled` transition. + +## Explore further + +1. Replace the grayscale calculation with `constants.Output[pixel] = color` to verify a direct copy. +2. Invert `color.rgb` while preserving alpha. +3. Change the declared thread-group size and update the C# `ThreadGroupSize` constant to match. +4. Set `fitToWindow` to `true` when presenting and compare the viewport behavior. + +## Complete source + +- [ComputeShaderRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/ComputeShaderRenderer.cs) +- [ComputeShader.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/ComputeShader.slang) +- [Input texture](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Textures/shoko.png) + +Continue with [Ray Tracing](ray-tracing.md) to reuse the compute-output flow for an image regenerated from inline ray queries every frame. diff --git a/documents/tutorials/guides/hello-triangle.md b/documents/tutorials/guides/hello-triangle.md new file mode 100644 index 00000000..002017d3 --- /dev/null +++ b/documents/tutorials/guides/hello-triangle.md @@ -0,0 +1,216 @@ +# Hello Triangle + +Hello Triangle is the smallest complete graphics workload in the series. It uploads three colored vertices, compiles one vertex and one fragment entry point, creates a graphics pipeline, and records a single draw into the swap-chain texture. + +This guide focuses on how vertex data moves from C# through a Zenith.NET pipeline into Slang. The shared window, command submission, and presentation loop come from [Application Host](../getting-started/application-host.md). + +## Result + +![A vertex-colored triangle rendered with Zenith.NET](https://raw.githubusercontent.com/qian-o/ZenithTutorials/master/ZenithTutorials/Assets/Screenshots/hello-triangle.png) + +The final image is one triangle on a dark clear color. Red, green, and blue values are supplied per vertex and interpolated by the rasterizer across the covered fragments. + +## Frame overview + +The renderer creates its long-lived resources once. Each frame then records one render pass: + +```mermaid +flowchart LR + A[Drawable in ColorAttachment] --> B[Begin render pass and clear] + B --> C[Bind graphics pipeline] + C --> D[Bind vertex buffer] + D --> E[Draw 3 vertices] + E --> F[End render pass] +``` + +The host transitions the drawable into `ColorAttachment` before calling the renderer and transitions it to `Present` afterward. + +## Resource map + +| Resource | Created from | Consumed by | +| --- | --- | --- | +| Vertex buffer | Three `Vertex` values | Vertex input stage | +| Vertex shader | `VSMain` in `HelloTriangle.slang` | Graphics pipeline | +| Fragment shader | `FSMain` in `HelloTriangle.slang` | Graphics pipeline | +| Graphics pipeline | Shaders, input layout, attachment format, render state | Draw command | +| Swap-chain drawable | Application host | Color attachment | + +There is no index buffer, constant buffer, depth attachment, or descriptor handle in this first workload. + +## Data contract + +The CPU vertex stores a three-component position followed by a four-component color: + +```csharp +[StructLayout(LayoutKind.Sequential)] +file struct Vertex(Vector3 position, Vector4 color) +{ + public Vector3 Position = position; + + public Vector4 Color = color; +} +``` + +The corresponding Slang input uses matching attribute types and semantic names: + +```slang +struct VSInput +{ + float3 Position : POSITION0; + + float4 Color : COLOR0; +}; +``` + +An `InputLayout` connects the buffer fields to those semantics: + +```csharp +InputLayout inputLayout = new(); +inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); +inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color }); +``` + +The layout order matches the C# field order. `App.LoadBuffer` supplies `sizeof(Vertex)` as the buffer stride, so the GPU advances by one complete position-and-color record for each vertex. + +## Create the vertex buffer + +The renderer defines three positions directly in clip space. Their colors become the three corners visible in the result: + +```csharp +Vertex[] vertices = +[ + new(new(0.0f, 0.6f, 0.0f), new(1.0f, 0.2f, 0.15f, 1.0f)), + new(new(0.6f, -0.5f, 0.0f), new(0.15f, 0.85f, 0.35f, 1.0f)), + new(new(-0.6f, -0.5f, 0.0f), new(0.2f, 0.45f, 1.0f, 1.0f)) +]; + +vertexBuffer = App.LoadBuffer(vertices, BufferUsages.Vertex); +``` + +`BufferUsages.Vertex` declares that later commands will bind this allocation as vertex input. Because the coordinates already lie in clip space, the workload does not need model, view, or projection matrices. + +## Shader interface + +The vertex shader converts each `float3` position into homogeneous clip-space coordinates and forwards the color: + +```slang +struct FSInput +{ + float4 Position : SV_POSITION; + + float4 Color : COLOR0; +}; + +[shader("vertex")] +FSInput VSMain(VSInput input) +{ + FSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + + return output; +} +``` + +`SV_POSITION` is consumed by rasterization. `COLOR0` is a user varying: the rasterizer interpolates it, then supplies the interpolated value to the fragment shader. + +```slang +[shader("fragment")] +float4 FSMain(FSInput input) : SV_TARGET +{ + return input.Color; +} +``` + +`SV_TARGET` marks the returned value as the color written to the active color attachment. + +## Create the pipeline + +Compile both named entry points and create the graphics pipeline: + +```csharp +using Shader vertexShader = App.LoadShader("HelloTriangle.slang", "VSMain"); +using Shader fragmentShader = App.LoadShader("HelloTriangle.slang", "FSMain"); + +pipeline = App.Context.CreateGraphicsPipeline(new() +{ + VertexShader = vertexShader, + FragmentShader = fragmentShader, + InputLayouts = [inputLayout], + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = new() + { + ColorFormats = [App.ColorFormat], + SampleCount = SampleCount.Count1 + }, + RenderState = new() + { + Rasterizer = RasterizerState.CullNone(), + DepthStencil = DepthStencilState.DepthNone(), + Blend = BlendState.Opaque() + } +}); +``` + +The attachment format must match the swap chain. A triangle list consumes every group of three vertices as one triangle. Culling and depth testing are unnecessary because this workload has one two-dimensional primitive and no depth attachment. + +The `using` declarations limit the temporary `Shader` objects to pipeline creation. + +## Record the frame + +The render method clears the drawable, binds the two resources required by the draw, and emits one triangle: + +```csharp +public void Render(CommandBuffer commandBuffer, Texture drawable) +{ + commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], null); + + commandBuffer.SetPipeline(pipeline); + commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); + + commandBuffer.Draw(3, 1, 0, 0); + + commandBuffer.EndRenderPass(); +} +``` + +The arguments to `Draw` request three vertices, one instance, first vertex zero, and first instance zero. No explicit viewport or scissor is needed here; the render pass uses the drawable dimensions. + +## Resource lifetime + +This renderer has no animated or size-dependent resources, so `Update` and `Resize` remain empty. Dispose both renderer-owned resources after the final submitted frame has completed: + +```csharp +public void Dispose() +{ + pipeline.Dispose(); + vertexBuffer.Dispose(); +} +``` + +## Inspect the result + +Run the tutorial host and select **Hello Triangle**. Confirm that: + +- the triangle appears centered on the dark background; +- all three corner colors are visible and interpolate smoothly; +- resizing the window preserves the workload without recreating resources; +- the validation layer reports no errors. + +If the pipeline is rejected, first compare `App.ColorFormat` with `AttachmentFormats.ColorFormats`. If geometry is missing or corrupted, compare the C# field order, `InputLayout`, and Slang semantics. + +## Explore further + +Try these changes independently: + +1. Change the x and y components while keeping them in $[-1,1]$; leave z at 0 so the vertices remain in the visible depth range. +2. Replace all three colors with one constant color and observe that interpolation no longer produces a gradient. +3. Reverse two vertices and enable back-face culling to inspect how winding affects visibility. +4. Add a fourth vertex and change the topology to explore how a triangle list groups input vertices. + +## Complete source + +- [HelloTriangleRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/HelloTriangleRenderer.cs) +- [HelloTriangle.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/HelloTriangle.slang) + +Continue with [Spinning Cube](spinning-cube.md) to add indexed geometry, transformation constants, back-face culling, and a depth attachment. diff --git a/documents/tutorials/guides/indirect-drawing.md b/documents/tutorials/guides/indirect-drawing.md new file mode 100644 index 00000000..af6ab622 --- /dev/null +++ b/documents/tutorials/guides/indirect-drawing.md @@ -0,0 +1,289 @@ +# Indirect Drawing + +Indirect Drawing reuses the depth-tested cube pipeline to render a $5 \times 5$ grid of animated instances. One structured buffer stores the model matrix and tint for each cube, while one indirect argument record supplies the indexed draw counts to the GPU. + +Two mechanisms are involved: + +- **instancing** draws the shared geometry multiple times and assigns each invocation an `SV_InstanceID`; +- **indirect drawing** reads draw parameters from a buffer instead of C# method arguments. + +The CPU still updates instance data in this workload. The indirect buffer demonstrates the command format; it is not generated by a GPU culling pass. + +## Result + +![A grid of instanced cubes rendered with one indirect draw](https://raw.githubusercontent.com/qian-o/ZenithTutorials/master/ZenithTutorials/Assets/Screenshots/indirect-drawing.png) + +Twenty-five cubes share one vertex buffer, one index buffer, one graphics pipeline, and one indirect draw record. Their positions, rotations, and colors vary through structured instance data. + +## Frame overview + +```mermaid +flowchart LR + A[Update 25 Instance records] --> B[Upload structured buffer] + B --> C[Begin color and depth pass] + C --> D[Bind geometry, constants, and pipeline] + D --> E[DrawIndexedIndirect one record] + E --> F[End render pass] +``` + +The indirect record is static in this example; only the instance buffer changes each frame. + +## Resource map + +| Resource | Usage | Contents | +| --- | --- | --- | +| Vertex buffer | `Vertex` | Shared cube corners | +| Index buffer | `Index` | 36 cube indices | +| Instance buffer | `StorageReadOnly` | 25 model matrices and colors | +| Indirect buffer | `Indirect` | One `IndirectDrawIndexedArgs` record | +| Constant buffer | `Constant` | View, projection, and instance-buffer handle | +| Graphics pipeline | Indexed instanced rendering | Depth and back-face culling enabled | +| Depth texture | Depth-stencil attachment | Recreated after resize | + +The shader reaches the structured buffer through a descriptor handle stored in the constant buffer. It does not bind the instance buffer as vertex input. + +## Define the indirect command + +The indirect structure contains the same values supplied to a direct indexed draw: + +```csharp +IndirectDrawIndexedArgs arguments = new() +{ + IndexCount = (uint)indices.Length, + InstanceCount = GridSize * GridSize +}; +``` + +Unassigned fields remain zero, so this record means: + +| Field | Value | Meaning | +| --- | ---: | --- | +| `IndexCount` | 36 | Indices consumed per instance | +| `InstanceCount` | 25 | Number of cube instances | +| `FirstIndex` | 0 | Start of the index buffer | +| `VertexOffset` | 0 | Added to each selected index | +| `FirstInstance` | 0 | Starting system instance ID | + +Upload one record with `BufferUsages.Indirect`: + +```csharp +indirectBuffer = App.LoadBuffer([arguments], BufferUsages.Indirect); +``` + +The final draw call requests one record from this buffer. It does not pass the instance count separately. + +## Instance data contract + +Each instance occupies 80 bytes in both C# and Slang: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 80)] +file struct Instance +{ + [FieldOffset(0)] + public Matrix4x4 Model; + + [FieldOffset(64)] + public Vector4 Color; +} +``` + +```slang +struct Instance +{ + float4x4 Model; + + float4 Color; +}; +``` + +The renderer allocates enough structured-buffer elements for the complete grid: + +```csharp +instanceBuffer = App.LoadBuffer(new Instance[GridSize * GridSize], BufferUsages.StorageReadOnly); +``` + +`App.LoadBuffer` records `sizeof(Instance)` as the stride. The shader therefore indexes a sequence of 80-byte records through `StructuredBuffer`. + +## Constant-buffer contract + +The vertex shader also needs the camera matrices and a handle to the instance buffer: + +```slang +struct Constants +{ + float4x4 View; + + float4x4 Projection; + + DescriptorHandle> Instances; +}; +``` + +The corresponding C# offsets place the handle after two 64-byte matrices: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 256)] +file struct Constants +{ + [FieldOffset(0)] + public Matrix4x4 View; + + [FieldOffset(64)] + public Matrix4x4 Projection; + + [FieldOffset(128)] + public ResourceHandle Instances; +} +``` + +On initialization and resize, upload `instanceBuffer.StorageReadOnlyHandle` with the current view and projection: + +```csharp +Constants constants = new() +{ + View = Matrix4x4.CreateLookAt(new(0.0f, 0.0f, 8.0f), Vector3.Zero, Vector3.UnitY), + Projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)width / height, 0.1f, 100.0f), + Instances = instanceBuffer.StorageReadOnlyHandle +}; +``` + +## Select an instance in Slang + +`SV_InstanceID` is generated by the draw command. It is a system value, not an attribute in the C# `InputLayout`: + +```slang +struct VSInput +{ + float3 Position : POSITION0; + + float4 Color : COLOR0; + + uint InstanceID : SV_InstanceID; +}; +``` + +The vertex shader uses that ID to select one structured-buffer element: + +```slang +[shader("vertex")] +VSOutput VSMain(VSInput input) +{ + Instance instance = constants.Instances[input.InstanceID]; + float4 worldPosition = mul(float4(input.Position, 1.0), instance.Model); + float4 viewPosition = mul(worldPosition, constants.View); + + VSOutput output; + output.Position = mul(viewPosition, constants.Projection); + output.Color = input.Color * instance.Color; + + return output; +} +``` + +Every invocation reads the same cube geometry. The instance record supplies only its world transform and tint. + +## Update instance records + +The CPU calculates one record for every grid coordinate: + +```csharp +for (uint index = 0; index < GridSize * GridSize; index++) +{ + uint x = index % GridSize; + uint y = index / GridSize; + float offsetX = (x - ((GridSize - 1) * 0.5f)) * 1.5f; + float offsetY = (y - ((GridSize - 1) * 0.5f)) * 1.5f; + float rotation = rotationAngle * (1.0f + (index * 0.1f)); + + pointer[index] = new() + { + Model = Matrix4x4.CreateScale(0.4f) * Matrix4x4.CreateRotationY(rotation) * Matrix4x4.CreateRotationX(rotation * 0.5f) * Matrix4x4.CreateTranslation(offsetX, offsetY, 0.0f), + Color = new((float)x / GridSize, (float)y / GridSize, 1.0f - ((float)x / GridSize), 1.0f) + }; +} +``` + +After filling the array, upload all records in one contiguous copy: + +```csharp +instanceBuffer.Upload(0, new() +{ + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Instance) * instances.Length) +}); +``` + +This direct CPU update is intentionally simple. A larger renderer would normally avoid allocating a new managed array every frame and would coordinate dynamic-buffer reuse across frames in flight. + +The shared host waits for each submitted frame before the next update, so this single instance buffer is not overwritten while an earlier frame is reading it. + +## Record the indirect draw + +Create the depth attachment on first use and transition it from `Undefined` to `DepthStencilAttachment`. The texture remains in that layout until resize replaces it. Then begin a color/depth render pass, bind the shared geometry and constants, and execute one indirect record: + +```csharp +if (depthTexture is null) +{ + depthTexture = App.Context.CreateTexture(TextureDesc.DepthStencilAttachment(PixelFormat.D32FloatS8UInt, App.Width, App.Height, SampleCount.Count1)); + + commandBuffer.Transition(depthTexture, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment); +} + +commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], DepthStencilAttachment.Clear(depthTexture, 1.0f, 0)); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); +commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); +commandBuffer.SetConstantBuffer(constantBuffer, 0); + +commandBuffer.DrawIndexedIndirect(indirectBuffer, 0, 1); + +commandBuffer.EndRenderPass(); +``` + +The arguments mean indirect-buffer byte offset zero and draw count one. That one record expands to an indexed draw with 25 instances. + +The GPU does not read the indirect buffer through a shader descriptor. The command processor interprets it because the allocation was created with `BufferUsages.Indirect` and passed to `DrawIndexedIndirect`. + +## Resize and lifetime + +Resize invalidates the depth texture and uploads a projection for the new aspect ratio. Geometry, indirect arguments, and instance-buffer capacity remain unchanged. + +Release resources in dependency order: + +```csharp +depthTexture?.Dispose(); + +pipeline.Dispose(); +constantBuffer.Dispose(); +instanceBuffer.Dispose(); +indirectBuffer.Dispose(); +indexBuffer.Dispose(); +vertexBuffer.Dispose(); +``` + +## Inspect the result + +Run the host and select **Indirect Drawing**. Confirm that: + +- 25 cubes appear in a centered grid; +- each cube rotates and receives a distinct tint; +- resize preserves the grid proportions and depth behavior; +- validation reports no buffer-usage, structured-stride, or indirect-command errors. + +If every cube uses the same transform, inspect `SV_InstanceID` and the structured-buffer handle. If only one cube appears, inspect `InstanceCount` in the indirect record. If the draw is rejected, confirm `BufferUsages.Indirect` and the record layout. + +## Explore further + +1. Replace `DrawIndexedIndirect` temporarily with `DrawIndexed(36, GridSize * GridSize, 0, 0, 0)` and verify that the picture remains equivalent. +2. Change `GridSize`, then update buffer capacity and camera distance together. +3. Set `FirstInstance` to a nonzero value and account for it in the instance buffer. +4. Create multiple indirect records and increase the final `drawCount` argument. + +## Complete source + +- [IndirectDrawingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/IndirectDrawingRenderer.cs) +- [IndirectDrawing.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/IndirectDrawing.slang) + +Continue with [Ray Tracing](ray-tracing.md) to see a different kind of GPU-readable scene structure, or [Mesh Shading](mesh-shading.md) to move geometry emission into programmable shader stages. diff --git a/documents/tutorials/guides/mesh-shading.md b/documents/tutorials/guides/mesh-shading.md new file mode 100644 index 00000000..a8ae80a2 --- /dev/null +++ b/documents/tutorials/guides/mesh-shading.md @@ -0,0 +1,454 @@ +# Mesh Shading + +Mesh Shading replaces fixed vertex and index fetch with programmable task and mesh stages. This workload renders a $10 \times 10 \times 10$ sphere grid. Task threads test candidate instances against the camera frustum, compact visible IDs into a payload, and launch one mesh workgroup for each surviving sphere. + +This guide focuses on the Zenith.NET mesh-shading pipeline, structured geometry, C#/Slang layout, task payload, synchronization, and mesh dispatch. The UV-sphere construction and final diffuse-lighting calculation support the sample result but are not derived as general geometry or lighting algorithms. + +## Result + +![A culled grid of spheres emitted by task and mesh shaders](https://raw.githubusercontent.com/qian-o/ZenithTutorials/master/ZenithTutorials/Assets/Screenshots/mesh-shading.png) + +The camera moves around 1,000 possible sphere instances. Only instances intersecting the view frustum produce mesh workgroups and rasterized geometry. + +> [!IMPORTANT] +> Mesh shading is optional. Check `App.Context.Capabilities.MeshShadingSupported` before creating shaders, buffers, or a mesh-shading pipeline. + +## Frame overview + +```mermaid +flowchart LR + A[Update camera and frustum constants] --> B[Begin color and depth pass] + B --> C[Bind mesh-shading pipeline] + C --> D[Bind constants] + D --> E[Dispatch 32 task groups] + E --> F[Task groups compact visible IDs] + F --> G[Mesh groups emit sphere geometry] + G --> H[Rasterize and shade fragments] +``` + +No vertex or index buffer is bound through traditional command-buffer slots. The mesh shader reads source geometry through descriptor handles and writes pipeline outputs directly. + +## Pipeline stages + +```mermaid +flowchart TD + A[32 candidate IDs per task group] --> B[Frustum test] + B --> C[groupshared payload] + C --> D[DispatchMesh visible count] + D --> E[One mesh group per visible ID] + E --> F[Emit 62 vertices] + E --> G[Emit 120 triangles] + F --> H[Rasterization] + G --> H + H --> I[Fragment shader] +``` + +Zenith.NET calls the first stage `TaskShader`; Slang declares the corresponding entry point with `[shader("task")]`. Other APIs may call this an amplification or object stage. + +## Resource map + +| Resource | Usage | Role | +| --- | --- | --- | +| Vertex buffer | `StorageReadOnly` | Source sphere positions and normals | +| Triangle buffer | `StorageReadOnly` | Source sphere index triplets | +| Constant buffer | `Constant` | View-projection, frustum, light, and two handles | +| Task shader | `ASMain` | Culls and compacts candidate instance IDs | +| Mesh shader | `MSMain` | Emits transformed vertices and primitive indices | +| Fragment shader | `FSMain` | Applies simple directional lighting | +| Mesh-shading pipeline | Task, mesh, and fragment stages | Rasterized output | +| Depth texture | Depth-stencil attachment | Recreated after resize | + +The source geometry remains in structured buffers for the renderer lifetime. Each visible instance reuses the same 62 vertices and 120 triangles. + +## Check device support + +Reject unsupported devices before generating or uploading the source mesh: + +```csharp +if (!App.Context.Capabilities.MeshShadingSupported) +{ + throw new PlatformNotSupportedException("Mesh Shading is not supported by the selected device."); +} +``` + +The capability controls creation and execution of the task/mesh pipeline. It is independent of the traditional graphics pipeline used by earlier guides. + +## Create the source mesh + +The CPU generates one compact UV sphere with 62 vertices and 120 triangles. It creates a north pole, five latitude rings, a south pole, and indices for both caps and the ring quads. + +The complete generation loop is available in the renderer source. Its important output for this workload is two tightly defined arrays: + +```csharp +vertexBuffer = App.LoadBuffer([.. sphereVertices], BufferUsages.StorageReadOnly); +triangleBuffer = App.LoadBuffer([.. sphereTriangles], BufferUsages.StorageReadOnly); +constantBuffer = App.LoadBuffer([new Constants()], BufferUsages.Constant); +``` + +These are structured shader inputs, not traditional vertex and index bindings. Their element strides come from the explicit C# structures described next. + +## Geometry data contract + +Three-component vectors can receive different alignment across shader targets. The C# vertex therefore places position and normal at separate 16-byte boundaries: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 32)] +file struct Vertex +{ + [FieldOffset(0)] + public Vector3 Position; + + [FieldOffset(16)] + public Vector3 Normal; +} +``` + +The Slang side uses two `float4` backing values: + +```slang +struct Vertex +{ + private float4 PositionAndPadding; + + private float4 NormalAndPadding; + + property float3 Position + { + get { + return PositionAndPadding.xyz; + } + } + + property float3 Normal + { + get { + return NormalAndPadding.xyz; + } + } +}; +``` + +Triangle elements follow the same pattern. C# stores three indices in a 16-byte record: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 16)] +file struct Triangle +{ + [FieldOffset(0)] + public uint Index0; + + [FieldOffset(4)] + public uint Index1; + + [FieldOffset(8)] + public uint Index2; +} +``` + +Slang reads one `uint4` and exposes its first three lanes: + +```slang +struct Triangle +{ + private uint4 IndicesAndPadding; + + property uint3 Indices + { + get { + return IndicesAndPadding.xyz; + } + } +}; +``` + +| Structured element | C# size | Slang backing storage | +| --- | ---: | --- | +| `Vertex` | 32 bytes | Two `float4` values | +| `Triangle` | 16 bytes | One `uint4` value | + +Explicit backing storage keeps these strides consistent across the supported graphics APIs. + +## Frame constants + +The constant buffer supplies one view-projection matrix, six frustum planes, a light direction, and two structured-buffer handles: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 256)] +file struct Constants +{ + [FieldOffset(0)] + public Matrix4x4 ViewProjection; + + [FieldOffset(64)] + public FrustumPlanes FrustumPlanes; + + [FieldOffset(160)] + public Vector3 LightDirection; + + [FieldOffset(176)] + public ResourceHandle Vertices; + + [FieldOffset(184)] + public ResourceHandle Triangles; +} +``` + +`FrustumPlanes` is an inline array of six `Vector4` values, occupying 96 bytes from offset 64 through 159. Slang backs `LightDirection` with a `float4`, so the handles begin at byte 176 rather than immediately after the 12-byte C# `Vector3`. + +```slang +struct Constants +{ + float4x4 ViewProjection; + + float4 FrustumPlanes[6]; + + private float4 LightDirectionAndPadding; + + DescriptorHandle> Vertices; + + DescriptorHandle> Triangles; + + property float3 LightDirection + { + get { + return LightDirectionAndPadding.xyz; + } + } +}; +``` + +The renderer extracts and normalizes the six planes from the current view-projection matrix. That matrix extraction is supporting camera math; the mesh-shading interface only requires six consistently oriented `float4` plane equations. + +## Create the pipeline + +Compile all three stage entry points: + +```csharp +using Shader taskShader = App.LoadShader("MeshShading.slang", "ASMain"); +using Shader meshShader = App.LoadShader("MeshShading.slang", "MSMain"); +using Shader fragmentShader = App.LoadShader("MeshShading.slang", "FSMain"); +``` + +Create a mesh-shading pipeline with the same color, depth, culling, and blend configuration used by the spinning cube: + +```csharp +pipeline = App.Context.CreateMeshShadingPipeline(new() +{ + TaskShader = taskShader, + MeshShader = meshShader, + FragmentShader = fragmentShader, + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = new() + { + ColorFormats = [App.ColorFormat], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, + RenderState = new() + { + Rasterizer = RasterizerState.CullBack(), + DepthStencil = DepthStencilState.DepthReadWrite(), + Blend = BlendState.Opaque() + } +}); +``` + +There is no `InputLayouts` field because fixed vertex fetch is not part of this pipeline. `TaskShader` is optional at the API level, but this workload uses it to generate mesh work dynamically. + +## Map instance IDs + +A linear ID maps into the $10 \times 10 \times 10$ grid: + +```slang +void DecomposeInstanceID(uint id, out uint x, out uint y, out uint z) +{ + x = id % GridSize; + y = (id / GridSize) % GridSize; + z = id / (GridSize * GridSize); +} +``` + +Helper functions convert those coordinates into world positions and colors. This arithmetic controls sample placement; it does not alter the task or mesh pipeline contract. + +## Cull and compact in the task shader + +The task stage declares 32 threads and two groupshared values: + +```slang +groupshared Payload s_payload; +groupshared uint s_visibleCount; + +[shader("task")] +[numthreads(ASGroupSize, 1, 1)] +void ASMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID) +``` + +Each thread derives one candidate instance and tests its bounding sphere against all six frustum planes: + +```slang +uint instanceIndex = groupID * ASGroupSize + groupThreadID; + +bool visible = false; +if (instanceIndex < GridSize * GridSize * GridSize) +{ + float3 worldPos = InstancePosition(instanceIndex); + visible = !IsFrustumCulled(worldPos, BoundingSphereRadius); +} +``` + +Thread zero resets the shared counter, then all threads synchronize: + +```slang +if (groupThreadID == 0) +{ + s_visibleCount = 0; +} + +GroupMemoryBarrierWithGroupSync(); +``` + +Visible threads reserve unique payload slots with an atomic increment: + +```slang +if (visible) +{ + uint offset; + InterlockedAdd(s_visibleCount, 1, offset); + s_payload.InstanceIndices[offset] = instanceIndex; +} + +GroupMemoryBarrierWithGroupSync(); + +DispatchMesh(s_visibleCount, 1, 1, s_payload); +``` + +The second barrier ensures all payload writes are visible before the task group launches mesh work. A task group dispatches between zero and 32 mesh groups, one for each compacted instance ID. + +## Emit geometry in the mesh shader + +The mesh shader receives the payload, selects one instance, and declares its output capacity: + +```slang +[shader("mesh")] +[numthreads(120, 1, 1)] +[outputtopology("triangle")] +void MSMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID, in payload Payload meshPayload, OutputVertices outVertices, OutputIndices outIndices) +{ + uint instanceIndex = meshPayload.InstanceIndices[groupID]; + float3 instancePos = InstancePosition(instanceIndex); + float3 color = InstanceColor(instanceIndex); + + SetMeshOutputCounts(SphereVertexCount, SphereTriangleCount); +``` + +The group has 120 threads because the source sphere contains 120 triangles. Threads zero through 61 also transform and emit the 62 vertices: + +```slang +if (groupThreadID < SphereVertexCount) +{ + Vertex v = constants.Vertices[groupThreadID]; + float3 worldPos = v.Position + instancePos; + + VertexOutput output; + output.Position = mul(float4(worldPos, 1.0), constants.ViewProjection); + output.WorldNormal = v.Normal; + output.Color = color; + + outVertices[groupThreadID] = output; +} + +if (groupThreadID < SphereTriangleCount) +{ + outIndices[groupThreadID] = constants.Triangles[groupThreadID].Indices; +} +``` + +`SetMeshOutputCounts` establishes the valid output ranges before writes occur. The fragment shader receives the emitted normal and color, then applies a compact ambient-plus-diffuse lighting calculation. + +## Update frame data + +Each update moves the camera, creates the view-projection matrix, extracts frustum planes, and supplies the two storage handles: + +```csharp +Constants constants = new() +{ + ViewProjection = viewProjection, + FrustumPlanes = new(viewProjection), + LightDirection = -Vector3.Normalize(cameraPosition), + Vertices = vertexBuffer.StorageReadOnlyHandle, + Triangles = triangleBuffer.StorageReadOnlyHandle +}; +``` + +The same matrix controls both emitted vertex positions and the planes used for culling, so visibility and rasterization share one camera definition. + +## Record the mesh dispatch + +Create the depth attachment on first use and transition it from `Undefined` to `DepthStencilAttachment`. Begin a color/depth render pass, bind the pipeline and constants, dispatch the task groups, and end the pass: + +```csharp +if (depthTexture is null) +{ + depthTexture = App.Context.CreateTexture(TextureDesc.DepthStencilAttachment(PixelFormat.D32FloatS8UInt, App.Width, App.Height, SampleCount.Count1)); + + commandBuffer.Transition(depthTexture, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment); +} + +commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.05f, 0.05f, 0.08f, 1.0f))], DepthStencilAttachment.Clear(depthTexture, 1.0f, 0)); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetConstantBuffer(constantBuffer, 0); + +commandBuffer.DispatchMesh(32, 1, 1); + +commandBuffer.EndRenderPass(); +``` + +There are 1,000 candidate instances and 32 task threads per group: + +$$ +\left\lceil \frac{1000}{32} \right\rceil = 32 +$$ + +The bounds check discards candidate IDs at or above 1,000 in the final task group. Frustum testing then determines how many mesh groups each task group launches. + +## Resize and lifetime + +Only the depth attachment depends on framebuffer dimensions. Dispose it during `Resize` and let the next frame create a replacement. The source mesh, pipeline, and constant-buffer allocation remain valid. + +Release the optional depth texture, then the pipeline and its buffers: + +```csharp +depthTexture?.Dispose(); + +pipeline.Dispose(); +constantBuffer.Dispose(); +triangleBuffer.Dispose(); +vertexBuffer.Dispose(); +``` + +## Inspect the result + +Run the host and select **Mesh Shading** on a supported device. Confirm that: + +- the camera moves around a colored sphere grid; +- visible spheres remain complete as they approach the viewport edges; +- resize preserves depth behavior and camera proportions; +- validation reports no structured-stride, payload, output-count, or attachment errors. + +Missing spheres inside the view often indicate incorrect frustum-plane orientation or a missing groupshared barrier. Corrupted geometry points to a C#/Slang stride mismatch or output counts that disagree with the source mesh. A rejected pipeline usually indicates missing mesh-shading capability or mismatched shader stages. + +## Explore further + +1. Replace `IsFrustumCulled` with `false` and compare the number of generated mesh groups. +2. Visualize visibility by assigning a constant color per task group. +3. Change `InstanceSpacing` without modifying the source sphere buffers. +4. Add a mesh entry point with no payload parameter that selects instance zero, create a pipeline with `TaskShader = null`, and dispatch one mesh group to isolate geometry emission. +5. Replace the UV sphere with another mesh while keeping the vertex and triangle contracts unchanged. + +## Complete source + +- [MeshShadingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/MeshShadingRenderer.cs) +- [MeshShading.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/MeshShading.slang) + +This completes the guide set. Use the [Zenith.NET documentation](../../docs/index.md) and [API reference](../../api/index.md) to inspect the underlying resource and command types in more detail. diff --git a/documents/tutorials/guides/ray-tracing.md b/documents/tutorials/guides/ray-tracing.md new file mode 100644 index 00000000..73d43a2a --- /dev/null +++ b/documents/tutorials/guides/ray-tracing.md @@ -0,0 +1,441 @@ +# Ray Tracing + +Ray Tracing reuses the compute-output flow from Compute Shader, but each thread now traces a camera ray through an acceleration structure. The scene contains a triangle floor and three procedural spheres represented by axis-aligned bounding boxes. + +This guide explains the Zenith.NET acceleration structures, resource handles, inline ray-query interface, output texture, and synchronization. The complete shader also implements soft shadows, rough reflections, checkerboard filtering, and tone mapping; those supporting effects are summarized rather than derived line by line. + +## Result + +![A ray-traced floor and procedural spheres rendered with Zenith.NET](https://raw.githubusercontent.com/qian-o/ZenithTutorials/master/ZenithTutorials/Assets/Screenshots/ray-tracing.png) + +The camera orbits a checkerboard floor and three colored spheres. Primary rays determine the visible surface, while additional queries produce shadows and reflections. + +> [!IMPORTANT] +> Inline ray tracing is optional. Check `App.Context.Capabilities.RayTracingSupported` before creating any acceleration-structure resources. + +## Frame overview + +Acceleration structures are built once during renderer construction. Every frame updates the camera, regenerates the output texture, and presents it: + +```mermaid +flowchart LR + A[Update camera constants] --> B[Output to Storage] + B --> C[Bind ray-query compute pipeline] + C --> D[Dispatch one thread per pixel] + D --> E[Output to Sampled] + E --> F[Present texture] +``` + +The output is recreated after a framebuffer resize, but the scene geometry and acceleration structures remain valid. + +## Scene structure + +The acceleration structures form a two-level hierarchy: + +```mermaid +flowchart TD + A[TLAS] --> B[Floor instance] + A --> C[Sphere instance] + B --> D[Triangle BLAS] + C --> E[AABB BLAS] + D --> F[Floor vertex and index buffers] + E --> G[AABB buffer] + H[Sphere structured buffer] --> I[Inline sphere-intersection logic in CSMain] +``` + +- A bottom-level acceleration structure, or **BLAS**, describes geometry. +- A top-level acceleration structure, or **TLAS**, instances one or more BLAS objects into the traced scene. +- Triangle geometry can be intersected directly by traversal hardware. +- An AABB narrows traversal to a candidate region; the shader must test and commit the exact procedural intersection. + +The sphere structured buffer is not part of the AABB geometry description. The shader reads it separately to recover each candidate's center, radius, and color. + +## Resource map + +| Resource | Usage | Role | +| --- | --- | --- | +| Floor vertex buffer | `StorageReadOnly` | Triangle BLAS positions | +| Floor index buffer | `StorageReadOnly` | Triangle BLAS indices | +| AABB buffer | `StorageReadOnly` | Procedural BLAS bounds | +| Sphere buffer | `StorageReadOnly` | Exact intersection and material data | +| Floor BLAS | Acceleration structure | Triangle geometry | +| Sphere BLAS | Acceleration structure | Procedural AABB geometry | +| TLAS | Acceleration structure | Scene instances supplied to ray queries | +| Constant buffer | `Constant` | Camera and three resource handles | +| Output texture | `Storage` and `Sampled` | Compute output and presenter input | +| Compute pipeline | `CSMain` | Primary and secondary inline queries | + +Build-input buffers must remain alive until the BLAS build or update submission completes. The BLAS objects themselves must remain alive for as long as a TLAS that instances them is in use. The sphere buffer has an additional lifetime requirement because the shader reads it during every frame. + +## Check device support + +Fail before allocating workload resources when the selected device does not expose ray tracing: + +```csharp +if (!App.Context.Capabilities.RayTracingSupported) +{ + throw new PlatformNotSupportedException("Ray Tracing is not supported by the selected device."); +} +``` + +Check the capability before creating the shaders and acceleration structures required by this workload. + +## Create scene geometry + +The floor uses four positions and six indices. Three `Sphere` values provide procedural geometry and shading data. Convert each sphere into an AABB: + +```csharp +Aabb[] aabbs = new Aabb[spheres.Length]; +for (int index = 0; index < spheres.Length; index++) +{ + aabbs[index] = new(spheres[index].Center - new Vector3(spheres[index].Radius), spheres[index].Center + new Vector3(spheres[index].Radius)); +} +``` + +Upload all four arrays: + +```csharp +floorVertexBuffer = App.LoadBuffer(floorVertices, BufferUsages.StorageReadOnly); +floorIndexBuffer = App.LoadBuffer(floorIndices, BufferUsages.StorageReadOnly); +aabbBuffer = App.LoadBuffer(aabbs, BufferUsages.StorageReadOnly); +sphereBuffer = App.LoadBuffer(spheres, BufferUsages.StorageReadOnly); +constantBuffer = App.LoadBuffer([new Constants()], BufferUsages.Constant); +``` + +These buffers are read during acceleration-structure construction or shader execution. They are not vertex and index bindings in a graphics render pass. + +## Create the compute pipeline + +Load the `CSMain` entry point and create the compute pipeline used for every inline ray-query dispatch: + +```csharp +using Shader computeShader = App.LoadShader("RayTracing.slang", "CSMain"); + +rayTracingPipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); +``` + +The acceleration structures and descriptor handles become inputs to this pipeline; no separate ray-tracing pipeline or intersection-shader stage is created. + +## Build the bottom-level structures + +Record acceleration-structure construction on a compute command buffer: + +```csharp +CommandBuffer commandBuffer = App.Context.ComputeQueue.CommandBuffer(); +``` + +The sample submits this one-time build and waits for it to complete before rendering begins. + +Describe the indexed triangle geometry for the floor: + +```csharp +floorBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc() +{ + Geometries = + [ + RayTracingGeometry.Triangles(new() + { + VertexBuffer = floorVertexBuffer, + VertexFormat = PixelFormat.R32G32B32Float, + VertexCount = (uint)floorVertices.Length, + VertexStrideInBytes = (uint)sizeof(Vector3), + IndexBuffer = floorIndexBuffer, + IndexFormat = IndexFormat.UInt32, + IndexCount = (uint)floorIndices.Length, + Transform = Matrix4x4.Identity + }, true) + ], + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace +}); +``` + +The vertex format and stride tell the builder how to read each `Vector3`. The final `true` marks the geometry opaque, allowing traversal to accept triangle hits without an any-hit stage. + +The procedural BLAS instead reads an array of AABBs: + +```csharp +sphereBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc() +{ + Geometries = + [ + RayTracingGeometry.Aabbs(new() + { + Buffer = aabbBuffer, + Count = (uint)spheres.Length, + StrideInBytes = aabbBuffer.Desc.StrideInBytes + }, true) + ], + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace +}); +``` + +The AABBs accelerate traversal, but they do not turn boxes into visible surfaces. `CSMain` later performs the exact sphere intersection for each procedural candidate. + +## Build the top-level scene + +Create one identity-transform instance for each BLAS: + +```csharp +tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc() +{ + Instances = + [ + new() + { + AccelerationStructure = floorBlas, + InstanceId = 0, + VisibilityMask = 0xFF, + Transform = Matrix4x4.Identity + }, + new() + { + AccelerationStructure = sphereBlas, + InstanceId = 1, + VisibilityMask = 0xFF, + Transform = Matrix4x4.Identity + } + ], + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace +}); + +commandBuffer.Submit().Wait(); +``` + +The CPU wait ensures both BLAS objects and the TLAS are complete before construction returns. The instance IDs distinguish scene entries; procedural primitive indices still identify individual AABBs inside the sphere BLAS. + +## Data contract + +Shared C#/Slang layout requires care around three-component vectors. The CPU sphere packs center and radius into the first 16 bytes, followed by color at byte 16: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 32)] +file struct Sphere +{ + [FieldOffset(0)] + public Vector3 Center; + + [FieldOffset(12)] + public float Radius; + + [FieldOffset(16)] + public Vector3 Color; +} +``` + +Slang uses two `float4` backing fields and exposes semantic properties: + +```slang +struct Sphere +{ + private float4 CenterAndRadius; + + private float4 ColorAndPadding; + + property float3 Center + { + get { + return CenterAndRadius.xyz; + } + } + + property float Radius + { + get { + return CenterAndRadius.w; + } + } + + property float3 Color + { + get { + return ColorAndPadding.xyz; + } + } +}; +``` + +This explicit storage keeps the structured-buffer stride at 32 bytes across the supported graphics APIs. + +The frame constants use the same technique for camera position, followed by three handles: + +| Offset | C# | Slang | +| ---: | --- | --- | +| 0 | `Vector3 Position` | `PositionAndPadding.xyz` | +| 16 | `tlas.Handle` | `RaytracingAccelerationStructure Scene` | +| 24 | `sphereBuffer.StorageReadOnlyHandle` | `StructuredBuffer Spheres` | +| 32 | `outputTexture.StorageHandle` | `RWTexture2D OutputTexture` | + +## Inline ray-query interface + +Each compute thread reconstructs one camera ray and begins traversal through the TLAS: + +```slang +RayDesc ray; +ray.Origin = cameraPos; +ray.Direction = rayDir; +ray.TMin = RayEpsilon; +ray.TMax = 1000.0; + +float3 sphereHitNormal = float3(0.0); +float3 sphereHitColor = float3(0.0); + +RayQuery query; +query.TraceRayInline(constants.Scene, RAY_FLAG_NONE, 0xFF, ray); +``` + +The two temporary values preserve the normal and material color produced by a procedural sphere candidate after that candidate is committed. + +Triangle intersections can become committed hits automatically. Procedural AABBs are reported as candidates and require an exact test: + +```slang +while (query.Proceed()) +{ + if (query.CandidateType() == CANDIDATE_PROCEDURAL_PRIMITIVE) + { + uint sphereIndex = query.CandidatePrimitiveIndex(); + Sphere sphere = constants.Spheres[sphereIndex]; + + float3 ro = query.CandidateObjectRayOrigin(); + float3 rd = query.CandidateObjectRayDirection(); + + float t = IntersectSphere(ro, rd, sphere); + + if (t >= query.RayTMin() && t <= query.CommittedRayT()) + { + float3 hitPoint = ro + rd * t; + + sphereHitNormal = normalize(hitPoint - sphere.Center); + sphereHitColor = sphere.Color; + + query.CommitProceduralPrimitiveHit(t); + } + } +} +``` + +`CandidatePrimitiveIndex` maps the AABB candidate back to its sphere record. The shader computes a positive intersection distance and commits it only when it lies inside the query's valid interval. + +After traversal, committed status selects the visible surface: + +```slang +if (query.CommittedStatus() == COMMITTED_TRIANGLE_HIT) +{ + float3 hitPoint = ray.Origin + ray.Direction * query.CommittedRayT(); + color = ShadeFloor(hitPoint, rayDir, cameraPos); +} +else if (query.CommittedStatus() == COMMITTED_PROCEDURAL_PRIMITIVE_HIT) +{ + float3 hitPoint = ray.Origin + ray.Direction * query.CommittedRayT(); + color = ShadePrimarySphere(hitPoint, rayDir, cameraPos, sphereHitNormal, sphereHitColor); +} +else +{ + color = SampleSky(rayDir); +} +``` + +## Supporting shading flow + +The remaining shader functions improve the sample image but do not change the Zenith.NET resource interface: + +```mermaid +flowchart TD + A[CSMain primary query] --> B[Shade floor] + A --> C[Shade sphere] + A --> D[Sample sky] + B --> E[Shadow queries] + B --> F[Reflection query] + C --> E + C --> G[Rough reflection queries] + B --> H[ACES tone mapping] + C --> H + D --> H +``` + +Shadow rays reuse the TLAS with `RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH`. Reflection rays use the same procedural-candidate loop as the primary query. Multiple jittered directions soften shadows and reflections. Finally, `ACESFilm` maps the accumulated HDR color into the output range. + +You can replace these helpers with simpler colors while preserving the acceleration structures, descriptors, pipeline, dispatch, and texture transitions described by this guide. + +## Create and update the output + +Create an `R32G32B32A32Float` texture with `Sampled | Storage` usage when the renderer first sees a valid framebuffer size. A new texture is initially established as sampled because every subsequent frame follows the same repeating cycle: + +```text +Sampled -> Storage -> compute writes -> Sampled -> presenter reads +``` + +Update the camera and resource handles before dispatch: + +```csharp +Constants constants = new() +{ + Position = new(12.0f * MathF.Sin(totalTime * 0.3f), 4.0f + MathF.Sin(totalTime * 0.2f), -12.0f * MathF.Cos(totalTime * 0.3f)), + Scene = tlas.Handle, + Spheres = sphereBuffer.StorageReadOnlyHandle, + OutputTexture = outputTexture.StorageHandle +}; +``` + +Then record the state changes and dispatch: + +```csharp +commandBuffer.Transition(outputTexture, default, TextureLayout.Sampled, TextureLayout.Storage); + +commandBuffer.SetPipeline(rayTracingPipeline); +commandBuffer.SetConstantBuffer(constantBuffer, 0); +commandBuffer.Dispatch((App.Width + ThreadGroupSize - 1) / ThreadGroupSize, (App.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1); + +commandBuffer.Transition(outputTexture, default, TextureLayout.Storage, TextureLayout.Sampled); + +App.PresentTexture(commandBuffer, drawable, outputTexture, true); +``` + +Unlike the static grayscale image, this workload dispatches every frame because the camera moves. Passing `true` to the presenter fits the output to the framebuffer. + +## Resize and lifetime + +Resize disposes only the framebuffer-sized output texture. The next frame recreates it and uploads its new storage handle. + +Dispose resources from users to dependencies: + +```csharp +outputTexture?.Dispose(); + +rayTracingPipeline.Dispose(); +constantBuffer.Dispose(); +tlas.Dispose(); +sphereBlas.Dispose(); +floorBlas.Dispose(); +sphereBuffer.Dispose(); +aabbBuffer.Dispose(); +floorIndexBuffer.Dispose(); +floorVertexBuffer.Dispose(); +``` + +The TLAS is released before the BLAS objects it instances. The sphere buffer remains live because ray-query shading reads it every frame. + +## Inspect the result + +Run the host and select **Ray Tracing** on a supported device. Confirm that: + +- the floor, three spheres, sky, shadows, and reflections are visible; +- the camera orbits continuously; +- resize recreates an output that fills the new framebuffer; +- validation reports no acceleration-structure lifetime, descriptor, or texture-layout errors. + +A sky-only image often indicates a missing TLAS handle or visibility-mask mismatch. Box-shaped procedural hits indicate that AABBs are being treated as bounds without committing the exact sphere intersection. A stale or blank image usually points to the output handle or `Sampled`/`Storage` transition sequence. + +## Explore further + +1. Return a constant color for each committed status to inspect traversal without the supporting shading functions. +2. Set `ShadowSamples` and `ReflectionSamples` to `1` and compare cost and image quality. +3. Disable reflections while leaving primary and shadow queries unchanged. +4. Move one TLAS instance with its transform instead of changing the source geometry. +5. Change a sphere radius and update both its structured-buffer record and AABB. + +## Complete source + +- [RayTracingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/RayTracingRenderer.cs) +- [RayTracing.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/RayTracing.slang) + +Continue with [Mesh Shading](mesh-shading.md) to explore another optional workload that replaces fixed vertex and index fetch with programmable geometry emission. diff --git a/documents/tutorials/guides/spinning-cube.md b/documents/tutorials/guides/spinning-cube.md new file mode 100644 index 00000000..a9c41c9e --- /dev/null +++ b/documents/tutorials/guides/spinning-cube.md @@ -0,0 +1,265 @@ +# Spinning Cube + +Spinning Cube extends the first graphics workload into three dimensions. It reuses vertex input and a graphics pipeline, then adds indexed geometry, model-view-projection constants, back-face culling, a depth attachment, animation, and size-dependent resource handling. + +The guide focuses on the Zenith.NET objects and their lifetime. Matrix construction uses `System.Numerics`; a full derivation of perspective projection is outside the workload boundary. + +## Result + +![A rotating depth-tested cube rendered with Zenith.NET](https://raw.githubusercontent.com/qian-o/ZenithTutorials/master/ZenithTutorials/Assets/Screenshots/spinning-cube.png) + +The cube rotates around two axes. Back faces are culled, and the depth attachment ensures that nearer fragments hide farther surfaces. + +## Frame overview + +Initialization uploads eight shared vertices and 36 indices, creates a constant buffer, and builds a depth-enabled pipeline. Per frame: + +```mermaid +flowchart LR + A[Update MVP constants] --> B{Depth texture exists?} + B -- No --> C[Create and transition depth texture] + B -- Yes --> D[Begin color and depth render pass] + C --> D + D --> E[Bind pipeline and buffers] + E --> F[DrawIndexed 36] + F --> G[End render pass] +``` + +On resize, the renderer disposes only the depth texture. The next frame recreates it with the new framebuffer dimensions. + +## Resource map + +| Resource | Usage | Lifetime | +| --- | --- | --- | +| Vertex buffer | Eight cube positions and colors | Renderer | +| Index buffer | Twelve triangles sharing those vertices | Renderer | +| Constant buffer | Model, view, and projection matrices | Renderer; updated each frame | +| Graphics pipeline | Depth-tested indexed drawing | Renderer | +| Depth texture | `D32FloatS8UInt` attachment | Recreated after resize | +| Swap-chain drawable | Color attachment | Host-owned | + +The depth format appears in both the pipeline attachment declaration and the texture description. Those values must agree. + +## Geometry and indexed drawing + +Eight vertices describe the unique corners of the cube. The index buffer selects them to build six faces, two triangles per face: + +```csharp +uint[] indices = +[ + 0, 1, 2, 0, 2, 3, + 5, 4, 7, 5, 7, 6, + 4, 0, 3, 4, 3, 7, + 1, 5, 6, 1, 6, 2, + 3, 2, 6, 3, 6, 7, + 4, 5, 1, 4, 1, 0 +]; + +vertexBuffer = App.LoadBuffer(vertices, BufferUsages.Vertex); +indexBuffer = App.LoadBuffer(indices, BufferUsages.Index); +``` + +Using indices avoids duplicating a complete position-and-color record for every triangle corner. The element type is `uint`, so the render method binds it as `IndexFormat.UInt32`. + +## Data contract + +The Slang constant buffer contains three consecutive matrices: + +```slang +struct Constants +{ + float4x4 Model; + + float4x4 View; + + float4x4 Projection; +}; + +ConstantBuffer constants; +``` + +The C# structure fixes each 64-byte matrix at the corresponding offset: + +```csharp +[StructLayout(LayoutKind.Explicit, Size = 192)] +file struct Constants +{ + [FieldOffset(0)] + public Matrix4x4 Model; + + [FieldOffset(64)] + public Matrix4x4 View; + + [FieldOffset(128)] + public Matrix4x4 Projection; +} +``` + +| Offset | C# | Slang | Size | +| ---: | --- | --- | ---: | +| 0 | `Matrix4x4 Model` | `float4x4 Model` | 64 bytes | +| 64 | `Matrix4x4 View` | `float4x4 View` | 64 bytes | +| 128 | `Matrix4x4 Projection` | `float4x4 Projection` | 64 bytes | + +Explicit offsets keep the shared binary contract visible. The total structure size is 192 bytes. + +## Transform vertices + +The vertex shader composes the three matrices and transforms each position: + +```slang +[shader("vertex")] +FSInput VSMain(VSInput input) +{ + float4x4 modelView = mul(constants.Model, constants.View); + float4x4 mvp = mul(modelView, constants.Projection); + + FSInput output; + output.Position = mul(float4(input.Position, 1.0), mvp); + output.Color = input.Color; + + return output; +} +``` + +`System.Numerics.Matrix4x4` and this shader use the same row-vector order: model, then view, then projection. The fragment shader simply returns the interpolated vertex color. + +## Enable depth and culling + +The graphics pipeline adds a depth format and enables both back-face culling and depth read/write: + +```csharp +pipeline = App.Context.CreateGraphicsPipeline(new() +{ + VertexShader = vertexShader, + FragmentShader = fragmentShader, + InputLayouts = [inputLayout], + PrimitiveTopology = PrimitiveTopology.TriangleList, + AttachmentFormats = new() + { + ColorFormats = [App.ColorFormat], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, + RenderState = new() + { + Rasterizer = RasterizerState.CullBack(), + DepthStencil = DepthStencilState.DepthReadWrite(), + Blend = BlendState.Opaque() + } +}); +``` + +Back-face culling removes triangles whose winding faces away from the camera. Depth testing compares overlapping fragments, while depth writes preserve the nearest accepted value for later triangles. + +## Update transformation constants + +`Update` advances a frame-rate-independent angle and constructs the three matrices: + +```csharp +rotationAngle += (float)deltaTime; + +Constants constants = new() +{ + Model = Matrix4x4.CreateRotationY(rotationAngle) * Matrix4x4.CreateRotationX(rotationAngle * 0.5f), + View = Matrix4x4.CreateLookAt(new(0.0f, 0.0f, 3.0f), Vector3.Zero, Vector3.UnitY), + Projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)App.Width / App.Height, 0.1f, 100.0f) +}; +``` + +The framebuffer aspect ratio keeps the projection proportional after a resize. Upload exactly one `Constants` value: + +```csharp +constantBuffer.Upload(0, new() +{ + Pointer = (nint)(&constants), + SizeInBytes = (uint)sizeof(Constants) +}); +``` + +The teaching host waits for each submitted frame, so overwriting this single constant buffer does not race a previous frame. A multi-frame application needs separate per-frame storage or equivalent synchronization. + +## Create the depth attachment + +The depth texture is created lazily after the window has a valid framebuffer size: + +```csharp +if (depthTexture is null) +{ + depthTexture = App.Context.CreateTexture(TextureDesc.DepthStencilAttachment(PixelFormat.D32FloatS8UInt, App.Width, App.Height, SampleCount.Count1)); + + commandBuffer.Transition(depthTexture, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment); +} +``` + +The first transition establishes `DepthStencilAttachment` layout. The texture remains in that layout while reused by later frames. + +## Record the frame + +Begin a render pass with both color and depth attachments, bind all inputs, and issue an indexed draw: + +```csharp +commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], DepthStencilAttachment.Clear(depthTexture, 1.0f, 0)); + +commandBuffer.SetPipeline(pipeline); +commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); +commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); +commandBuffer.SetConstantBuffer(constantBuffer, 0); + +commandBuffer.DrawIndexed(36, 1, 0, 0, 0); + +commandBuffer.EndRenderPass(); +``` + +Depth is cleared to `1.0` for each frame. `DrawIndexed` consumes all 36 indices for one instance, starting at index and vertex offsets zero. + +## Resize and lifetime + +The depth attachment dimensions must match the drawable. Dispose it on resize and recreate it during the next render: + +```csharp +public void Resize(uint width, uint height) +{ + depthTexture?.Dispose(); + depthTexture = null; +} +``` + +Release the optional depth texture first, then the pipeline and its input resources: + +```csharp +public void Dispose() +{ + depthTexture?.Dispose(); + + pipeline.Dispose(); + constantBuffer.Dispose(); + indexBuffer.Dispose(); + vertexBuffer.Dispose(); +} +``` + +## Inspect the result + +Run the host and select **Spinning Cube**. Confirm that: + +- the cube rotates smoothly and hidden surfaces do not bleed through; +- resizing changes the projection without stretching the cube; +- the depth texture is recreated after resize; +- the validation layer reports no attachment-format or layout errors. + +If faces disappear unexpectedly, inspect index winding and culling. If distant faces draw over near faces, confirm that the pipeline enables depth read/write and that a depth attachment is supplied to the render pass. + +## Explore further + +1. Replace `CullBack` with `CullNone` and compare the result. +2. Disable depth testing while leaving the depth attachment present. +3. Change the near and far projection planes and observe depth precision at different distances. +4. Give each face its own vertices and colors to see the tradeoff between shared indices and per-face attributes. + +## Complete source + +- [SpinningCubeRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/SpinningCubeRenderer.cs) +- [SpinningCube.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/SpinningCube.slang) + +Continue with [Indirect Drawing](indirect-drawing.md) to reuse this depth-tested cube for 25 instances driven by one indirect argument record, or branch to [Compute Shader](compute-shader.md) to produce an image without rasterized geometry. diff --git a/documents/tutorials/index.md b/documents/tutorials/index.md index 77947dd4..9d3b2af6 100644 --- a/documents/tutorials/index.md +++ b/documents/tutorials/index.md @@ -1,75 +1,36 @@ # Tutorials -Welcome to the Zenith.NET tutorials! These step-by-step guides will help you learn how to use Zenith.NET for GPU programming. +The Zenith.NET tutorials examine complete rendering workloads built with C# and the Slang shader language. Each guide focuses on the resources, pipelines, commands, data contracts, and lifetime rules that connect a workload to Zenith.NET. -## Getting Started +Begin with Project Setup and Application Host. The six guides then move from a first graphics pipeline to compute, indirect drawing, inline ray queries, and programmable geometry. -New to Zenith.NET? Start here to set up your environment and render your first graphics. - -| Tutorial | Description | -|----------|-------------| -| [Prerequisites](getting-started/prerequisites.md) | Set up your development environment with `App` framework, `IRenderer` interface, and `BindingHelper` | -| [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` | -| [Spinning Cube](getting-started/spinning-cube.md) | Use constant buffers for MVP matrices and render 3D geometry with depth testing | - -## Intermediate - -Build on the basics with GPU compute and advanced rendering techniques. - -| Tutorial | Description | -|----------|-------------| -| [Compute Shader](intermediate/compute-shader.md) | Create compute pipelines for GPU image processing (grayscale conversion) | -| [Indirect Drawing](intermediate/indirect-drawing.md) | GPU-driven rendering with `DrawIndexedIndirect` for multi-instance drawing | - -## Advanced - -Explore cutting-edge GPU features for modern rendering (requires hardware support). - -| Tutorial | Description | Requirement | -|----------|-------------|-------------| -| [Ray Tracing](advanced/ray-tracing.md) | Build acceleration structures (BLAS/TLAS), use `RayQuery` for ray tracing with soft shadows, reflections, and ACES tonemapping | `RayTracingSupported` | -| [Mesh Shading](advanced/mesh-shading.md) | Render 1,000 sphere instances with amplification shader frustum culling and mesh shading pipeline | `MeshShadingSupported` | - -## Tutorial Structure - -Each tutorial follows a consistent pattern: - -1. **Overview** - What you'll build and the key concepts covered -2. **Key Concepts** (advanced tutorials) - In-depth explanation of new API features -3. **Renderer Class** - Complete, runnable implementation code -4. **Running the Tutorial** - How to switch renderers and run the example -5. **Result** - Screenshot of the expected output -6. **Code Breakdown** - Step-by-step explanation of important code sections +> [!NOTE] +> The guides explain the Zenith.NET integration points in detail. Supporting graphics techniques such as sampling patterns, tone mapping, and procedural mesh generation are summarized when they are not essential to the API workflow. Their complete implementations remain available in the tutorial source. -All tutorials share the same `App` framework. Run `dotnet run` and select a tutorial from the interactive menu in `Program.cs`. +## Getting started -## Learning Path +- [Project Setup](getting-started/project-setup.md) creates the .NET 10 project and configures runtime assets. +- [Application Host](getting-started/application-host.md) follows the cross-platform window, swap chain, frame loop, and offscreen texture presenter. -| Stage | You Will Learn | -|-------|----------------| -| **Prerequisites** | Set up the application framework, graphics context, and cross-platform 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 | -| **Compute Shader** | Run general-purpose GPU computations for image processing | -| **Indirect Drawing** | Let the GPU control draw parameters for efficient multi-instance rendering | -| **Ray Tracing** | Build acceleration structures, trace rays with `RayQuery`, implement soft shadows, reflections, Fresnel, and ACES tonemapping | -| **Mesh Shading** | Use amplification shaders for GPU-driven frustum culling with mesh shading at scale (1,000 instances) | +## Guides -## Requirements +| Guide | Result | Focus | +| --- | --- | --- | +| [Hello Triangle](guides/hello-triangle.md) | A vertex-colored triangle | Vertex input, graphics pipeline, render pass, and draw command | +| [Spinning Cube](guides/spinning-cube.md) | A rotating depth-tested cube | Indexed geometry, constant buffers, transforms, depth, and resize | +| [Compute Shader](guides/compute-shader.md) | A grayscale image | Storage textures, descriptor handles, dispatch, and layout transitions | +| [Indirect Drawing](guides/indirect-drawing.md) | A grid of animated cubes | Structured instance data and indirect draw arguments | +| [Ray Tracing](guides/ray-tracing.md) | Procedural spheres above a floor | Acceleration structures, inline ray queries, and compute output | +| [Mesh Shading](guides/mesh-shading.md) | A culled sphere grid | Task and mesh shaders, payload compaction, and mesh dispatch | -Before starting, ensure you have: +The complete, runnable implementations are maintained in the [ZenithTutorials repository](https://github.com/qian-o/ZenithTutorials). Code shown in these pages is static so it remains readable and searchable with the rest of the documentation; each guide links its complete renderer and shader at the end. -- .NET 10.0 SDK or later -- A GPU with DirectX 12, Metal 4, or Vulkan 1.4 support -- Visual Studio 2026, VS Code, or JetBrains Rider +## Suggested paths -> [!NOTE] -> These tutorials are designed for desktop platforms (Windows, macOS, and Linux). -> See [Prerequisites](getting-started/prerequisites.md) for detailed platform support and setup instructions. +Complete [Hello Triangle](guides/hello-triangle.md) first. From there: -## Source Code +- continue with [Spinning Cube](guides/spinning-cube.md) and [Indirect Drawing](guides/indirect-drawing.md) for the graphics path; +- continue with [Compute Shader](guides/compute-shader.md) and [Ray Tracing](guides/ray-tracing.md) for compute-produced images; +- read [Mesh Shading](guides/mesh-shading.md) after Spinning Cube when you are comfortable with graphics pipelines, depth attachments, and GPU thread groups. -> [!TIP] -> The complete source code for all tutorials is available on GitHub: [ZenithTutorials](https://github.com/qian-o/ZenithTutorials) +Ray tracing and mesh shading are optional device capabilities. Their guides show how to test support before creating workload-specific resources. diff --git a/documents/tutorials/intermediate/compute-shader.md b/documents/tutorials/intermediate/compute-shader.md deleted file mode 100644 index 8d3be520..00000000 --- a/documents/tutorials/intermediate/compute-shader.md +++ /dev/null @@ -1,280 +0,0 @@ -# Compute Shader - -In this tutorial, you'll use a compute pipeline to process an image on the GPU — converting it from color to grayscale. This introduces compute shaders, read/write textures, and dispatching work groups. - -## Overview - -This tutorial covers: - -- Creating a **compute pipeline** with thread group configuration -- Using `Texture2D` (read-only) and `RWTexture2D` (read-write) resources -- **Dispatching** compute work groups based on texture dimensions -- Performing **linearize → grayscale → gamma** color conversion -- Copying the processed texture to the frame buffer with centered placement - -## The Renderer Class - -Create the file `Renderers/ComputeShaderRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal class ComputeShaderRenderer : IRenderer -{ - private const uint ThreadGroupSize = 16; - - private const string ShaderSource = """ - 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]; - - float3 linear = pow(color.rgb, 2.2); - float gray = dot(linear, float3(0.2126, 0.7152, 0.0722)); - gray = pow(gray, 1.0 / 2.2); - - outputTexture[dispatchThreadID.xy] = float4(gray, gray, gray, color.a); - } - """; - - private readonly Texture inputTexture; - private readonly Texture outputTexture; - private readonly ResourceLayout resourceLayout; - private readonly ResourceTable resourceTable; - private readonly ComputePipeline pipeline; - - private bool processed; - - public ComputeShaderRenderer() - { - inputTexture = App.Context.LoadTextureFromFile(Path.Combine(AppContext.BaseDirectory, "Assets", "shoko.png"), generateMipMaps: false); - - outputTexture = App.Context.CreateTexture(new() - { - Type = TextureType.Texture2D, - Format = PixelFormat.B8G8R8A8UNorm, - Width = inputTexture.Desc.Width, - Height = inputTexture.Desc.Height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - 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] - }); - - using Shader computeShader = App.Context.LoadShaderFromSource(ShaderSource, "CSMain", ShaderStageFlags.Compute); - - pipeline = App.Context.CreateComputePipeline(new() - { - Compute = computeShader, - ResourceLayout = resourceLayout, - ThreadGroupSizeX = ThreadGroupSize, - ThreadGroupSizeY = ThreadGroupSize, - ThreadGroupSizeZ = 1 - }); - } - - public void Update(double deltaTime) - { - } - - public void Render() - { - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - if (!processed) - { - uint dispatchX = (inputTexture.Desc.Width + ThreadGroupSize - 1) / ThreadGroupSize; - uint dispatchY = (inputTexture.Desc.Height + ThreadGroupSize - 1) / ThreadGroupSize; - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - commandBuffer.Dispatch(dispatchX, dispatchY, 1); - - processed = true; - } - - Texture colorTarget = App.FrameBuffer.Desc.ColorAttachments[0].Target; - - uint copyWidth = Math.Min(outputTexture.Desc.Width, App.Width); - uint copyHeight = Math.Min(outputTexture.Desc.Height, App.Height); - - uint srcX = (outputTexture.Desc.Width - copyWidth) / 2; - uint srcY = (outputTexture.Desc.Height - copyHeight) / 2; - uint destX = (App.Width - copyWidth) / 2; - uint destY = (App.Height - copyHeight) / 2; - - commandBuffer.CopyTexture(outputTexture, - default, - new() { X = srcX, Y = srcY, Z = 0 }, - colorTarget, - default, - new() { X = destX, Y = destY, Z = 0 }, - new() { Width = copyWidth, Height = copyHeight, Depth = 1 }); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - } - - public void Dispose() - { - pipeline.Dispose(); - resourceTable.Dispose(); - resourceLayout.Dispose(); - outputTexture.Dispose(); - inputTexture.Dispose(); - } -} -``` - -## Running the Tutorial - -Run the application and select **4. Compute Shader** from the menu: - -```bash -dotnet run -``` - -## Result - -![Compute Shader](../../images/compute-shader.png) - -## Code Breakdown - -### Shader - -The compute shader processes each pixel independently in 16×16 thread groups: - -```csharp -private const string ShaderSource = """ - 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]; - - float3 linear = pow(color.rgb, 2.2); - float gray = dot(linear, float3(0.2126, 0.7152, 0.0722)); - gray = pow(gray, 1.0 / 2.2); - - outputTexture[dispatchThreadID.xy] = float4(gray, gray, gray, color.a); - } - """; -``` - -The grayscale conversion follows three steps: - -1. **Linearize**: `pow(color.rgb, 2.2)` removes sRGB gamma -2. **Luminance**: `dot(linear, float3(0.2126, 0.7152, 0.0722))` computes perceptual brightness using Rec. 709 coefficients -3. **Re-encode**: `pow(gray, 1.0 / 2.2)` applies gamma correction - -### Compute Pipeline - -Unlike the graphics pipeline, a compute pipeline has no vertex/pixel stages or render states: - -```csharp -pipeline = App.Context.CreateComputePipeline(new() -{ - Compute = computeShader, - ResourceLayout = resourceLayout, - ThreadGroupSizeX = ThreadGroupSize, - ThreadGroupSizeY = ThreadGroupSize, - ThreadGroupSizeZ = 1 -}); -``` - -The thread group size (16×16×1) defines how many threads run per group. This must match the `[numthreads]` attribute in the shader. - -### Output Texture - -The output texture is created with `UnorderedAccess` to allow compute shader writes: - -```csharp -outputTexture = App.Context.CreateTexture(new() -{ - Type = TextureType.Texture2D, - Format = PixelFormat.B8G8R8A8UNorm, - Width = inputTexture.Desc.Width, - Height = inputTexture.Desc.Height, - Depth = 1, - MipLevels = 1, - ArrayLayers = 1, - SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.ShaderResource | TextureUsageFlags.UnorderedAccess -}); -``` - -| Flag | Purpose | -|------|---------| -| `ShaderResource` | Can be read as `Texture2D` in shaders | -| `UnorderedAccess` | Can be written as `RWTexture2D` in compute shaders | - -### Dispatch and Copy - -The compute shader runs once, then the result is copied centered to the frame buffer each frame: - -```csharp -if (!processed) -{ - uint dispatchX = (inputTexture.Desc.Width + ThreadGroupSize - 1) / ThreadGroupSize; - uint dispatchY = (inputTexture.Desc.Height + ThreadGroupSize - 1) / ThreadGroupSize; - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - commandBuffer.Dispatch(dispatchX, dispatchY, 1); - - processed = true; -} -``` - -The dispatch count is computed as `ceil(dimension / threadGroupSize)` to ensure all pixels are covered. - -The `CopyTexture` call copies the result centered within the swap chain's color target, handling cases where the image and window have different sizes. - -## Next Steps - -- [Indirect Drawing](indirect-drawing.md) - Draw multiple instances with GPU-driven indirect commands - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [ComputeShaderRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/ComputeShaderRenderer.cs) diff --git a/documents/tutorials/intermediate/indirect-drawing.md b/documents/tutorials/intermediate/indirect-drawing.md deleted file mode 100644 index f2f2380c..00000000 --- a/documents/tutorials/intermediate/indirect-drawing.md +++ /dev/null @@ -1,433 +0,0 @@ -# Indirect Drawing - -In this tutorial, you'll render a 5×5 grid of spinning cubes using indirect drawing and GPU instancing. This introduces indirect draw buffers, structured buffers for per-instance data, and shows how to drive draw calls from GPU-accessible memory. - -## Overview - -This tutorial covers: - -- Creating an **indirect draw buffer** with `IndirectDrawIndexedArgs` -- Using a **structured buffer** to store per-instance transforms and colors -- Updating instance data per-frame for independent animations -- Issuing a single `DrawIndexedIndirect` call to render all instances -- Setting up view/projection matrices in `Resize` for window-independent rendering - -## The Renderer Class - -Create the file `Renderers/IndirectDrawingRenderer.cs`: - -```csharp -namespace ZenithTutorials.Renderers; - -internal unsafe class IndirectDrawingRenderer : IRenderer -{ - private const int InstanceCount = 25; - - private const string ShaderSource = """ - struct VSInput - { - float3 Position : POSITION0; - - float4 Color : COLOR0; - - uint InstanceID : SV_InstanceID; - }; - - struct PSInput - { - float4 Position : SV_POSITION; - - float4 Color : COLOR; - }; - - struct Constants - { - float4x4 View; - - float4x4 Projection; - }; - - struct Instance - { - float4x4 Model; - - float4 Color; - }; - - ConstantBuffer constants; - StructuredBuffer instances; - - PSInput VSMain(VSInput input) - { - Instance instance = instances[input.InstanceID]; - - float4 worldPos = mul(float4(input.Position, 1.0), instance.Model); - float4 viewPos = mul(worldPos, constants.View); - - PSInput output; - output.Position = mul(viewPos, constants.Projection); - output.Color = input.Color * instance.Color; - - return output; - } - - float4 PSMain(PSInput input) : SV_TARGET - { - return input.Color; - } - """; - - 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; - - private float rotationAngle; - - public IndirectDrawingRenderer() - { - Vertex[] vertices = - [ - new(new(-0.5f, -0.5f, 0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new( 0.5f, -0.5f, 0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new( 0.5f, 0.5f, 0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new(-0.5f, 0.5f, 0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new(-0.5f, -0.5f, -0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new( 0.5f, -0.5f, -0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new( 0.5f, 0.5f, -0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)), - new(new(-0.5f, 0.5f, -0.5f), new(1.0f, 1.0f, 1.0f, 1.0f)) - ]; - - uint[] indices = - [ - 0, 1, 2, 0, 2, 3, - 5, 4, 7, 5, 7, 6, - 4, 0, 3, 4, 3, 7, - 1, 5, 6, 1, 6, 2, - 3, 2, 6, 3, 6, 7, - 4, 5, 1, 4, 1, 0 - ]; - - vertexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), - StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.Vertex | BufferUsageFlags.MapWrite - }); - vertexBuffer.Upload(vertices, 0); - - indexBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(uint) * indices.Length), - StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.Index | BufferUsageFlags.MapWrite - }); - indexBuffer.Upload(indices, 0); - - indirectBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(IndirectDrawIndexedArgs), - StrideInBytes = (uint)sizeof(IndirectDrawIndexedArgs), - Flags = BufferUsageFlags.Indirect | BufferUsageFlags.MapWrite - }); - - indirectBuffer.Upload([new IndirectDrawIndexedArgs() - { - IndexCount = (uint)indices.Length, - InstanceCount = InstanceCount, - FirstIndex = 0, - VertexOffset = 0, - FirstInstance = 0 - }], 0); - - constantsBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite - }); - Resize(App.Width, App.Height); - - instanceBuffer = App.Context.CreateBuffer(new() - { - SizeInBytes = (uint)(sizeof(Instance) * InstanceCount), - StrideInBytes = (uint)sizeof(Instance), - 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] - }); - - InputLayout inputLayout = new(); - inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position }); - inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color }); - - using Shader vertexShader = App.Context.LoadShaderFromSource(ShaderSource, "VSMain", ShaderStageFlags.Vertex); - using Shader pixelShader = App.Context.LoadShaderFromSource(ShaderSource, "PSMain", ShaderStageFlags.Pixel); - - pipeline = App.Context.CreateGraphicsPipeline(new() - { - RenderStates = new() - { - RasterizerState = RasterizerStates.CullBack, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceLayout = resourceLayout, - InputLayouts = [inputLayout], - PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = App.FrameBuffer.Output - }); - } - - public void Update(double deltaTime) - { - rotationAngle += (float)deltaTime; - - Instance[] instances = new Instance[InstanceCount]; - - int index = 0; - int gridSize = (int)Math.Sqrt(InstanceCount); - - for (int y = 0; y < gridSize; y++) - { - for (int x = 0; x < gridSize; x++) - { - float offsetX = (x - (gridSize / 2)) * 1.5f; - float offsetY = (y - (gridSize / 2)) * 1.5f; - float rotation = rotationAngle * (1.0f + (index * 0.1f)); - - instances[index] = new() - { - Model = Matrix4x4.CreateScale(0.4f) - * Matrix4x4.CreateRotationY(rotation) - * Matrix4x4.CreateRotationX(rotation * 0.5f) - * Matrix4x4.CreateTranslation(offsetX, offsetY, 0), - Color = new((float)x / gridSize, (float)y / gridSize, 1.0f - ((float)x / gridSize), 1.0f) - }; - - index++; - } - } - - instanceBuffer.Upload(instances, 0); - } - - public void Render() - { - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); - - commandBuffer.BeginRenderPass(App.FrameBuffer, new() - { - ColorValues = [new(0.1f, 0.1f, 0.15f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }, resourceTable); - - commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); - commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); - commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32); - commandBuffer.DrawIndexedIndirect(indirectBuffer, 0, 1); - - commandBuffer.EndRenderPass(); - - commandBuffer.Submit(waitForCompletion: true); - } - - public void Resize(uint width, uint height) - { - Matrix4x4 view = Matrix4x4.CreateLookAt(new(0, 0, 8), Vector3.Zero, Vector3.UnitY); - Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)width / height, 0.1f, 100.0f); - - constantsBuffer.Upload([new Constants() { View = view, Projection = projection }], 0); - } - - public void Dispose() - { - pipeline.Dispose(); - resourceTable.Dispose(); - resourceLayout.Dispose(); - instanceBuffer.Dispose(); - constantsBuffer.Dispose(); - indirectBuffer.Dispose(); - indexBuffer.Dispose(); - vertexBuffer.Dispose(); - } -} - -[StructLayout(LayoutKind.Sequential)] -file struct Vertex(Vector3 position, Vector4 color) -{ - public Vector3 Position = position; - - public Vector4 Color = color; -} - -[StructLayout(LayoutKind.Explicit, Size = 128)] -file struct Constants -{ - [FieldOffset(0)] - public Matrix4x4 View; - - [FieldOffset(64)] - public Matrix4x4 Projection; -} - -[StructLayout(LayoutKind.Explicit, Size = 80)] -file struct Instance -{ - [FieldOffset(0)] - public Matrix4x4 Model; - - [FieldOffset(64)] - public Vector4 Color; -} -``` - -## Running the Tutorial - -Run the application and select **5. Indirect Drawing** from the menu: - -```bash -dotnet run -``` - -## Result - -![Indirect Drawing](../../images/indirect-drawing.png) - -## Code Breakdown - -### Shader - -The vertex shader reads per-instance data from a `StructuredBuffer`: - -```csharp -ConstantBuffer constants; -StructuredBuffer instances; - -PSInput VSMain(VSInput input) -{ - Instance instance = instances[input.InstanceID]; - - float4 worldPos = mul(float4(input.Position, 1.0), instance.Model); - float4 viewPos = mul(worldPos, constants.View); - - PSInput output; - output.Position = mul(viewPos, constants.Projection); - output.Color = input.Color * instance.Color; - - return output; -} -``` - -`SV_InstanceID` provides the instance index, used to look up the per-instance model matrix and color from the structured buffer. - -### Indirect Draw Buffer - -The draw arguments are stored in a GPU buffer instead of being passed as CPU parameters: - -```csharp -indirectBuffer = App.Context.CreateBuffer(new() -{ - SizeInBytes = (uint)sizeof(IndirectDrawIndexedArgs), - StrideInBytes = (uint)sizeof(IndirectDrawIndexedArgs), - Flags = BufferUsageFlags.Indirect | BufferUsageFlags.MapWrite -}); - -indirectBuffer.Upload([new IndirectDrawIndexedArgs() -{ - IndexCount = (uint)indices.Length, - InstanceCount = InstanceCount, - FirstIndex = 0, - VertexOffset = 0, - FirstInstance = 0 -}], 0); -``` - -`IndirectDrawIndexedArgs` mirrors the standard GPU indirect draw structure. Using `DrawIndexedIndirect` instead of `DrawIndexed` allows the GPU to read draw parameters from a buffer, enabling GPU-driven rendering scenarios. - -### Structured Buffer - -Per-instance data (model matrix + color) is uploaded to a structured buffer each frame: - -```csharp -instanceBuffer = App.Context.CreateBuffer(new() -{ - SizeInBytes = (uint)(sizeof(Instance) * InstanceCount), - StrideInBytes = (uint)sizeof(Instance), - Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.MapWrite -}); -``` - -The `Instance` struct is 80 bytes — a 64-byte `Matrix4x4` plus a 16-byte `Vector4`: - -```csharp -[StructLayout(LayoutKind.Explicit, Size = 80)] -file struct Instance -{ - [FieldOffset(0)] - public Matrix4x4 Model; - - [FieldOffset(64)] - public Vector4 Color; -} -``` - -### Per-Instance Animation - -Each cube gets a unique rotation speed and color based on its grid position: - -```csharp -instances[index] = new() -{ - Model = Matrix4x4.CreateScale(0.4f) - * Matrix4x4.CreateRotationY(rotation) - * Matrix4x4.CreateRotationX(rotation * 0.5f) - * Matrix4x4.CreateTranslation(offsetX, offsetY, 0), - Color = new((float)x / gridSize, (float)y / gridSize, 1.0f - ((float)x / gridSize), 1.0f) -}; -``` - -### View/Projection in Resize - -View and projection matrices are set in `Resize` rather than `Update`, since the camera is static and only the aspect ratio changes: - -```csharp -public void Resize(uint width, uint height) -{ - Matrix4x4 view = Matrix4x4.CreateLookAt(new(0, 0, 8), Vector3.Zero, Vector3.UnitY); - Matrix4x4 projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)width / height, 0.1f, 100.0f); - - constantsBuffer.Upload([new Constants() { View = view, Projection = projection }], 0); -} -``` - -## Next Steps - -- [Ray Tracing](../advanced/ray-tracing.md) - Cast rays with hardware acceleration structures - -## Source Code - -> [!TIP] -> View the complete source code on GitHub: [IndirectDrawingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/IndirectDrawingRenderer.cs) diff --git a/documents/tutorials/toc.yml b/documents/tutorials/toc.yml index 38fb6a6a..0616191a 100644 --- a/documents/tutorials/toc.yml +++ b/documents/tutorials/toc.yml @@ -1,24 +1,24 @@ - name: Tutorials href: index.md + - name: Getting Started items: - - name: Prerequisites - href: getting-started/prerequisites.md + - name: Project Setup + href: getting-started/project-setup.md + - name: Application Host + href: getting-started/application-host.md + +- name: Guides + items: - name: Hello Triangle - href: getting-started/hello-triangle.md - - name: Textured Quad - href: getting-started/textured-quad.md + href: guides/hello-triangle.md - name: Spinning Cube - href: getting-started/spinning-cube.md -- name: Intermediate - items: + href: guides/spinning-cube.md - name: Compute Shader - href: intermediate/compute-shader.md + href: guides/compute-shader.md - name: Indirect Drawing - href: intermediate/indirect-drawing.md -- name: Advanced - items: + href: guides/indirect-drawing.md - name: Ray Tracing - href: advanced/ray-tracing.md + href: guides/ray-tracing.md - name: Mesh Shading - href: advanced/mesh-shading.md + href: guides/mesh-shading.md diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 8a77753e..44203098 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,23 +5,23 @@ - + - - - + + + - + - - - - + + + + \ No newline at end of file diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 8646e411..2097c79d 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 { @@ -72,9 +72,19 @@ 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.FrameBuffer.Output); - camera = new(input, Matrix4x4.CreateTranslation(278f, 273f, -800f)) + 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, FarPlane = 2000.0f @@ -117,52 +127,45 @@ 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.SetNextWindowPos(new(10, 10), ImGuiCond.FirstUseEver); - if (ImGui.Begin("Cornell Box", ImGuiWindowFlags.AlwaysAutoResize)) - { - ImGui.Text($"Backend: {Context.Backend}"); - ImGui.Text(Context.Capabilities.DeviceName); + ImGui.GetBackgroundDrawList().AddImage(imGui.Binding(activeRenderer.Color), new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y)); - ImGui.Separator(); + ImGuiHelper.Overlay(() => + { + ImGui.Text(Context.Capabilities.DeviceName); + ImGui.Text($"GraphicsApi: {Context.GraphicsApi}"); + ImGui.Text($"FPS: {ImGui.GetIO().Framerate:F1}"); + }); - ImGui.Text("Render Mode:"); + ImGuiHelper.Settings(() => + { + 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; - } + currentMode = 0; + activeRenderer = pathTracer!; - ImGui.SameLine(); + pathTracer!.FrameCount = 0; } - 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); + }); }; window.Render += _ => @@ -172,13 +175,18 @@ public static void Run() return; } - CommandBuffer commandBuffer = Context.Graphics.CommandBuffer(); + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); + activeRenderer.Update(camera); activeRenderer.Render(commandBuffer); - imGui.Render(commandBuffer, swapChain.FrameBuffer, ClearValues.Default); + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); - commandBuffer.Submit(true); + imGui.Render(commandBuffer, ColorAttachment.DontCare(swapChain.Drawable)); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit().Wait(); swapChain.Present(); }; @@ -205,7 +213,5 @@ public static void Run() window.Dispose(); Context.Dispose(); - - Console.WriteLine("Exited cleanly."); } } diff --git a/sources/Experiments/CornellBox/Assets/Shaders/.clang-format b/sources/Experiments/CornellBox/Assets/Shaders/.clang-format index c6996ed2..ff17eaf4 100644 --- a/sources/Experiments/CornellBox/Assets/Shaders/.clang-format +++ b/sources/Experiments/CornellBox/Assets/Shaders/.clang-format @@ -4,13 +4,14 @@ BasedOnStyle: Microsoft # Indentation IndentWidth: 4 +ContinuationIndentWidth: 4 TabWidth: 4 UseTab: Never IndentCaseLabels: true -AccessModifierOffset: -4 +NamespaceIndentation: All -# Braces - Allman style -BreakBeforeBraces: Allman +# Brace placement +BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true @@ -19,64 +20,27 @@ BraceWrapping: AfterFunction: true AfterNamespace: true AfterStruct: true - AfterUnion: true BeforeCatch: true BeforeElse: true + BeforeWhile: false IndentBraces: false + SplitEmptyFunction: true + SplitEmptyNamespace: true + SplitEmptyRecord: true -# Column limit -ColumnLimit: 120 - -# Line breaking -BreakBeforeBinaryOperators: None -BreakBeforeTernaryOperators: false -AlwaysBreakAfterReturnType: None - -# Alignment -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: None -AlignConsecutiveDeclarations: None -AlignOperands: Align -AlignTrailingComments: false - -# Function arguments and parameters - one per line when wrapped -BinPackArguments: false -BinPackParameters: true -AllowAllArgumentsOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: false - -# Short statements +# Block bodies AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false -# Spaces -SpaceAfterCStyleCast: false -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 1 -SpacesInAngles: false -SpacesInParentheses: false -SpacesInSquareBrackets: false - -# Empty lines +# Line wrapping +ColumnLimit: 0 KeepEmptyLinesAtTheStartOfBlocks: false MaxEmptyLinesToKeep: 1 - -# Other -NamespaceIndentation: All -PointerAlignment: Left -SortIncludes: false -SortUsingDeclarations: false ReflowComments: false -# Penalty settings -PenaltyBreakAssignment: 1000000 -PenaltyBreakBeforeFirstCallParameter: 500 -PenaltyBreakComment: 300 -PenaltyBreakString: 1000 -PenaltyExcessCharacter: 1 -PenaltyReturnTypeOnItsOwnLine: 1000000 \ No newline at end of file +# Declaration order +SortIncludes: false +SortUsingDeclarations: false \ No newline at end of file diff --git a/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang b/sources/Experiments/CornellBox/Assets/Shaders/PathTracing.slang index c171acc2..4ea5ad30 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); } } }; @@ -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> AccumulationTexture; + + 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; +ConstantBuffer 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; @@ -432,51 +461,52 @@ float halton(uint index, uint base) return result; } +[shader("compute")] [numthreads(16, 16, 1)] 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 accumulated; - if (camera.FrameCount == 0) + if (pathTracing.FrameCount == 0) { accumulated = float4(color, 1.0); } else { + float4 prev = pathTracing.AccumulationTexture[pixel]; accumulated = prev + float4(color, 1.0); } - accumTexture[pixel] = accumulated; + pathTracing.AccumulationTexture[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 +514,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..1e32c157 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; @@ -33,16 +43,18 @@ struct RasterConstants float4x4 Projection; - private float4 LightPosAndPadding; + private float4 LightPositionAndPadding; private float4 LightColorAndPadding; - private float4 CameraPosAndPadding; + private float4 CameraPositionAndPadding; - property float3 LightPos + DescriptorHandle> Materials; + + property float3 LightPosition { get { - return LightPosAndPadding.xyz; + return LightPositionAndPadding.xyz; } } @@ -53,10 +65,10 @@ struct RasterConstants } } - property float3 CameraPos + property float3 CameraPosition { get { - return CameraPosAndPadding.xyz; + return CameraPositionAndPadding.xyz; } } }; @@ -65,7 +77,7 @@ struct VSInput { private float4 PositionAndPadding : POSITION0; - private float4 NormalAndMaterialID : NORMAL0; + private float4 NormalAndMaterialId : NORMAL0; property float3 Position { @@ -77,19 +89,19 @@ 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); } } }; -struct PSInput +struct FSInput { float4 Position : SV_POSITION; @@ -97,20 +109,17 @@ struct PSInput float3 Normal : TEXCOORD1; - nointerpolation uint MaterialID : TEXCOORD2; + nointerpolation uint MaterialId : TEXCOORD2; }; -ConstantBuffer raster; -StructuredBuffer materials; - -static const float PI = 3.14159265; +ConstantBuffer 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) @@ -140,35 +151,37 @@ float3 FresnelSchlick(float cosTheta, float3 F0) return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0); } -PSInput VSMain(VSInput input) +[shader("vertex")] +FSInput 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); + FSInput output; + 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.MaterialID = input.MaterialID; + output.Normal = normalize(mul(float4(input.Normal, 0.0), rasterization.Model).xyz); + output.MaterialId = input.MaterialId; return output; } -float4 PSMain(PSInput input) : SV_TARGET +[shader("fragment")] +float4 FSMain(FSInput 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.CameraPosition - worldPos); + float3 toLight = rasterization.LightPosition - worldPos; float dist = length(toLight); float3 L = toLight / dist; float3 H = normalize(L + V); @@ -192,14 +205,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/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/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; 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/Helpers/CocoaHelper.cs b/sources/Experiments/CornellBox/Helpers/CocoaHelper.cs index acb3879d..5b0d97ae 100644 --- a/sources/Experiments/CornellBox/Helpers/CocoaHelper.cs +++ b/sources/Experiments/CornellBox/Helpers/CocoaHelper.cs @@ -24,7 +24,6 @@ internal static partial class CocoaHelper public static nint CreateLayer(nint cocoa) { nint layer = Send(GetClass("CAMetalLayer"), Selector("layer")); - Send(layer, Selector("retain")); nint view = Send(cocoa, Selector("contentView")); Send(view, Selector("setWantsLayer:"), true); diff --git a/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs b/sources/Experiments/CornellBox/Helpers/CornellBoxGeometry.cs index 0d378131..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 + } ]; } @@ -97,16 +133,39 @@ 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 +186,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/Helpers/ImGuiHelper.cs b/sources/Experiments/CornellBox/Helpers/ImGuiHelper.cs new file mode 100644 index 00000000..16d808c0 --- /dev/null +++ b/sources/Experiments/CornellBox/Helpers/ImGuiHelper.cs @@ -0,0 +1,32 @@ +using Hexa.NET.ImGui; + +namespace CornellBox.Helpers; + +internal static class ImGuiHelper +{ + public static void Overlay(Action action) + { + ImGui.SetNextWindowPos(new(10, 10), ImGuiCond.Always, new(0, 0)); + ImGui.SetNextWindowBgAlpha(0.35f); + + if (ImGui.Begin("Overlay", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoDecoration)) + { + action(); + } + + ImGui.End(); + } + + public static void Settings(Action action) + { + ImGui.SetNextWindowPos(new(ImGui.GetIO().DisplaySize.X - 10, 10), ImGuiCond.Always, new(1, 0)); + ImGui.SetNextWindowCollapsed(true, ImGuiCond.FirstUseEver); + + if (ImGui.Begin("Settings", ImGuiWindowFlags.AlwaysAutoResize)) + { + action(); + } + + ImGui.End(); + } +} diff --git a/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs b/sources/Experiments/CornellBox/Renderers/PathTracingRenderer.cs index d75ec775..666e4af9 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; @@ -14,15 +13,13 @@ internal unsafe class PathTracingRenderer : Renderer 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 Buffer constantBuffer; private readonly ComputePipeline pipeline; + private readonly BottomLevelAccelerationStructure blas; + private readonly TopLevelAccelerationStructure tlas; + private readonly Buffer materialBuffer; private Texture? accumulationTexture; - private ResourceTable? resourceTable; private Matrix4x4 lastView; private Matrix4x4 lastProjection; @@ -35,34 +32,48 @@ public PathTracingRenderer() { SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length), StrideInBytes = (uint)sizeof(Vertex), - Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly }); - 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() { SizeInBytes = (uint)(sizeof(uint) * indices.Length), StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.ShaderResource | BufferUsageFlags.AccelerationStructure + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly }); - indexBuffer.Upload(indices, 0); - materialBuffer = App.Context.CreateBuffer(new() + fixed (uint* pointer = indices) { - SizeInBytes = (uint)(sizeof(Material) * materials.Length), - StrideInBytes = (uint)sizeof(Material), - Flags = BufferUsageFlags.ShaderResource - }); - materialBuffer.Upload(materials, 0); + indexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(uint) * indices.Length) + }); + } - cameraBuffer = App.Context.CreateBuffer(new() + constantBuffer = App.Context.CreateBuffer(new() { - SizeInBytes = (uint)sizeof(CameraParams), - StrideInBytes = (uint)sizeof(CameraParams), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite + SizeInBytes = (uint)sizeof(PathTracingConstants), + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly }); - CommandBuffer commandBuffer = App.Context.Graphics.CommandBuffer(); + using Shader computeShader = App.Context.CreateShader(ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath("PathTracing.slang"), "CSMain")); + + pipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader }); + + CommandBuffer commandBuffer = App.Context.ComputeQueue.CommandBuffer(); blas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc { @@ -70,8 +81,8 @@ public PathTracingRenderer() [ new() { - Type = RayTracingGeometryType.Triangles, - Triangles = new() + Type = RayTracingGeometryType.Triangle, + TriangleGeometry = new() { VertexBuffer = vertexBuffer, VertexFormat = PixelFormat.R32G32B32Float, @@ -82,10 +93,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 @@ -95,41 +106,33 @@ 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(); - resourceLayout = App.Context.CreateResourceLayout(new() + materialBuffer = App.Context.CreateBuffer(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 } - ) + SizeInBytes = (uint)(sizeof(Material) * materials.Length), + StrideInBytes = (uint)sizeof(Material), + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly }); - using Shader computeShader = App.Context.LoadShaderFromFile(ShaderPath("PathTracing.slang"), "CSMain", ShaderStageFlags.Compute); - - pipeline = App.Context.CreateComputePipeline(new() + fixed (Material* pointer = materials) { - Compute = computeShader, - ResourceLayout = resourceLayout, - ThreadGroupSizeX = ThreadGroupSize, - ThreadGroupSizeY = ThreadGroupSize, - ThreadGroupSizeZ = 1 - }); + materialBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Material) * materials.Length) + }); + } } public uint FrameCount { get; set; } @@ -150,46 +153,45 @@ public override void Update(CameraHandler camera) Matrix4x4.Invert(view, out Matrix4x4 invView); Matrix4x4.Invert(projection, out Matrix4x4 invProjection); - cameraBuffer.Upload([new() + PathTracingConstants parameters = new() { InvView = invView, InvProjection = invProjection, Position = camera.Position, FrameCount = FrameCount, Width = App.Width, - Height = App.Height - }], 0); + Height = App.Height, + Scene = tlas.Handle, + Vertices = vertexBuffer.StorageReadOnlyHandle, + Indices = indexBuffer.StorageReadOnlyHandle, + Materials = materialBuffer.StorageReadOnlyHandle, + AccumulationTexture = accumulationTexture!.StorageHandle, + OutputTexture = Color.StorageHandle + }; + + 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 - }); + commandBuffer.Transition(Color, default, TextureLayout.Undefined, TextureLayout.Storage); - resourceTable = App.Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [tlas, cameraBuffer, vertexBuffer, indexBuffer, materialBuffer, accumulationTexture, Color] - }); + if (FrameCount is 0) + { + commandBuffer.Transition(accumulationTexture!, default, TextureLayout.Undefined, TextureLayout.Storage); } commandBuffer.SetPipeline(pipeline); - commandBuffer.SetResourceTable(resourceTable); + commandBuffer.SetConstantBuffer(constantBuffer, 0); commandBuffer.Dispatch((App.Width + ThreadGroupSize - 1) / ThreadGroupSize, (App.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1); + commandBuffer.Transition(Color, default, TextureLayout.Storage, TextureLayout.Sampled); + FrameCount++; } @@ -197,35 +199,40 @@ 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; } public override void Dispose() { - base.Dispose(); - - resourceTable?.Dispose(); - accumulationTexture?.Dispose(); - - pipeline.Dispose(); - resourceLayout.Dispose(); + materialBuffer.Dispose(); tlas.Dispose(); blas.Dispose(); - cameraBuffer.Dispose(); - materialBuffer.Dispose(); + pipeline.Dispose(); + constantBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); + accumulationTexture?.Dispose(); + + base.Dispose(); } } -[StructLayout(LayoutKind.Explicit, Size = 160)] -file struct CameraParams +[StructLayout(LayoutKind.Explicit, Size = 208)] +file struct PathTracingConstants { [FieldOffset(0)] public Matrix4x4 InvView; @@ -244,4 +251,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 AccumulationTexture; + + [FieldOffset(200)] + public ResourceHandle OutputTexture; } diff --git a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs index ce9a8cc4..05ac34db 100644 --- a/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs +++ b/sources/Experiments/CornellBox/Renderers/RasterizationRenderer.cs @@ -3,146 +3,152 @@ 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 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 ResourceLayout resourceLayout; - 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.Upload(vertices, 0); + vertexBuffer = App.Context.CreateBuffer(BufferDesc.Vertex((uint)(sizeof(Vertex) * vertices.Length))); - indexBuffer = App.Context.CreateBuffer(new() + fixed (Vertex* pointer = vertices) { - SizeInBytes = (uint)(sizeof(uint) * indices.Length), - StrideInBytes = sizeof(uint), - Flags = BufferUsageFlags.Index - }); - indexBuffer.Upload(indices, 0); + vertexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length) + }); + } + + indexBuffer = App.Context.CreateBuffer(BufferDesc.Index((uint)(sizeof(uint) * indices.Length))); - materialBuffer = App.Context.CreateBuffer(new() + fixed (uint* pointer = indices) { - SizeInBytes = (uint)(sizeof(Material) * materials.Length), - StrideInBytes = (uint)sizeof(Material), - Flags = BufferUsageFlags.ShaderResource - }); - materialBuffer.Upload(materials, 0); + indexBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(uint) * indices.Length) + }); + } constantBuffer = App.Context.CreateBuffer(new() { - SizeInBytes = (uint)sizeof(RasterConstants), - StrideInBytes = (uint)sizeof(RasterConstants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite + SizeInBytes = (uint)sizeof(RasterizationConstants), + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly }); - resourceLayout = App.Context.CreateResourceLayout(new() + InputLayout inputLayout = new(); + inputLayout.Add(new() { - Bindings = BindingHelper.Bindings - ( - new() { Type = ResourceType.ConstantBuffer, Count = 1, StageFlags = ShaderStageFlags.Vertex | ShaderStageFlags.Pixel }, - new() { Type = ResourceType.StructuredBuffer, Count = 1, StageFlags = ShaderStageFlags.Pixel } - ) + Format = ElementFormat.Float4, + Semantic = ElementSemantic.Position }); - resourceTable = App.Context.CreateResourceTable(new() + inputLayout.Add(new() { - Layout = resourceLayout, - Resources = [constantBuffer, materialBuffer] + Format = ElementFormat.Float4, + Semantic = ElementSemantic.Normal }); - 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.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() { - RenderStates = new() - { - RasterizerState = RasterizerStates.CullNone, - DepthStencilState = DepthStencilStates.Default, - BlendState = BlendStates.Opaque - }, - Vertex = vertexShader, - Pixel = pixelShader, - ResourceLayout = resourceLayout, + VertexShader = vertexShader, + FragmentShader = fragmentShader, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = FrameBuffer.Output + AttachmentFormats = AttachmentFormats, + RenderState = new() + { + Rasterizer = RasterizerState.CullNone(), + DepthStencil = DepthStencilState.DepthReadWrite(), + Blend = BlendState.Opaque() + } }); + + 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) { - constantBuffer.Upload([new() + RasterizationConstants parameters = new() { 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 - }], 0); + CameraPosition = camera.Position, + Materials = materialBuffer.StorageReadOnlyHandle + }; + + constantBuffer.Upload(0, new() + { + Pointer = (nint)(¶meters), + SizeInBytes = (uint)sizeof(RasterizationConstants) + }); } public override void Render(CommandBuffer commandBuffer) { - commandBuffer.BeginRenderPass(FrameBuffer, new() - { - ColorValues = [new(0.51f, 0.518f, 0.557f, 1.0f)], - Depth = 1.0f, - Stencil = 0, - Flags = ClearFlags.All - }, resourceTable); + commandBuffer.Transition(Color, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.Transition(DepthStencil, default, TextureLayout.Undefined, 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); - commandBuffer.SetResourceTable(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(); + + commandBuffer.Transition(Color, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); } public override void Dispose() { - base.Dispose(); - + materialBuffer.Dispose(); pipeline.Dispose(); - resourceTable.Dispose(); - resourceLayout.Dispose(); constantBuffer.Dispose(); - materialBuffer.Dispose(); indexBuffer.Dispose(); vertexBuffer.Dispose(); + + base.Dispose(); } } -[StructLayout(LayoutKind.Explicit, Size = 240)] -file struct RasterConstants +[StructLayout(LayoutKind.Explicit, Size = 256)] +file struct RasterizationConstants { [FieldOffset(0)] public Matrix4x4 Model; @@ -154,11 +160,14 @@ file struct RasterConstants 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; } diff --git a/sources/Experiments/CornellBox/Renderers/Renderer.cs b/sources/Experiments/CornellBox/Renderers/Renderer.cs index 4dd9bd99..9c6b52ee 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 AttachmentFormats AttachmentFormats => new() + { + ColorFormats = [Color.Desc.Format], + DepthStencilFormat = 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(); @@ -36,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() @@ -49,19 +53,12 @@ public virtual void Resize(uint width, uint height) MipLevels = 1, ArrayLayers = 1, SampleCount = SampleCount.Count1, - Flags = TextureUsageFlags.DepthStencil - }); - - FrameBuffer = App.Context.CreateFrameBuffer(new() - { - ColorAttachments = [new() { Target = Color }], - DepthStencilAttachment = new() { Target = DepthStencil } + Usages = TextureUsages.DepthStencilAttachment }); } public virtual void Dispose() { - FrameBuffer.Dispose(); DepthStencil.Dispose(); Color.Dispose(); } diff --git a/sources/Experiments/FluidTank/App.cs b/sources/Experiments/FluidTank/App.cs new file mode 100644 index 00000000..876c53fd --- /dev/null +++ b/sources/Experiments/FluidTank/App.cs @@ -0,0 +1,282 @@ +using System.Numerics; +using FluidTank.Handlers; +using FluidTank.Helpers; +using Hexa.NET.ImGui; +using Silk.NET.Input; +using Silk.NET.Windowing; +using Zenith.NET; +using Zenith.NET.DirectX12; +using Zenith.NET.Metal; +using Zenith.NET.Vulkan; + +namespace FluidTank; + +internal static class App +{ + private static readonly IWindow window; + private static readonly IInputContext input; + private static readonly SwapChain swapChain; + private static readonly ImGuiHandler imGui; + private static readonly CameraHandler camera; + private static readonly FluidTankRenderer renderer; + + static App() + { + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsLinux()) + { + throw new PlatformNotSupportedException("This application only supports Windows, macOS, and Linux."); + } + + if (OperatingSystem.IsWindows()) + { + Context = GraphicsContext.CreateDirectX12(useValidationLayer: true); + } + else if (OperatingSystem.IsMacOS()) + { + Context = GraphicsContext.CreateMetal(useValidationLayer: true); + } + else + { + Context = GraphicsContext.CreateVulkan(useValidationLayer: true); + } + + Context.ValidationMessage += static (sender, args) => Console.WriteLine($"[{args.Severity}] {args.Message}"); + + window = Window.Create(WindowOptions.Default with + { + API = GraphicsAPI.None, + Title = "Fluid Tank - Zenith.NET", + Size = new(1280, 720) + }); + window.Initialize(); + window.Center(); + + input = window.CreateInput(); + + Surface surface; + if (OperatingSystem.IsWindows()) + { + surface = Surface.Win32(window.Native!.Win32!.Value.Hwnd, Width, Height); + } + else if (OperatingSystem.IsMacOS()) + { + surface = Surface.Apple(CocoaHelper.CreateLayer(window.Native!.Cocoa!.Value), Width, Height); + } + else + { + if (window.Native?.X11 is not { } x11) + { + throw new PlatformNotSupportedException("FluidTank requires an X11 or XWayland window on Linux."); + } + + surface = Surface.Xlib(x11.Display, (nint)x11.Window, Width, Height); + } + + swapChain = Context.CreateSwapChain(new() + { + Surface = surface, + Format = PixelFormat.B8G8R8A8UNorm + }); + + imGui = new(input, new() + { + ColorFormats = [PixelFormat.B8G8R8A8UNorm], + SampleCount = SampleCount.Count1 + }); + + camera = new(input, new(9.2f, 5.3f, -10.8f), new(0.0f, 1.45f, 0.0f)) + { + Speed = 4.0f, + NearPlane = 0.05f, + FarPlane = 80.0f, + Fov = 48.0f + }; + + renderer = new(); + } + + public static GraphicsContext Context { get; } + + public static uint Width => (uint)window.FramebufferSize.X; + + public static uint Height => (uint)window.FramebufferSize.Y; + + public static Vector2 DpiScale => (Vector2)window.FramebufferSize / (Vector2)window.Size; + + public static void Run() + { + window.Update += delta => + { + if (Width is 0 || Height is 0) + { + return; + } + + uint width = (uint)(Width / DpiScale.X); + uint height = (uint)(Height / DpiScale.Y); + + imGui.Update(delta, width, height); + camera.Update(delta, width, height); + renderer.Update(camera, delta); + + if (camera.TryConsumeClickRay(out Vector3 origin, out Vector3 direction) && !ImGui.GetIO().WantCaptureMouse) + { + renderer.PushFluid(origin, direction); + } + + ImGui.GetBackgroundDrawList().AddImage(imGui.Binding(renderer.Color), new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y)); + + ImGuiHelper.Overlay(() => + { + ImGui.Text(Context.Capabilities.DeviceName); + ImGui.Text($"GraphicsApi: {Context.GraphicsApi}"); + ImGui.Text($"FPS: {ImGui.GetIO().Framerate:F1}"); + ImGui.Text($"Particles: {renderer.ParticleCount:N0}"); + }); + + ImGuiHelper.Settings(() => + { + ImGui.Text("Run"); + + bool paused = renderer.Paused; + if (ImGui.Checkbox("Pause", ref paused)) + { + renderer.Paused = paused; + } + + ImGui.SameLine(); + + if (ImGui.Button("Reset dam")) + { + renderer.Reset(); + } + + ImGui.Separator(); + + ImGui.Text("Motion"); + + bool waveMakerEnabled = renderer.WaveMakerEnabled; + if (ImGui.Checkbox("Wave maker", ref waveMakerEnabled)) + { + renderer.WaveMakerEnabled = waveMakerEnabled; + } + + ImGui.BeginDisabled(!waveMakerEnabled); + + float waveAmplitude = renderer.WaveAmplitude; + if (ImGui.SliderFloat("Wave amplitude", ref waveAmplitude, 0.0f, 0.34f, "%.2f m")) + { + renderer.WaveAmplitude = waveAmplitude; + } + + float waveFrequency = renderer.WaveFrequency; + if (ImGui.SliderFloat("Wave frequency", ref waveFrequency, 0.2f, 2.5f, "%.2f Hz")) + { + renderer.WaveFrequency = waveFrequency; + } + + ImGui.EndDisabled(); + + ImGui.Separator(); + + ImGui.Text("Display"); + + if (ImGui.RadioButton("Water", renderer.ViewMode == FluidViewMode.Water)) + { + renderer.ViewMode = FluidViewMode.Water; + } + + ImGui.SameLine(); + + if (ImGui.RadioButton("Particles", renderer.ViewMode == FluidViewMode.Particles)) + { + renderer.ViewMode = FluidViewMode.Particles; + } + + if (renderer.ViewMode is FluidViewMode.Water) + { + float clarity = renderer.Clarity; + if (ImGui.SliderFloat("Clarity", ref clarity, 0.25f, 2.0f, "%.2f")) + { + renderer.Clarity = clarity; + } + + float refraction = renderer.RefractionStrength; + if (ImGui.SliderFloat("Refraction", ref refraction, 0.0f, 1.5f, "%.2f")) + { + renderer.RefractionStrength = refraction; + } + } + + if (ImGui.CollapsingHeader("Advanced simulation")) + { + float flipRatio = renderer.FlipRatio; + if (ImGui.SliderFloat("FLIP ratio", ref flipRatio, 0.0f, 1.0f, "%.2f")) + { + renderer.FlipRatio = flipRatio; + } + + int pressureIterations = renderer.PressureIterations; + if (ImGui.SliderInt("Pressure iterations", ref pressureIterations, 4, 32)) + { + renderer.PressureIterations = pressureIterations; + } + + float velocityDamping = renderer.VelocityDamping; + if (ImGui.SliderFloat("Velocity damping", ref velocityDamping, 0.97f, 1.0f, "%.3f")) + { + renderer.VelocityDamping = velocityDamping; + } + + ImGui.Text("Solver: APIC / FLIP"); + } + }); + }; + + window.Render += _ => + { + if (Width is 0 || Height is 0) + { + return; + } + + TimelineValue simulationReady = renderer.Simulate(); + + CommandBuffer sceneCommandBuffer = Context.GraphicsQueue.CommandBuffer(); + renderer.RenderScene(sceneCommandBuffer); + sceneCommandBuffer.Submit(); + + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); + renderer.RenderFluid(commandBuffer); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + imGui.Render(commandBuffer, ColorAttachment.DontCare(swapChain.Drawable)); + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit(simulationReady).Wait(); + + swapChain.Present(); + }; + + window.Resize += _ => + { + if (Width is 0 || Height is 0) + { + return; + } + + renderer.Resize(Width, Height); + swapChain.Resize(Width, Height); + }; + + window.Run(); + + renderer.Dispose(); + imGui.Dispose(); + swapChain.Dispose(); + input.Dispose(); + window.Dispose(); + + Context.Dispose(); + } +} diff --git a/sources/Experiments/FluidTank/Assets/Fonts/msyh.ttf b/sources/Experiments/FluidTank/Assets/Fonts/msyh.ttf new file mode 100644 index 00000000..2bf27863 Binary files /dev/null and b/sources/Experiments/FluidTank/Assets/Fonts/msyh.ttf differ diff --git a/sources/Experiments/FluidTank/Assets/Shaders/.clang-format b/sources/Experiments/FluidTank/Assets/Shaders/.clang-format new file mode 100644 index 00000000..ff17eaf4 --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/.clang-format @@ -0,0 +1,46 @@ +--- +Language: CSharp +BasedOnStyle: Microsoft + +# Indentation +IndentWidth: 4 +ContinuationIndentWidth: 4 +TabWidth: 4 +UseTab: Never +IndentCaseLabels: true +NamespaceIndentation: All + +# Brace placement +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + BeforeCatch: true + BeforeElse: true + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyNamespace: true + SplitEmptyRecord: true + +# Block bodies +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false + +# Line wrapping +ColumnLimit: 0 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +ReflowComments: false + +# Declaration order +SortIncludes: false +SortUsingDeclarations: false \ No newline at end of file diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidBlur.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidBlur.slang new file mode 100644 index 00000000..d5e9e035 --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidBlur.slang @@ -0,0 +1,189 @@ +struct BlurConstants +{ + private uint4 DimensionsAndDirection; + + private float4 FilterParameters; + + DescriptorHandle> InputTexture; + + DescriptorHandle> OutputTexture; + + property uint Width + { + get { + return DimensionsAndDirection.x; + } + } + + property uint Height + { + get { + return DimensionsAndDirection.y; + } + } + + property uint Direction + { + get { + return DimensionsAndDirection.z; + } + } + + property uint Radius + { + get { + return DimensionsAndDirection.w; + } + } + + property float SpatialSigma + { + get { + return FilterParameters.x; + } + } + + property float DepthSigma + { + get { + return FilterParameters.y; + } + } + + property float ProjectedRadiusScale + { + get { + return FilterParameters.z; + } + } +}; + +ConstantBuffer blur; + +[shader("compute")] +[numthreads(16, 16, 1)] +void BlurCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadID.xy; + if (pixel.x >= blur.Width || pixel.y >= blur.Height) + { + return; + } + + float centerDepth = blur.InputTexture.Load(int3(pixel, 0)); + if (centerDepth <= 0.0) + { + int2 direction = blur.Direction == 0 ? int2(1, 0) : int2(0, 1); + int bridgeRadius = min(int(blur.Radius), 4); + float negativeDepth = 0.0; + float positiveDepth = 0.0; + int negativeDistance = 0; + int positiveDistance = 0; + + for (int offset = 1; offset <= bridgeRadius; offset++) + { + int2 negativePixel = clamp(int2(pixel) - direction * offset, int2(0, 0), int2(int(blur.Width) - 1, int(blur.Height) - 1)); + int2 positivePixel = clamp(int2(pixel) + direction * offset, int2(0, 0), int2(int(blur.Width) - 1, int(blur.Height) - 1)); + + if (negativeDepth <= 0.0) + { + negativeDepth = blur.InputTexture.Load(int3(negativePixel, 0)); + negativeDistance = negativeDepth > 0.0 ? offset : 0; + } + + if (positiveDepth <= 0.0) + { + positiveDepth = blur.InputTexture.Load(int3(positivePixel, 0)); + positiveDistance = positiveDepth > 0.0 ? offset : 0; + } + + if (negativeDepth > 0.0 && positiveDepth > 0.0) + { + break; + } + } + + float depthDifference = abs(negativeDepth - positiveDepth); + float bridgeRange = blur.DepthSigma * (0.85 + min(negativeDepth, positiveDepth) * 0.015) * 2.5; + + if (negativeDepth > 0.0 && positiveDepth > 0.0 && depthDifference < bridgeRange) + { + float distanceSum = float(negativeDistance + positiveDistance); + blur.OutputTexture[pixel] = (negativeDepth * positiveDistance + positiveDepth * negativeDistance) / distanceSum; + + return; + } + + blur.OutputTexture[pixel] = 0.0; + + return; + } + + float weightedDepth = centerDepth; + float weightSum = 1.0; + int2 direction = blur.Direction == 0 ? int2(1, 0) : int2(0, 1); + float projectedRadius = blur.ProjectedRadiusScale / max(centerDepth, 0.1); + int localRadius = clamp(int(projectedRadius * 1.35 + 0.5), 4, int(blur.Radius)); + float spatialSigma = max(blur.SpatialSigma, float(localRadius) * 0.58); + float range = blur.DepthSigma * (0.85 + centerDepth * 0.015); + + for (int offset = 1; offset <= localRadius; offset++) + { + float spatialWeight = exp(-float(offset * offset) / (2.0 * spatialSigma * spatialSigma)); + + for (int sign = -1; sign <= 1; sign += 2) + { + int2 samplePixel = clamp(int2(pixel) + direction * offset * sign, int2(0, 0), int2(int(blur.Width) - 1, int(blur.Height) - 1)); + float sampleDepth = blur.InputTexture.Load(int3(samplePixel, 0)); + + if (sampleDepth <= 0.0) + { + continue; + } + + float difference = abs(sampleDepth - centerDepth); + + if (difference > range * 2.5) + { + continue; + } + + float depthWeight = exp(-(difference * difference) / (2.0 * range * range)); + float weight = spatialWeight * depthWeight; + + weightedDepth += sampleDepth * weight; + weightSum += weight; + } + } + + blur.OutputTexture[pixel] = weightedDepth / weightSum; +} + +[shader("compute")] +[numthreads(16, 16, 1)] +void BlurThicknessCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadID.xy; + if (pixel.x >= blur.Width || pixel.y >= blur.Height) + { + return; + } + + float weightedThickness = blur.InputTexture.Load(int3(pixel, 0)); + float weightSum = 1.0; + int2 direction = blur.Direction == 0 ? int2(1, 0) : int2(0, 1); + + for (int offset = 1; offset <= int(blur.Radius); offset++) + { + float weight = exp(-float(offset * offset) / (2.0 * blur.SpatialSigma * blur.SpatialSigma)); + + for (int sign = -1; sign <= 1; sign += 2) + { + int2 samplePixel = clamp(int2(pixel) + direction * offset * sign, int2(0, 0), int2(int(blur.Width) - 1, int(blur.Height) - 1)); + weightedThickness += blur.InputTexture.Load(int3(samplePixel, 0)) * weight; + weightSum += weight; + } + } + + blur.OutputTexture[pixel] = weightedThickness / weightSum; +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidCommon.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidCommon.slang new file mode 100644 index 00000000..da4fa9d4 --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidCommon.slang @@ -0,0 +1,29 @@ +static const float PI = 3.14159265359; + +struct Particle +{ + float4 PositionDensity; + + float4 VelocityLambda; +}; + +float3 ParticlePosition(Particle particle) +{ + return particle.PositionDensity.xyz; +} + +float3 ParticleVelocity(Particle particle) +{ + return particle.VelocityLambda.xyz; +} + +float Hash11(uint value) +{ + value ^= value >> 16; + value *= 0x7feb352du; + value ^= value >> 15; + value *= 0x846ca68bu; + value ^= value >> 16; + + return float(value) / 4294967295.0; +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang new file mode 100644 index 00000000..be78fb65 --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang @@ -0,0 +1,270 @@ +#include "SceneCommon.slang" + +struct CompositeConstants +{ + float4x4 InvView; + + float4x4 InvProjection; + + private float4 CameraPositionAndTime; + + private float4 SunDirectionAndClarity; + + private float4 WaterColorAndRefraction; + + private float4 AbsorptionAndIor; + + private uint4 ScreenAndRenderMode; + + DescriptorHandle SceneColor; + + DescriptorHandle SceneDepth; + + DescriptorHandle FluidDepth; + + DescriptorHandle Thickness; + + DescriptorHandle Attributes; + + DescriptorHandle Reflection; + + DescriptorHandle Sampler; + + property float3 CameraPosition + { + get { + return CameraPositionAndTime.xyz; + } + } + + property float Time + { + get { + return CameraPositionAndTime.w; + } + } + + property float3 SunDirection + { + get { + return SunDirectionAndClarity.xyz; + } + } + + property float Clarity + { + get { + return SunDirectionAndClarity.w; + } + } + + property float3 WaterColor + { + get { + return WaterColorAndRefraction.xyz; + } + } + + property float RefractionStrength + { + get { + return WaterColorAndRefraction.w; + } + } + + property float3 Absorption + { + get { + return AbsorptionAndIor.xyz; + } + } + + property float Ior + { + get { + return AbsorptionAndIor.w; + } + } + + property uint Width + { + get { + return ScreenAndRenderMode.x; + } + } + + property uint Height + { + get { + return ScreenAndRenderMode.y; + } + } + + property uint RenderMode + { + get { + return ScreenAndRenderMode.z; + } + } + + property bool RayTracingEnabled + { + get { + return ScreenAndRenderMode.w != 0; + } + } +}; + +struct FullscreenOutput +{ + float4 Position : SV_POSITION; + + float2 UV : TEXCOORD0; +}; + +ConstantBuffer composite; + +[shader("vertex")] +FullscreenOutput FullscreenVS(uint vertexId: SV_VertexID) +{ + float2 uv = float2((vertexId << 1) & 2, vertexId & 2); + + FullscreenOutput output; + output.Position = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + output.UV = uv; + + return output; +} + +float3 ViewRay(float2 uv) +{ + float2 ndc = uv * 2.0 - 1.0; + ndc.y = -ndc.y; + float4 target = mul(float4(ndc, 1.0, 1.0), composite.InvProjection); + + return normalize(target.xyz / target.w); +} + +float3 ReconstructViewPosition(float2 uv, float linearDepth) +{ + float3 ray = ViewRay(uv); + + return ray * linearDepth / max(-ray.z, 0.0001); +} + +float3 ReconstructWorldPosition(float2 uv, float linearDepth) +{ + return mul(float4(ReconstructViewPosition(uv, linearDepth), 1.0), composite.InvView).xyz; +} + +float3 ReconstructNormal(float2 uv, float centerDepth) +{ + float2 texel = 5.0 / float2(composite.Width, composite.Height); + float depthLeft = composite.FluidDepth.SampleLevel(composite.Sampler, uv - float2(texel.x, 0.0), 0.0).r; + float depthRight = composite.FluidDepth.SampleLevel(composite.Sampler, uv + float2(texel.x, 0.0), 0.0).r; + float depthUp = composite.FluidDepth.SampleLevel(composite.Sampler, uv - float2(0.0, texel.y), 0.0).r; + float depthDown = composite.FluidDepth.SampleLevel(composite.Sampler, uv + float2(0.0, texel.y), 0.0).r; + + depthLeft = depthLeft > 0.0 && abs(depthLeft - centerDepth) < 0.75 ? depthLeft : centerDepth; + depthRight = depthRight > 0.0 && abs(depthRight - centerDepth) < 0.75 ? depthRight : centerDepth; + depthUp = depthUp > 0.0 && abs(depthUp - centerDepth) < 0.75 ? depthUp : centerDepth; + depthDown = depthDown > 0.0 && abs(depthDown - centerDepth) < 0.75 ? depthDown : centerDepth; + + float3 left = ReconstructViewPosition(uv - float2(texel.x, 0.0), depthLeft); + float3 right = ReconstructViewPosition(uv + float2(texel.x, 0.0), depthRight); + float3 up = ReconstructViewPosition(uv - float2(0.0, texel.y), depthUp); + float3 down = ReconstructViewPosition(uv + float2(0.0, texel.y), depthDown); + float3 normal = normalize(cross(down - up, right - left)); + + if (normal.z < 0.0) + { + normal = -normal; + } + + return normal; +} + +float3 SceneAt(float2 uv) +{ + float depth = composite.SceneDepth.SampleLevel(composite.Sampler, uv, 0.0).r; + if (depth <= 0.0) + { + float3 rayView = ViewRay(uv); + float3 rayWorld = normalize(mul(float4(rayView, 0.0), composite.InvView).xyz); + + return SkyColor(rayWorld); + } + + return composite.SceneColor.SampleLevel(composite.Sampler, uv, 0.0).rgb; +} + +float4 ShadeWater(FullscreenOutput input) +{ + float2 uv = input.UV; + + if (composite.RenderMode == 1) + { + return float4(ToSRGB(ACESFilm(SceneAt(uv))), 1.0); + } + + float fluidDepth = composite.FluidDepth.SampleLevel(composite.Sampler, uv, 0.0).r; + + if (fluidDepth <= 0.0) + { + float3 background = SceneAt(uv); + + return float4(ToSRGB(ACESFilm(background)), 1.0); + } + + float thickness = composite.Thickness.SampleLevel(composite.Sampler, uv, 0.0).r; + float4 attributes = composite.Attributes.SampleLevel(composite.Sampler, uv, 0.0); + float surfaceConfidence = smoothstep(0.105, 0.135, thickness); + + if (surfaceConfidence <= 0.01) + { + return float4(ToSRGB(ACESFilm(SceneAt(uv))), 1.0); + } + + float3 normalView = ReconstructNormal(uv, fluidDepth); + float3 normalWorld = normalize(mul(float4(normalView, 0.0), composite.InvView).xyz); + + float3 worldPosition = ReconstructWorldPosition(uv, fluidDepth); + float3 viewDirection = normalize(composite.CameraPosition - worldPosition); + float ndotv = saturate(dot(normalWorld, viewDirection)); + float f0 = pow((composite.Ior - 1.0) / (composite.Ior + 1.0), 2.0); + float fresnel = f0 + (1.0 - f0) * pow(1.0 - ndotv, 5.0); + + float distortionScale = composite.RefractionStrength * (0.008 + min(thickness, 3.0) * 0.0035) / (0.35 + fluidDepth * 0.055); + float2 refractedUv = clamp(uv + normalView.xy * distortionScale, 0.001, 0.999); + + float3 reflected = composite.RayTracingEnabled ? composite.Reflection.SampleLevel(composite.Sampler, uv, 0.0).rgb : SkyColor(reflect(-viewDirection, normalWorld)); + float3 refracted = SceneAt(refractedUv); + + float opticalDepth = min(thickness * 0.82, 6.0); + float3 transmission = exp(-composite.Absorption * opticalDepth / max(composite.Clarity, 0.05)); + float3 inScatter = composite.WaterColor * (1.0 - transmission) * (0.34 + 0.26 * saturate(normalWorld.y)); + refracted = refracted * transmission + inScatter; + + float3 lightDirection = normalize(-composite.SunDirection); + float3 halfVector = normalize(viewDirection + lightDirection); + float sunSpecular = pow(saturate(dot(normalWorld, halfVector)), 240.0) * 3.2; + + float densityEdge = saturate((0.68 - attributes.y) * 3.0); + float speedFoam = saturate((attributes.x - 0.28) * 1.8); + float thinFoam = saturate((0.22 - thickness) * 5.0); + float foam = saturate(speedFoam * thinFoam * 0.72 + densityEdge * speedFoam * 0.22); + + float3 color = lerp(refracted, reflected, fresnel); + color += sunSpecular * float3(1.0, 0.94, 0.78); + color = lerp(color, float3(0.76, 0.92, 0.96), foam * 0.72); + + color = lerp(SceneAt(uv), color, surfaceConfidence); + + return float4(ToSRGB(ACESFilm(color)), 1.0); +} + +[shader("fragment")] +float4 CompositeFS(FullscreenOutput input) : SV_TARGET +{ + return ShadeWater(input); +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidReflection.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidReflection.slang new file mode 100644 index 00000000..d46c804f --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidReflection.slang @@ -0,0 +1,185 @@ +#include "SceneCommon.slang" + +struct ReflectionConstants +{ + float4x4 InvView; + + float4x4 InvProjection; + + private float4 CameraPositionAndTime; + + private float4 SunDirectionAndPadding; + + private uint4 DimensionsAndPadding; + + DescriptorHandle> FluidDepth; + + DescriptorHandle Scene; + + DescriptorHandle> Vertices; + + DescriptorHandle> Indices; + + DescriptorHandle> Materials; + + DescriptorHandle> OutputTexture; + + property float3 CameraPosition + { + get { + return CameraPositionAndTime.xyz; + } + } + + property float Time + { + get { + return CameraPositionAndTime.w; + } + } + + property float3 SunDirection + { + get { + return SunDirectionAndPadding.xyz; + } + } + + property uint Width + { + get { + return DimensionsAndPadding.x; + } + } + + property uint Height + { + get { + return DimensionsAndPadding.y; + } + } +}; + +ConstantBuffer reflection; + +float3 ViewRay(float2 uv) +{ + float2 ndc = uv * 2.0 - 1.0; + ndc.y = -ndc.y; + float4 target = mul(float4(ndc, 1.0, 1.0), reflection.InvProjection); + + return normalize(target.xyz / target.w); +} + +float3 ViewPosition(float2 uv, float linearDepth) +{ + float3 ray = ViewRay(uv); + + return ray * linearDepth / max(-ray.z, 0.0001); +} + +float3 WorldPosition(float2 uv, float linearDepth) +{ + return mul(float4(ViewPosition(uv, linearDepth), 1.0), reflection.InvView).xyz; +} + +float3 SurfaceNormal(uint2 pixel, float depth) +{ + const uint Radius = 5; + uint2 leftPixel = uint2(pixel.x > Radius ? pixel.x - Radius : 0, pixel.y); + uint2 rightPixel = uint2(min(pixel.x + Radius, reflection.Width - 1), pixel.y); + uint2 upPixel = uint2(pixel.x, pixel.y > Radius ? pixel.y - Radius : 0); + uint2 downPixel = uint2(pixel.x, min(pixel.y + Radius, reflection.Height - 1)); + float depthLeft = reflection.FluidDepth.Load(int3(leftPixel, 0)); + float depthRight = reflection.FluidDepth.Load(int3(rightPixel, 0)); + float depthUp = reflection.FluidDepth.Load(int3(upPixel, 0)); + float depthDown = reflection.FluidDepth.Load(int3(downPixel, 0)); + + depthLeft = depthLeft > 0.0 && abs(depthLeft - depth) < 0.75 ? depthLeft : depth; + depthRight = depthRight > 0.0 && abs(depthRight - depth) < 0.75 ? depthRight : depth; + depthUp = depthUp > 0.0 && abs(depthUp - depth) < 0.75 ? depthUp : depth; + depthDown = depthDown > 0.0 && abs(depthDown - depth) < 0.75 ? depthDown : depth; + + float2 dimensions = float2(reflection.Width, reflection.Height); + float3 left = ViewPosition((float2(leftPixel) + 0.5) / dimensions, depthLeft); + float3 right = ViewPosition((float2(rightPixel) + 0.5) / dimensions, depthRight); + float3 up = ViewPosition((float2(upPixel) + 0.5) / dimensions, depthUp); + float3 down = ViewPosition((float2(downPixel) + 0.5) / dimensions, depthDown); + float3 normalVector = cross(down - up, right - left); + float normalLengthSquared = dot(normalVector, normalVector); + float3 normal = normalLengthSquared > 0.0 ? normalVector * rsqrt(normalLengthSquared) : float3(0.0, 0.0, 1.0); + + if (normal.z < 0.0) + { + normal = -normal; + } + + return normalize(mul(float4(normal, 0.0), reflection.InvView).xyz); +} + +float3 TraceScene(float3 origin, float3 direction) +{ + RayDesc ray; + ray.Origin = origin; + ray.Direction = direction; + ray.TMin = 0.02; + ray.TMax = 120.0; + + RayQuery query; + query.TraceRayInline(reflection.Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES, 0xFF, ray); + while (query.Proceed()) + { + } + + if (query.CommittedStatus() == COMMITTED_NOTHING) + { + return SkyColor(direction); + } + + uint primitive = query.CommittedPrimitiveIndex(); + float2 barycentric = query.CommittedTriangleBarycentrics(); + uint i0 = reflection.Indices[primitive * 3 + 0]; + uint i1 = reflection.Indices[primitive * 3 + 1]; + uint i2 = reflection.Indices[primitive * 3 + 2]; + SceneVertex v0 = reflection.Vertices[i0]; + SceneVertex v1 = reflection.Vertices[i1]; + SceneVertex v2 = reflection.Vertices[i2]; + float3 weights = float3(1.0 - barycentric.x - barycentric.y, barycentric.x, barycentric.y); + float3 normal = normalize(v0.Normal * weights.x + v1.Normal * weights.y + v2.Normal * weights.z); + SceneMaterial material = reflection.Materials[v0.MaterialId]; + float3 lightDirection = normalize(-reflection.SunDirection); + float diffuse = 0.18 + saturate(dot(normal, lightDirection)) * 1.65; + float3 environment = SkyColor(reflect(direction, normal)); + float3 f0 = lerp(float3(0.035), material.Albedo, material.Metallic); + float3 fresnel = FresnelSchlick(saturate(dot(-direction, normal)), f0); + + return material.Albedo * diffuse * (1.0 - material.Metallic) + environment * fresnel * (1.0 - material.Roughness * 0.7); +} + +[shader("compute")] +[numthreads(16, 16, 1)] +void ReflectionCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint2 pixel = dispatchThreadID.xy; + if (pixel.x >= reflection.Width || pixel.y >= reflection.Height) + { + return; + } + + float depth = reflection.FluidDepth.Load(int3(pixel, 0)); + if (depth <= 0.0) + { + reflection.OutputTexture[pixel] = float4(0.0, 0.0, 0.0, 0.0); + + return; + } + + float2 uv = (float2(pixel) + 0.5) / float2(reflection.Width, reflection.Height); + float3 position = WorldPosition(uv, depth); + float3 normal = SurfaceNormal(pixel, depth); + float3 viewDirection = normalize(reflection.CameraPosition - position); + float3 direction = reflect(-viewDirection, normal); + float3 color = TraceScene(position + normal * 0.025, direction); + + reflection.OutputTexture[pixel] = float4(color, 1.0); +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidSimulation.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidSimulation.slang new file mode 100644 index 00000000..d9893e6c --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidSimulation.slang @@ -0,0 +1,1000 @@ +#include "FluidCommon.slang" + +static const uint CellAir = 0; +static const uint CellFluid = 1; +static const uint CellSolid = 2; +static const float AccumulationScale = 65536.0; +static const float ParticleSpacingScale = 1.67; + +struct SimulationConstants +{ + private float4 TankMinAndTimeStep; + + private float4 TankMaxAndTime; + + private float4 GridParameters; + + private float4 WaveAndInteraction; + + private float4 InteractionOriginAndRadius; + + private float4 InteractionDirectionAndDensity; + + private uint4 Counts; + + private uint4 GridAndPressure; + + private uint4 DamAndSubsteps; + + DescriptorHandle> Particles; + + DescriptorHandle> PreviousPositions; + + DescriptorHandle> ParticleAffine; + + DescriptorHandle> GridAccumulation; + + DescriptorHandle> GridVelocity; + + DescriptorHandle> GridVelocityOld; + + DescriptorHandle> CellTypes; + + DescriptorHandle> Divergence; + + DescriptorHandle> PressureA; + + property float3 TankMin + { + get { + return TankMinAndTimeStep.xyz; + } + } + + property float TimeStep + { + get { + return TankMinAndTimeStep.w; + } + } + + property float3 TankMax + { + get { + return TankMaxAndTime.xyz; + } + } + + property float Time + { + get { + return TankMaxAndTime.w; + } + } + + property float GridSpacing + { + get { + return GridParameters.x; + } + } + + property float InverseGridSpacing + { + get { + return GridParameters.y; + } + } + + property float FlipRatio + { + get { + return GridParameters.z; + } + } + + property float VelocityDamping + { + get { + return GridParameters.w; + } + } + + property float WaveAmplitude + { + get { + return WaveAndInteraction.x; + } + } + + property float WaveFrequency + { + get { + return WaveAndInteraction.y; + } + } + + property float InteractionRadius + { + get { + return WaveAndInteraction.z; + } + } + + property float InteractionStrength + { + get { + return WaveAndInteraction.w; + } + } + + property float3 InteractionOrigin + { + get { + return InteractionOriginAndRadius.xyz; + } + } + + property float ParticleRadius + { + get { + return InteractionOriginAndRadius.w; + } + } + + property float3 InteractionDirection + { + get { + return InteractionDirectionAndDensity.xyz; + } + } + + property float RestDensity + { + get { + return InteractionDirectionAndDensity.w; + } + } + + property uint ParticleCount + { + get { + return Counts.x; + } + } + + property uint CellCount + { + get { + return Counts.y; + } + } + + property uint GridPointCount + { + get { + return Counts.z; + } + } + + property bool WaveMakerEnabled + { + get { + return Counts.w != 0; + } + } + + property uint3 GridDimensions + { + get { + return GridAndPressure.xyz; + } + } + + property uint PressureIterations + { + get { + return GridAndPressure.w; + } + } + + property uint3 DamDimensions + { + get { + return DamAndSubsteps.xyz; + } + } + + property uint Substeps + { + get { + return DamAndSubsteps.w; + } + } +}; + +ConstantBuffer simulation; + +float RoundedBoxSdf(float3 position, float3 center, float3 halfExtents, float radius) +{ + float3 q = abs(position - center) - halfExtents + radius; + + return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - radius; +} + +float CylinderSdf(float3 position, float3 center, float radius, float halfHeight) +{ + float2 d = abs(float2(length(position.xz - center.xz), position.y - center.y)) - float2(radius, halfHeight); + + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); +} + +float OrientedBoxSdf(float3 position, float3 center, float3 halfExtents, float angle) +{ + float cosine = cos(angle); + float sine = sin(angle); + float3 local = position - center; + local.xy = float2(cosine * local.x + sine * local.y, -sine * local.x + cosine * local.y); + float3 q = abs(local) - halfExtents; + + return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); +} + +float ObstacleSdf(float3 position) +{ + float roundedBox = RoundedBoxSdf(position, float3(-1.45, 0.82, 0.95), float3(0.72, 0.82, 0.72), 0.0); + float cylinder = CylinderSdf(position, float3(1.15, 0.92, -0.85), 0.62, 0.92); + float ramp = OrientedBoxSdf(position, float3(3.45, 0.62, 0.55), float3(1.15, 0.16, 1.05), -0.35); + + return min(roundedBox, min(cylinder, ramp)); +} + +float3 ObstacleNormal(float3 position) +{ + float epsilon = simulation.ParticleRadius * 0.1; + float3 x = float3(epsilon, 0.0, 0.0); + float3 y = float3(0.0, epsilon, 0.0); + float3 z = float3(0.0, 0.0, epsilon); + + return normalize(float3(ObstacleSdf(position + x) - ObstacleSdf(position - x), + ObstacleSdf(position + y) - ObstacleSdf(position - y), + ObstacleSdf(position + z) - ObstacleSdf(position - z))); +} + +bool CellInBounds(int3 cell) +{ + return all(cell >= 0) && all(cell < int3(simulation.GridDimensions)); +} + +bool GridPointInBounds(int3 point) +{ + return all(point >= 0) && all(point <= int3(simulation.GridDimensions)); +} + +uint CellIndex(int3 cell) +{ + return uint(cell.x) + uint(cell.y) * simulation.GridDimensions.x + uint(cell.z) * simulation.GridDimensions.x * simulation.GridDimensions.y; +} + +uint GridIndex(int3 point) +{ + uint width = simulation.GridDimensions.x + 1; + uint height = simulation.GridDimensions.y + 1; + + return uint(point.x) + uint(point.y) * width + uint(point.z) * width * height; +} + +int3 CellCoordinates(uint index) +{ + uint slice = simulation.GridDimensions.x * simulation.GridDimensions.y; + uint z = index / slice; + uint remainder = index - z * slice; + uint y = remainder / simulation.GridDimensions.x; + uint x = remainder - y * simulation.GridDimensions.x; + + return int3(x, y, z); +} + +int3 PressureCellCoordinates(uint index, uint parity) +{ + uint pairsPerRow = (simulation.GridDimensions.x + 1) / 2; + uint z = index / (pairsPerRow * simulation.GridDimensions.y); + uint remainder = index - z * pairsPerRow * simulation.GridDimensions.y; + uint y = remainder / pairsPerRow; + uint pairX = remainder - y * pairsPerRow; + uint x = pairX * 2 + ((y + z + parity) & 1); + + return int3(x, y, z); +} + +int3 GridCoordinates(uint index) +{ + uint width = simulation.GridDimensions.x + 1; + uint height = simulation.GridDimensions.y + 1; + uint slice = width * height; + uint z = index / slice; + uint remainder = index - z * slice; + uint y = remainder / width; + uint x = remainder - y * width; + + return int3(x, y, z); +} + +uint CellTypeAt(int3 cell) +{ + if (!CellInBounds(cell)) + { + return CellSolid; + } + + return simulation.CellTypes[CellIndex(cell)]; +} + +float PressureAt(int3 cell) +{ + return CellTypeAt(cell) == CellFluid ? simulation.PressureA[CellIndex(cell)] : 0.0; +} + +float ParticleDensityAt(int3 cell) +{ + uint index = GridIndex(cell); + uint width = simulation.GridDimensions.x + 1; + uint slice = width * (simulation.GridDimensions.y + 1); + float weight = float(simulation.GridAccumulation[simulation.GridPointCount + index]) + float(simulation.GridAccumulation[simulation.GridPointCount + index + 1]) + float(simulation.GridAccumulation[simulation.GridPointCount * 3 + index]) + float(simulation.GridAccumulation[simulation.GridPointCount * 3 + index + width]) + float(simulation.GridAccumulation[simulation.GridPointCount * 5 + index]) + float(simulation.GridAccumulation[simulation.GridPointCount * 5 + index + slice]); + + return weight / (6.0 * AccumulationScale); +} + +float3 FaceOffset(uint component) +{ + if (component == 0) + { + return float3(0.0, 0.5, 0.5); + } + + if (component == 1) + { + return float3(0.5, 0.0, 0.5); + } + + return float3(0.5, 0.5, 0.0); +} + +bool FaceIsValid(uint component, int3 point) +{ + if (!GridPointInBounds(point)) + { + return false; + } + + if (component == 0) + { + return point.y < int(simulation.GridDimensions.y) && point.z < int(simulation.GridDimensions.z); + } + + if (component == 1) + { + return point.x < int(simulation.GridDimensions.x) && point.z < int(simulation.GridDimensions.z); + } + + return point.x < int(simulation.GridDimensions.x) && point.y < int(simulation.GridDimensions.y); +} + +void FaceCells(uint component, int3 point, out int3 negativeCell, out int3 positiveCell) +{ + negativeCell = point; + positiveCell = point; + + if (component == 0) + { + negativeCell.x--; + } + else if (component == 1) + { + negativeCell.y--; + } + else + { + negativeCell.z--; + } +} + +bool FaceIsOpen(uint component, int3 point) +{ + if (!FaceIsValid(component, point)) + { + return false; + } + + int3 negativeCell; + int3 positiveCell; + FaceCells(component, point, negativeCell, positiveCell); + + return CellTypeAt(negativeCell) != CellSolid && CellTypeAt(positiveCell) != CellSolid; +} + +bool FaceTouchesFluid(uint component, int3 point) +{ + int3 negativeCell; + int3 positiveCell; + FaceCells(component, point, negativeCell, positiveCell); + + return CellTypeAt(negativeCell) == CellFluid || CellTypeAt(positiveCell) == CellFluid; +} + +float3 QuadraticWeights(float value) +{ + return float3(0.5 * (1.5 - value) * (1.5 - value), + 0.75 - (value - 1.0) * (value - 1.0), + 0.5 * (value - 0.5) * (value - 0.5)); +} + +void ScatterComponent(float3 position, float3 velocity, float3 affineRow, uint component) +{ + float3 offset = FaceOffset(component); + float3 coordinate = (position - simulation.TankMin) * simulation.InverseGridSpacing - offset; + int3 base = int3(floor(coordinate - 0.5)); + float3 fractional = coordinate - float3(base); + float3 weightX = QuadraticWeights(fractional.x); + float3 weightY = QuadraticWeights(fractional.y); + float3 weightZ = QuadraticWeights(fractional.z); + + for (int z = 0; z < 3; z++) + { + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 3; x++) + { + int3 point = base + int3(x, y, z); + if (!FaceIsValid(component, point)) + { + continue; + } + + float weight = weightX[x] * weightY[y] * weightZ[z]; + float3 facePosition = simulation.TankMin + (float3(point) + offset) * simulation.GridSpacing; + float momentum = clamp(velocity[component] + dot(affineRow, facePosition - position), -12.0, 12.0); + uint index = GridIndex(point); + uint valueOffset = component * 2 * simulation.GridPointCount; + uint weightOffset = valueOffset + simulation.GridPointCount; + int fixedWeight = int(round(weight * AccumulationScale)); + int fixedMomentum = int(round(momentum * weight * AccumulationScale)); + InterlockedAdd(simulation.GridAccumulation[valueOffset + index], fixedMomentum); + InterlockedAdd(simulation.GridAccumulation[weightOffset + index], fixedWeight); + } + } + } +} + +bool FaceCanSample(uint component, int3 point) +{ + if (!FaceIsValid(component, point)) + { + return false; + } + + int3 negativeCell; + int3 positiveCell; + FaceCells(component, point, negativeCell, positiveCell); + uint negativeType = CellTypeAt(negativeCell); + uint positiveType = CellTypeAt(positiveCell); + + return negativeType != CellSolid && positiveType != CellSolid && (negativeType == CellFluid || positiveType == CellFluid); +} + +void SampleComponentPair(float3 position, + uint component, + out float currentVelocity, + out float previousVelocity, + out float3 affineRow) +{ + float3 offset = FaceOffset(component); + float3 coordinate = (position - simulation.TankMin) * simulation.InverseGridSpacing - offset; + int3 base = int3(floor(coordinate - 0.5)); + float3 fractional = coordinate - float3(base); + float3 weightX = QuadraticWeights(fractional.x); + float3 weightY = QuadraticWeights(fractional.y); + float3 weightZ = QuadraticWeights(fractional.z); + currentVelocity = 0.0; + previousVelocity = 0.0; + float3 moment = float3(0.0, 0.0, 0.0); + float3 firstMoment = float3(0.0, 0.0, 0.0); + float3 secondMoment = float3(0.0, 0.0, 0.0); + float weightSum = 0.0; + + for (int z = 0; z < 3; z++) + { + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 3; x++) + { + int3 point = base + int3(x, y, z); + if (!FaceCanSample(component, point)) + { + continue; + } + + float weight = weightX[x] * weightY[y] * weightZ[z]; + uint index = GridIndex(point); + float faceVelocity = simulation.GridVelocity[index][component]; + float oldFaceVelocity = simulation.GridVelocityOld[index][component]; + float3 facePosition = simulation.TankMin + (float3(point) + offset) * simulation.GridSpacing; + float3 delta = facePosition - position; + currentVelocity += weight * faceVelocity; + previousVelocity += weight * oldFaceVelocity; + moment += weight * faceVelocity * delta; + firstMoment += weight * delta; + secondMoment += weight * delta * delta; + weightSum += weight; + } + } + } + + if (weightSum <= 0.0001) + { + currentVelocity = 0.0; + previousVelocity = 0.0; + affineRow = float3(0.0, 0.0, 0.0); + + return; + } + + currentVelocity /= weightSum; + previousVelocity /= weightSum; + float3 centeredMoment = moment - currentVelocity * firstMoment; + float3 variance = secondMoment - firstMoment * firstMoment / weightSum; + affineRow = centeredMoment / max(variance, simulation.GridSpacing * simulation.GridSpacing * 0.02); + affineRow = clamp(affineRow, -24.0, 24.0); +} + +float SampleGridComponent(float3 position, uint component) +{ + float3 offset = FaceOffset(component); + float3 coordinate = (position - simulation.TankMin) * simulation.InverseGridSpacing - offset; + int3 base = int3(floor(coordinate - 0.5)); + float3 fractional = coordinate - float3(base); + float3 weightX = QuadraticWeights(fractional.x); + float3 weightY = QuadraticWeights(fractional.y); + float3 weightZ = QuadraticWeights(fractional.z); + float velocity = 0.0; + float weightSum = 0.0; + + for (int z = 0; z < 3; z++) + { + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 3; x++) + { + int3 point = base + int3(x, y, z); + if (!FaceCanSample(component, point)) + { + continue; + } + + float weight = weightX[x] * weightY[y] * weightZ[z]; + velocity += weight * simulation.GridVelocity[GridIndex(point)][component]; + weightSum += weight; + } + } + } + + return weightSum > 0.0001 ? velocity / weightSum : 0.0; +} + +float3 SampleGridVelocity(float3 position) +{ + return float3(SampleGridComponent(position, 0), + SampleGridComponent(position, 1), + SampleGridComponent(position, 2)); +} + +bool ResolveCollisions(inout float3 position, inout float3 velocity) +{ + float3 minimum = simulation.TankMin + simulation.ParticleRadius; + float3 maximum = simulation.TankMax - simulation.ParticleRadius; + bool collided = false; + + if (position.x < minimum.x) + { + position.x = minimum.x; + velocity.x = max(velocity.x, 0.0); + collided = true; + } + else if (position.x > maximum.x) + { + position.x = maximum.x; + velocity.x = min(velocity.x, 0.0); + collided = true; + } + + if (position.y < minimum.y) + { + position.y = minimum.y; + velocity.y = max(velocity.y, 0.0); + velocity.xz *= 0.998; + collided = true; + } + else if (position.y > maximum.y) + { + position.y = maximum.y; + velocity.y = min(velocity.y, 0.0); + collided = true; + } + + if (position.z < minimum.z) + { + position.z = minimum.z; + velocity.z = max(velocity.z, 0.0); + collided = true; + } + else if (position.z > maximum.z) + { + position.z = maximum.z; + velocity.z = min(velocity.z, 0.0); + collided = true; + } + + float distance = ObstacleSdf(position); + if (distance < simulation.ParticleRadius) + { + float3 normal = ObstacleNormal(position); + position += normal * (simulation.ParticleRadius - distance); + velocity -= normal * min(dot(velocity, normal), 0.0); + velocity *= 0.999; + collided = true; + } + + position = clamp(position, minimum, maximum); + + return collided; +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void ResetCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + if (index >= simulation.ParticleCount) + { + return; + } + + uint x = index % simulation.DamDimensions.x; + uint y = (index / simulation.DamDimensions.x) % simulation.DamDimensions.y; + uint z = index / (simulation.DamDimensions.x * simulation.DamDimensions.y); + float spacing = simulation.ParticleRadius * ParticleSpacingScale; + float3 jitter = float3(Hash11(index * 3 + 0), Hash11(index * 3 + 1), Hash11(index * 3 + 2)) - 0.5; + float3 position = simulation.TankMin + simulation.ParticleRadius * float3(1.05, 1.25, 1.25); + position += float3(x, y, z) * spacing + jitter * spacing * 0.02; + + Particle particle; + particle.PositionDensity = float4(position, simulation.RestDensity); + particle.VelocityLambda = float4(0.0, 0.0, 0.0, 0.0); + simulation.Particles[index] = particle; + simulation.PreviousPositions[index] = float4(position, 0.0); + simulation.ParticleAffine[index * 3 + 0] = float4(0.0, 0.0, 0.0, 0.0); + simulation.ParticleAffine[index * 3 + 1] = float4(0.0, 0.0, 0.0, 0.0); + simulation.ParticleAffine[index * 3 + 2] = float4(0.0, 0.0, 0.0, 0.0); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void InitializeGridCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + if (index < simulation.CellCount) + { + int3 cell = CellCoordinates(index); + float3 center = simulation.TankMin + (float3(cell) + 0.5) * simulation.GridSpacing; + simulation.CellTypes[index] = ObstacleSdf(center) < 0.0 ? CellSolid : CellAir; + simulation.PressureA[index] = 0.0; + } +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void ClearGridCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + + if (index < simulation.GridPointCount * 6) + { + simulation.GridAccumulation[index] = 0; + } + + if (index < simulation.CellCount) + { + simulation.CellTypes[index] = simulation.CellTypes[index] == CellSolid ? CellSolid : CellAir; + } +} + +void ParticleToGrid(uint index, bool savePreviousPosition) +{ + if (index >= simulation.ParticleCount) + { + return; + } + + Particle particle = simulation.Particles[index]; + float3 position = ParticlePosition(particle); + float3 velocity = ParticleVelocity(particle); + + if (savePreviousPosition) + { + simulation.PreviousPositions[index] = float4(position, 0.0); + } + + int3 cell = clamp(int3(floor((position - simulation.TankMin) * simulation.InverseGridSpacing)), + int3(0, 0, 0), int3(simulation.GridDimensions) - 1); + InterlockedMax(simulation.CellTypes[CellIndex(cell)], CellFluid); + ScatterComponent(position, velocity, simulation.ParticleAffine[index * 3 + 0].xyz, 0); + ScatterComponent(position, velocity, simulation.ParticleAffine[index * 3 + 1].xyz, 1); + ScatterComponent(position, velocity, simulation.ParticleAffine[index * 3 + 2].xyz, 2); +} + +[shader("compute")] +[numthreads(128, 1, 1)] +void BeginParticleToGridCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + ParticleToGrid(dispatchThreadID.x, true); +} + +[shader("compute")] +[numthreads(128, 1, 1)] +void ParticleToGridCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + ParticleToGrid(dispatchThreadID.x, false); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void NormalizeAndApplyForcesCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + if (index >= simulation.GridPointCount) + { + return; + } + + int3 point = GridCoordinates(index); + float3 velocity = float3(0.0, 0.0, 0.0); + + for (uint component = 0; component < 3; component++) + { + uint valueOffset = component * 2 * simulation.GridPointCount; + uint weightOffset = valueOffset + simulation.GridPointCount; + int weight = simulation.GridAccumulation[weightOffset + index]; + + if (weight > 0 && FaceIsOpen(component, point)) + { + velocity[component] = float(simulation.GridAccumulation[valueOffset + index]) / float(weight); + } + } + + simulation.GridVelocityOld[index] = float4(velocity, 0.0); + + if (FaceIsOpen(1, point) && FaceTouchesFluid(1, point)) + { + velocity.y -= 9.81 * simulation.TimeStep; + } + + if (simulation.WaveMakerEnabled && point.x <= 2 && FaceIsOpen(0, point) && FaceTouchesFluid(0, point)) + { + float phase = simulation.Time * simulation.WaveFrequency * 2.0 * PI + float(point.z) * 0.13; + float targetVelocity = simulation.WaveAmplitude * simulation.WaveFrequency * 2.0 * PI * cos(phase); + velocity.x = lerp(velocity.x, targetVelocity, exp(-float(point.x) * 0.65)); + } + + if (simulation.InteractionStrength > 0.0) + { + for (uint component = 0; component < 3; component++) + { + if (!FaceIsOpen(component, point) || !FaceTouchesFluid(component, point)) + { + continue; + } + + float3 facePosition = simulation.TankMin + (float3(point) + FaceOffset(component)) * simulation.GridSpacing; + float rayDistance = dot(facePosition - simulation.InteractionOrigin, simulation.InteractionDirection); + float3 closestPoint = simulation.InteractionOrigin + simulation.InteractionDirection * max(rayDistance, 0.0); + float distance = length(facePosition - closestPoint); + + if (rayDistance > 0.0 && distance < simulation.InteractionRadius) + { + float falloff = 1.0 - distance / simulation.InteractionRadius; + velocity[component] += (simulation.InteractionDirection[component] + (component == 1 ? 0.45 : 0.0)) * simulation.InteractionStrength * falloff / float(simulation.Substeps); + } + } + } + + simulation.GridVelocity[index] = float4(velocity, 0.0); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void DivergenceCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + if (index >= simulation.CellCount) + { + return; + } + + if (simulation.CellTypes[index] != CellFluid) + { + simulation.Divergence[index] = 0.0; + simulation.PressureA[index] = 0.0; + + return; + } + + int3 cell = CellCoordinates(index); + float3 negativeVelocity = simulation.GridVelocity[GridIndex(cell)].xyz; + float velocityLeft = negativeVelocity.x; + float velocityRight = simulation.GridVelocity[GridIndex(cell + int3(1, 0, 0))].x; + float velocityBottom = negativeVelocity.y; + float velocityTop = simulation.GridVelocity[GridIndex(cell + int3(0, 1, 0))].y; + float velocityBack = negativeVelocity.z; + float velocityFront = simulation.GridVelocity[GridIndex(cell + int3(0, 0, 1))].z; + float divergence = (velocityRight - velocityLeft + velocityTop - velocityBottom + velocityFront - velocityBack) * simulation.InverseGridSpacing; + float particleSpacing = simulation.ParticleRadius * ParticleSpacingScale; + float targetDensity = simulation.GridSpacing * simulation.GridSpacing * simulation.GridSpacing / (particleSpacing * particleSpacing * particleSpacing); + float compression = max(ParticleDensityAt(cell) / targetDensity - 1.0, 0.0); + float expansion = min(compression * 0.1 / simulation.TimeStep, 2.0); + simulation.Divergence[index] = divergence - expansion; +} + +void RelaxPressure(int3 cell) +{ + if (any(cell >= int3(simulation.GridDimensions))) + { + return; + } + + uint index = CellIndex(cell); + if (simulation.CellTypes[index] != CellFluid) + { + return; + } + + int3 neighbors[6] = + { + cell + int3(-1, 0, 0), cell + int3(1, 0, 0), + cell + int3(0, -1, 0), cell + int3(0, 1, 0), + cell + int3(0, 0, -1), cell + int3(0, 0, 1) + }; + float sum = 0.0; + float denominator = 0.0; + + for (uint neighborIndex = 0; neighborIndex < 6; neighborIndex++) + { + uint type = CellTypeAt(neighbors[neighborIndex]); + if (type == CellSolid) + { + continue; + } + + denominator += 1.0; + if (type == CellFluid) + { + sum += simulation.PressureA[CellIndex(neighbors[neighborIndex])]; + } + } + + float rightHandSide = simulation.Divergence[index] * simulation.GridSpacing * simulation.GridSpacing / simulation.TimeStep; + float pressure = denominator > 0.0 ? (sum - rightHandSide) / denominator : 0.0; + simulation.PressureA[index] = lerp(simulation.PressureA[index], pressure, 1.6); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void PressureRedCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + RelaxPressure(PressureCellCoordinates(dispatchThreadID.x, 0)); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void PressureBlackCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + RelaxPressure(PressureCellCoordinates(dispatchThreadID.x, 1)); +} + +[shader("compute")] +[numthreads(256, 1, 1)] +void ProjectGridCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + if (index >= simulation.GridPointCount) + { + return; + } + + int3 point = GridCoordinates(index); + float3 velocity = simulation.GridVelocity[index].xyz; + + for (uint component = 0; component < 3; component++) + { + int3 negativeCell; + int3 positiveCell; + FaceCells(component, point, negativeCell, positiveCell); + uint negativeType = CellTypeAt(negativeCell); + uint positiveType = CellTypeAt(positiveCell); + + if (negativeType == CellSolid || positiveType == CellSolid) + { + velocity[component] = 0.0; + continue; + } + + if (negativeType != CellFluid && positiveType != CellFluid) + { + continue; + } + + float negativePressure = negativeType == CellFluid ? simulation.PressureA[CellIndex(negativeCell)] : 0.0; + float positivePressure = positiveType == CellFluid ? simulation.PressureA[CellIndex(positiveCell)] : 0.0; + velocity[component] -= simulation.TimeStep * (positivePressure - negativePressure) * simulation.InverseGridSpacing; + velocity[component] = clamp(velocity[component], -8.0, 8.0); + } + + simulation.GridVelocity[index] = float4(velocity, 0.0); +} + +[shader("compute")] +[numthreads(128, 1, 1)] +void GridToParticleAndAdvectCS(uint3 dispatchThreadID: SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x; + if (index >= simulation.ParticleCount) + { + return; + } + + Particle particle = simulation.Particles[index]; + float3 position = ParticlePosition(particle); + float3 oldVelocity = ParticleVelocity(particle); + float3 affineX; + float3 affineY; + float3 affineZ; + float3 picVelocity; + float3 oldGridVelocity; + SampleComponentPair(position, 0, picVelocity.x, oldGridVelocity.x, affineX); + SampleComponentPair(position, 1, picVelocity.y, oldGridVelocity.y, affineY); + SampleComponentPair(position, 2, picVelocity.z, oldGridVelocity.z, affineZ); + float3 flipVelocity = oldVelocity + picVelocity - oldGridVelocity; + float damping = pow(simulation.VelocityDamping, simulation.TimeStep * 60.0); + float3 velocity = lerp(picVelocity, flipVelocity, simulation.FlipRatio) * damping; + float speed = length(velocity); + + if (speed > 7.5) + { + velocity *= 7.5 / speed; + } + + if (speed < 0.012 && !simulation.WaveMakerEnabled && simulation.InteractionStrength <= 0.0) + { + velocity = float3(0.0, 0.0, 0.0); + } + + float3 midpoint = position + picVelocity * simulation.TimeStep * 0.5; + float3 advectionVelocity = SampleGridVelocity(midpoint); + position += advectionVelocity * simulation.TimeStep; + bool collided = ResolveCollisions(position, velocity); + particle.PositionDensity = float4(position, simulation.RestDensity); + particle.VelocityLambda = float4(velocity, 0.0); + simulation.Particles[index] = particle; + + if (collided) + { + simulation.ParticleAffine[index * 3 + 0] = float4(0.0, 0.0, 0.0, 0.0); + simulation.ParticleAffine[index * 3 + 1] = float4(0.0, 0.0, 0.0, 0.0); + simulation.ParticleAffine[index * 3 + 2] = float4(0.0, 0.0, 0.0, 0.0); + } + else + { + simulation.ParticleAffine[index * 3 + 0] = float4(affineX, 0.0); + simulation.ParticleAffine[index * 3 + 1] = float4(affineY, 0.0); + simulation.ParticleAffine[index * 3 + 2] = float4(affineZ, 0.0); + } +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang new file mode 100644 index 00000000..f0b7b4fc --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang @@ -0,0 +1,278 @@ +#include "FluidCommon.slang" + +struct SurfaceConstants +{ + float4x4 View; + + float4x4 Projection; + + private float4 CameraRightAndRadius; + + private float4 CameraUpAndRestDensity; + + private uint4 CountsAndDimensions; + + private float4 InterpolationParameters; + + DescriptorHandle> Particles; + + DescriptorHandle> PreviousPositions; + + DescriptorHandle SceneDepth; + + DescriptorHandle Sampler; + + property float3 CameraRight + { + get { + return CameraRightAndRadius.xyz; + } + } + + property float ParticleRadius + { + get { + return CameraRightAndRadius.w; + } + } + + property float3 CameraUp + { + get { + return CameraUpAndRestDensity.xyz; + } + } + + property float RestDensity + { + get { + return CameraUpAndRestDensity.w; + } + } + + property uint ParticleCount + { + get { + return CountsAndDimensions.x; + } + } + + property uint RenderMode + { + get { + return CountsAndDimensions.y; + } + } + + property uint Width + { + get { + return CountsAndDimensions.z; + } + } + + property uint Height + { + get { + return CountsAndDimensions.w; + } + } + + property float InterpolationAlpha + { + get { + return InterpolationParameters.x; + } + } +}; + +struct SurfaceVSOutput +{ + float4 Position : SV_POSITION; + + float2 Corner : TEXCOORD0; + + float3 CenterView : TEXCOORD1; + + float Speed : TEXCOORD2; + + float Density : TEXCOORD3; +}; + +struct DepthOutput +{ + float LinearDepth : SV_TARGET0; + + float4 Attributes : SV_TARGET1; + + float DeviceDepth : SV_DEPTH; +}; + +struct ThicknessOutput +{ + float Thickness : SV_TARGET; + + float DeviceDepth : SV_DEPTH; +}; + +struct ParticleOutput +{ + float4 Color : SV_TARGET; + + float DeviceDepth : SV_DEPTH; +}; + +ConstantBuffer surface; + +bool OccludedByScene(SurfaceVSOutput input, float linearDepth) +{ + float2 uv = input.Position.xy / float2(surface.Width, surface.Height); + float sceneDepth = surface.SceneDepth.SampleLevel(surface.Sampler, uv, 0.0).r; + + return surface.RenderMode == 0 && sceneDepth > 0.0 && linearDepth >= sceneDepth; +} + +float2 QuadCorner(uint vertexId) +{ + if (vertexId == 0) + return float2(-1.0, -1.0); + if (vertexId == 1) + return float2(1.0, -1.0); + if (vertexId == 2) + return float2(-1.0, 1.0); + + return float2(1.0, 1.0); +} + +[shader("vertex")] +SurfaceVSOutput SurfaceVS(uint vertexId: SV_VertexID, uint instanceId: SV_InstanceID) +{ + Particle particle = surface.Particles[instanceId]; + float2 corner = QuadCorner(vertexId); + float3 worldPosition = lerp(surface.PreviousPositions[instanceId].xyz, particle.PositionDensity.xyz, surface.InterpolationAlpha); + float3 velocity = particle.VelocityLambda.xyz; + float speed = length(velocity); + float density = particle.PositionDensity.w / surface.RestDensity; + float2 offset = corner; + + if (surface.RenderMode == 0) + { + float2 projectedVelocity = float2(dot(velocity, surface.CameraRight), dot(velocity, surface.CameraUp)); + float projectedSpeed = length(projectedVelocity); + float2 direction = projectedSpeed > 0.001 ? projectedVelocity / projectedSpeed : float2(1.0, 0.0); + float stretchWeight = smoothstep(0.35, 3.2, speed) * (0.45 + 0.55 * (1.0 - smoothstep(0.68, 1.05, density))); + float majorScale = 1.0 + stretchWeight * 1.65; + float minorScale = 1.0 - stretchWeight * 0.25; + offset = direction * corner.x * majorScale + float2(-direction.y, direction.x) * corner.y * minorScale; + } + + float3 billboardPosition = worldPosition + (surface.CameraRight * offset.x + surface.CameraUp * offset.y) * surface.ParticleRadius; + float4 centerView = mul(float4(worldPosition, 1.0), surface.View); + + SurfaceVSOutput output; + output.Position = mul(mul(float4(billboardPosition, 1.0), surface.View), surface.Projection); + output.Corner = corner; + output.CenterView = centerView.xyz; + output.Speed = speed; + output.Density = density; + + return output; +} + +[shader("fragment")] +DepthOutput DepthFS(SurfaceVSOutput input) +{ + float radiusSquared = dot(input.Corner, input.Corner); + if (radiusSquared > 1.0) + { + discard; + } + + float sphereZ = sqrt(max(1.0 - radiusSquared, 0.0)); + float3 viewPosition = input.CenterView + float3(input.Corner * surface.ParticleRadius, surface.ParticleRadius * 0.62); + float4 clipPosition = mul(float4(viewPosition, 1.0), surface.Projection); + + if (OccludedByScene(input, -viewPosition.z)) + { + discard; + } + + DepthOutput output; + output.LinearDepth = -viewPosition.z; + output.Attributes = float4(input.Speed, input.Density, sphereZ, 1.0); + output.DeviceDepth = clipPosition.z / clipPosition.w; + + return output; +} + +[shader("fragment")] +ThicknessOutput ThicknessFS(SurfaceVSOutput input) +{ + float radiusSquared = dot(input.Corner, input.Corner); + if (radiusSquared > 1.0) + { + discard; + } + + float sphereZ = sqrt(max(1.0 - radiusSquared, 0.0)); + float3 viewPosition = input.CenterView + float3(input.Corner * surface.ParticleRadius, sphereZ * surface.ParticleRadius); + float4 clipPosition = mul(float4(viewPosition, 1.0), surface.Projection); + + if (OccludedByScene(input, -viewPosition.z)) + { + discard; + } + + ThicknessOutput output; + output.Thickness = 1.08 * surface.ParticleRadius * sphereZ; + output.DeviceDepth = clipPosition.z / clipPosition.w; + + return output; +} + +float3 SpeedColor(float speed) +{ + float value = saturate(speed / 6.5); + float3 blue = float3(0.015, 0.12, 0.95); + float3 cyan = float3(0.0, 0.82, 1.0); + float3 yellow = float3(1.0, 0.84, 0.02); + float3 red = float3(0.95, 0.025, 0.01); + + if (value < 0.34) + { + return lerp(blue, cyan, value / 0.34); + } + + if (value < 0.70) + { + return lerp(cyan, yellow, (value - 0.34) / 0.36); + } + + return lerp(yellow, red, (value - 0.70) / 0.30); +} + +[shader("fragment")] +ParticleOutput ParticleFS(SurfaceVSOutput input) +{ + float radiusSquared = dot(input.Corner, input.Corner); + if (radiusSquared > 1.0) + { + discard; + } + + float sphereZ = sqrt(max(1.0 - radiusSquared, 0.0)); + float3 normal = normalize(float3(input.Corner, sphereZ)); + float3 lightDirection = normalize(float3(-0.38, 0.62, 0.69)); + float diffuse = 0.24 + 0.76 * saturate(dot(normal, lightDirection)); + float specular = pow(saturate(dot(reflect(-lightDirection, normal), float3(0.0, 0.0, 1.0))), 36.0) * 0.42; + float rim = pow(1.0 - sphereZ, 3.0) * 0.12; + float3 color = SpeedColor(input.Speed) * (diffuse + rim) + specular; + float3 viewPosition = input.CenterView + float3(input.Corner * surface.ParticleRadius, sphereZ * surface.ParticleRadius); + float4 clipPosition = mul(float4(viewPosition, 1.0), surface.Projection); + + ParticleOutput output; + output.Color = float4(pow(saturate(color), 1.0 / 2.2), 1.0); + output.DeviceDepth = clipPosition.z / clipPosition.w; + + return output; +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/Glass.slang b/sources/Experiments/FluidTank/Assets/Shaders/Glass.slang new file mode 100644 index 00000000..9b01bdf2 --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/Glass.slang @@ -0,0 +1,69 @@ +#include "SceneCommon.slang" + +struct GlassConstants +{ + float4x4 View; + + float4x4 Projection; + + private float4 CameraPositionAndTime; + + property float3 CameraPosition + { + get { + return CameraPositionAndTime.xyz; + } + } + + property float Time + { + get { + return CameraPositionAndTime.w; + } + } +}; + +struct VSInput +{ + float4 Position : POSITION; + + float4 NormalAndMaterial : NORMAL; +}; + +struct VSOutput +{ + float4 Position : SV_POSITION; + + float3 WorldPosition : TEXCOORD0; + + float3 Normal : TEXCOORD1; +}; + +ConstantBuffer glass; + +[shader("vertex")] +VSOutput GlassVS(VSInput input) +{ + VSOutput output; + output.Position = mul(mul(float4(input.Position.xyz, 1.0), glass.View), glass.Projection); + output.WorldPosition = input.Position.xyz; + output.Normal = input.NormalAndMaterial.xyz; + + return output; +} + +[shader("fragment")] +float4 GlassFS(VSOutput input) : SV_TARGET +{ + float3 normal = normalize(input.Normal); + float3 viewDirection = normalize(glass.CameraPosition - input.WorldPosition); + float facing = abs(dot(normal, viewDirection)); + float fresnel = 0.028 + 0.972 * pow(1.0 - facing, 5.0); + float verticalEdge = pow(saturate(abs(input.WorldPosition.y - 2.6) / 2.6), 18.0); + float micro = 0.5 + 0.5 * sin(input.WorldPosition.x * 31.0 + input.WorldPosition.y * 17.0 + glass.Time * 0.1); + float alpha = saturate(0.018 + fresnel * 0.34 + verticalEdge * 0.04 + micro * 0.004); + float3 tint = lerp(float3(0.42, 0.74, 0.78), float3(0.83, 0.96, 1.0), fresnel); + float3 color = ToSRGB(ACESFilm(tint * (0.42 + fresnel * 0.75))); + + return float4(color * alpha, alpha); +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang b/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang new file mode 100644 index 00000000..b69b0367 --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang @@ -0,0 +1,122 @@ +#include "SceneCommon.slang" + +struct SceneConstants +{ + float4x4 View; + + float4x4 Projection; + + private float4 CameraPositionAndTime; + + private float4 LightDirectionAndIntensity; + + DescriptorHandle> Materials; + + property float3 CameraPosition + { + get { + return CameraPositionAndTime.xyz; + } + } + + property float Time + { + get { + return CameraPositionAndTime.w; + } + } + + property float3 LightDirection + { + get { + return LightDirectionAndIntensity.xyz; + } + } + + property float LightIntensity + { + get { + return LightDirectionAndIntensity.w; + } + } +}; + +struct VSInput +{ + float4 Position : POSITION; + + float4 NormalAndMaterial : NORMAL; +}; + +struct VSOutput +{ + float4 Position : SV_POSITION; + + float3 WorldPosition : TEXCOORD0; + + float3 Normal : TEXCOORD1; + + float ViewDepth : TEXCOORD2; + + nointerpolation uint MaterialId : TEXCOORD3; +}; + +struct FSOutput +{ + float4 Color : SV_TARGET0; + + float LinearDepth : SV_TARGET1; +}; + +ConstantBuffer scene; + +[shader("vertex")] +VSOutput VSMain(VSInput input) +{ + float4 worldPosition = float4(input.Position.xyz, 1.0); + float4 viewPosition = mul(worldPosition, scene.View); + + VSOutput output; + output.Position = mul(viewPosition, scene.Projection); + output.WorldPosition = input.Position.xyz; + output.Normal = input.NormalAndMaterial.xyz; + output.ViewDepth = -viewPosition.z; + output.MaterialId = asuint(input.NormalAndMaterial.w); + + return output; +} + +[shader("fragment")] +FSOutput FSMain(VSOutput input) +{ + SceneMaterial material = scene.Materials[input.MaterialId]; + float3 normal = normalize(input.Normal); + float3 viewDirection = normalize(scene.CameraPosition - input.WorldPosition); + float3 lightDirection = normalize(-scene.LightDirection); + float3 halfVector = normalize(viewDirection + lightDirection); + + float ndotl = saturate(dot(normal, lightDirection)); + float ndoth = saturate(dot(normal, halfVector)); + float roughness = max(material.Roughness, 0.035); + float exponent = max(2.0 / (roughness * roughness) - 2.0, 1.0); + float3 f0 = lerp(float3(0.035, 0.038, 0.04), material.Albedo, material.Metallic); + float3 fresnel = FresnelSchlick(saturate(dot(halfVector, viewDirection)), f0); + float3 diffuse = material.Albedo * (1.0 - material.Metallic) * (0.20 + ndotl * scene.LightIntensity); + float3 specular = fresnel * pow(ndoth, exponent) * (0.18 + scene.LightIntensity * 1.8); + float3 environment = SkyColor(reflect(-viewDirection, normal)) * fresnel * (0.18 + 0.82 * (1.0 - roughness)); + + if (input.MaterialId == 0) + { + float gridX = smoothstep(0.42, 0.49, abs(frac(input.WorldPosition.x * 0.5) - 0.5)); + float gridZ = smoothstep(0.42, 0.49, abs(frac(input.WorldPosition.z * 0.5) - 0.5)); + diffuse *= lerp(0.84, 1.07, max(gridX, gridZ)); + } + + float3 color = diffuse + specular + environment + material.Albedo * material.Emission; + + FSOutput output; + output.Color = float4(color, 1.0); + output.LinearDepth = input.ViewDepth; + + return output; +} diff --git a/sources/Experiments/FluidTank/Assets/Shaders/SceneCommon.slang b/sources/Experiments/FluidTank/Assets/Shaders/SceneCommon.slang new file mode 100644 index 00000000..ed98efeb --- /dev/null +++ b/sources/Experiments/FluidTank/Assets/Shaders/SceneCommon.slang @@ -0,0 +1,89 @@ +struct SceneVertex +{ + private float4 PositionAndPadding; + + private float4 NormalAndMaterial; + + property float3 Position + { + get { + return PositionAndPadding.xyz; + } + } + + property float3 Normal + { + get { + return NormalAndMaterial.xyz; + } + } + + property uint MaterialId + { + get { + return asuint(NormalAndMaterial.w); + } + } +}; + +struct SceneMaterial +{ + private float4 AlbedoAndRoughness; + + private float4 MetallicEmissionAndPadding; + + property float3 Albedo + { + get { + return AlbedoAndRoughness.xyz; + } + } + + property float Roughness + { + get { + return AlbedoAndRoughness.w; + } + } + + property float Metallic + { + get { + return MetallicEmissionAndPadding.x; + } + } + + property float Emission + { + get { + return MetallicEmissionAndPadding.y; + } + } +}; + +float3 SkyColor(float3 direction) +{ + float horizon = saturate(direction.y * 0.5 + 0.5); + float3 sky = lerp(float3(0.62, 0.76, 0.82), float3(0.055, 0.18, 0.34), pow(horizon, 0.62)); + float3 sunDirection = normalize(float3(-0.38, 0.83, -0.42)); + float sunAlignment = saturate(dot(direction, sunDirection)); + float sun = pow(sunAlignment, 720.0); + float halo = pow(sunAlignment, 18.0); + + return sky + float3(10.0, 8.2, 5.3) * sun + float3(0.55, 0.42, 0.25) * halo; +} + +float3 FresnelSchlick(float cosine, float3 f0) +{ + return f0 + (1.0 - f0) * pow(saturate(1.0 - cosine), 5.0); +} + +float3 ACESFilm(float3 color) +{ + return saturate((color * (color * 2.51 + 0.03)) / (color * (color * 2.43 + 0.59) + 0.14)); +} + +float3 ToSRGB(float3 color) +{ + return pow(max(color, 0.0), 1.0 / 2.2); +} diff --git a/sources/Experiments/FluidTank/FluidSimulation.cs b/sources/Experiments/FluidTank/FluidSimulation.cs new file mode 100644 index 00000000..547be4fd --- /dev/null +++ b/sources/Experiments/FluidTank/FluidSimulation.cs @@ -0,0 +1,386 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using FluidTank.Helpers; +using Zenith.NET; +using Buffer = Zenith.NET.Buffer; + +namespace FluidTank; + +internal unsafe class FluidSimulation : IDisposable +{ + private static readonly Vector3 TankMin = new(-6.0f, 0.0f, -3.0f); + private static readonly Vector3 TankMax = new(6.0f, 5.2f, 3.0f); + private static readonly (uint X, uint Y, uint Z) DamDimensions = (40, 48, 59); + private const float GridSpacing = 6.0f / 33.0f; + + private readonly Buffer constantBuffer; + private readonly Buffer particles; + private readonly Buffer previousPositions; + private readonly Buffer particleAffine; + private readonly Buffer gridAccumulation; + private readonly Buffer gridVelocity; + private readonly Buffer gridVelocityOld; + private readonly Buffer cellTypes; + private readonly Buffer divergence; + private readonly Buffer pressureA; + private readonly uint pressureParityDispatchCount; + + private readonly ComputePipeline resetPipeline; + private readonly ComputePipeline initializeGridPipeline; + private readonly ComputePipeline clearGridPipeline; + private readonly ComputePipeline beginParticleToGridPipeline; + private readonly ComputePipeline particleToGridPipeline; + private readonly ComputePipeline normalizeAndApplyForcesPipeline; + private readonly ComputePipeline divergencePipeline; + private readonly ComputePipeline pressureRedPipeline; + private readonly ComputePipeline pressureBlackPipeline; + private readonly ComputePipeline projectGridPipeline; + private readonly ComputePipeline gridToParticleAndAdvectPipeline; + + private bool resetRequested = true; + private Vector3 interactionOrigin; + private Vector3 interactionDirection; + private float interactionStrength; + private TimelineValue ready; + + public FluidSimulation() + { + ParticleCount = DamDimensions.X * DamDimensions.Y * DamDimensions.Z; + + Vector3 tankExtent = TankMax - TankMin; + GridDimensions = new((uint)MathF.Ceiling(tankExtent.X / GridSpacing), + (uint)MathF.Ceiling(tankExtent.Y / GridSpacing), + (uint)MathF.Ceiling(tankExtent.Z / GridSpacing)); + CellCount = GridDimensions.X * GridDimensions.Y * GridDimensions.Z; + GridPointCount = (GridDimensions.X + 1) * (GridDimensions.Y + 1) * (GridDimensions.Z + 1); + pressureParityDispatchCount = (GridDimensions.X + 1) / 2 * GridDimensions.Y * GridDimensions.Z; + + constantBuffer = GraphicsHelper.CreateConstantBuffer(); + + particles = GraphicsHelper.CreateBuffer(ParticleCount, 32, BufferUsages.StorageReadOnly | BufferUsages.StorageReadWrite); + previousPositions = GraphicsHelper.CreateBuffer(ParticleCount, 16, BufferUsages.StorageReadOnly | BufferUsages.StorageReadWrite); + particleAffine = GraphicsHelper.CreateBuffer(ParticleCount * 3, 16, BufferUsages.StorageReadWrite); + gridAccumulation = GraphicsHelper.CreateBuffer(GridPointCount * 6, sizeof(int), BufferUsages.StorageReadWrite); + gridVelocity = GraphicsHelper.CreateBuffer(GridPointCount, 16, BufferUsages.StorageReadWrite); + gridVelocityOld = GraphicsHelper.CreateBuffer(GridPointCount, 16, BufferUsages.StorageReadWrite); + cellTypes = GraphicsHelper.CreateBuffer(CellCount, sizeof(uint), BufferUsages.StorageReadWrite); + divergence = GraphicsHelper.CreateBuffer(CellCount, sizeof(float), BufferUsages.StorageReadWrite); + pressureA = GraphicsHelper.CreateBuffer(CellCount, sizeof(float), BufferUsages.StorageReadWrite); + + resetPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "ResetCS"); + initializeGridPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "InitializeGridCS"); + clearGridPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "ClearGridCS"); + beginParticleToGridPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "BeginParticleToGridCS"); + particleToGridPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "ParticleToGridCS"); + normalizeAndApplyForcesPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "NormalizeAndApplyForcesCS"); + divergencePipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "DivergenceCS"); + pressureRedPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "PressureRedCS"); + pressureBlackPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "PressureBlackCS"); + projectGridPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "ProjectGridCS"); + gridToParticleAndAdvectPipeline = GraphicsHelper.CreateComputePipeline("FluidSimulation.slang", "GridToParticleAndAdvectCS"); + } + + public uint ParticleCount { get; } + + public uint CellCount { get; } + + public uint GridPointCount { get; } + + public (uint X, uint Y, uint Z) GridDimensions { get; } + + public ResourceHandle ParticleHandle => particles.StorageReadOnlyHandle; + + public ResourceHandle PreviousPositionHandle => previousPositions.StorageReadOnlyHandle; + + public const float ParticleRadius = GridSpacing * 0.316f; + + public const float RestDensity = 5.52f; + + public float FlipRatio { get; set; } = 0.97f; + + public float VelocityDamping { get; set; } = 0.9998f; + + public float WaveAmplitude { get; set; } = 0.12f; + + public float WaveFrequency { get; set; } = 0.58f; + + public bool WaveMakerEnabled { get; set; } + + public int PressureIterations + { + get; + set => field = Math.Clamp(value, 4, 32); + } = 18; + + public void Reset() + { + resetRequested = true; + } + + public void Push(Vector3 origin, Vector3 direction) + { + interactionOrigin = origin; + interactionDirection = Vector3.Normalize(direction); + interactionStrength = 4.8f; + } + + public TimelineValue Step(double totalTime, double deltaSeconds, bool paused) + { + const uint substeps = 2; + + if (paused && !resetRequested) + { + return ready; + } + + float frameTime = (float)deltaSeconds; + float timeStep = paused ? 0.0f : frameTime / substeps; + + SimulationConstants parameters = new() + { + TankMin = TankMin, + TimeStep = MathF.Max(timeStep, 0.000001f), + TankMax = TankMax, + Time = (float)totalTime, + GridSpacing = GridSpacing, + InverseGridSpacing = 1.0f / GridSpacing, + FlipRatio = FlipRatio, + VelocityDamping = VelocityDamping, + WaveAmplitude = WaveAmplitude, + WaveFrequency = WaveFrequency, + InteractionRadius = 1.15f, + InteractionStrength = interactionStrength, + InteractionOrigin = interactionOrigin, + ParticleRadius = ParticleRadius, + InteractionDirection = interactionDirection, + RestDensity = RestDensity, + ParticleCount = ParticleCount, + CellCount = CellCount, + GridPointCount = GridPointCount, + WaveMakerEnabled = WaveMakerEnabled ? 1u : 0u, + GridX = GridDimensions.X, + GridY = GridDimensions.Y, + GridZ = GridDimensions.Z, + PressureIterations = (uint)PressureIterations, + DamX = DamDimensions.X, + DamY = DamDimensions.Y, + DamZ = DamDimensions.Z, + Substeps = substeps, + Particles = particles.StorageReadWriteHandle, + PreviousPositions = previousPositions.StorageReadWriteHandle, + ParticleAffine = particleAffine.StorageReadWriteHandle, + GridAccumulation = gridAccumulation.StorageReadWriteHandle, + GridVelocity = gridVelocity.StorageReadWriteHandle, + GridVelocityOld = gridVelocityOld.StorageReadWriteHandle, + CellTypes = cellTypes.StorageReadWriteHandle, + Divergence = divergence.StorageReadWriteHandle, + PressureA = pressureA.StorageReadWriteHandle + }; + + constantBuffer.Upload(0, new() + { + Pointer = (nint)(¶meters), + SizeInBytes = (uint)sizeof(SimulationConstants) + }); + + CommandBuffer commandBuffer = App.Context.ComputeQueue.CommandBuffer(); + + if (resetRequested) + { + Dispatch(commandBuffer, resetPipeline, ParticleCount); + Dispatch(commandBuffer, initializeGridPipeline, CellCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + resetRequested = false; + } + + if (!paused) + { + for (uint substep = 0; substep < substeps; substep++) + { + Dispatch(commandBuffer, clearGridPipeline, Math.Max(GridPointCount * 6, CellCount)); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + + Dispatch(commandBuffer, substep is 0 ? beginParticleToGridPipeline : particleToGridPipeline, ParticleCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + + Dispatch(commandBuffer, normalizeAndApplyForcesPipeline, GridPointCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + + Dispatch(commandBuffer, divergencePipeline, CellCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + + for (int iteration = 0; iteration < PressureIterations; iteration++) + { + Dispatch(commandBuffer, pressureRedPipeline, pressureParityDispatchCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + + Dispatch(commandBuffer, pressureBlackPipeline, pressureParityDispatchCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + } + + Dispatch(commandBuffer, projectGridPipeline, GridPointCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + + Dispatch(commandBuffer, gridToParticleAndAdvectPipeline, ParticleCount); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + } + } + + if (!paused) + { + interactionStrength = 0.0f; + } + + return ready = commandBuffer.Submit(); + } + + public void Dispose() + { + gridToParticleAndAdvectPipeline.Dispose(); + projectGridPipeline.Dispose(); + pressureBlackPipeline.Dispose(); + pressureRedPipeline.Dispose(); + divergencePipeline.Dispose(); + normalizeAndApplyForcesPipeline.Dispose(); + particleToGridPipeline.Dispose(); + beginParticleToGridPipeline.Dispose(); + clearGridPipeline.Dispose(); + initializeGridPipeline.Dispose(); + resetPipeline.Dispose(); + + pressureA.Dispose(); + divergence.Dispose(); + cellTypes.Dispose(); + gridVelocityOld.Dispose(); + gridVelocity.Dispose(); + gridAccumulation.Dispose(); + particleAffine.Dispose(); + previousPositions.Dispose(); + particles.Dispose(); + constantBuffer.Dispose(); + } + + private void Dispatch(CommandBuffer commandBuffer, ComputePipeline pipeline, uint count) + { + uint groupSize = pipeline.Desc.ComputeShader.Desc.ThreadGroupSize.X; + + commandBuffer.SetPipeline(pipeline); + commandBuffer.SetConstantBuffer(constantBuffer, 0); + commandBuffer.Dispatch((count + groupSize - 1) / groupSize, 1, 1); + } +} + +[StructLayout(LayoutKind.Explicit, Size = 216)] +file struct SimulationConstants +{ + [FieldOffset(0)] + public Vector3 TankMin; + + [FieldOffset(12)] + public float TimeStep; + + [FieldOffset(16)] + public Vector3 TankMax; + + [FieldOffset(28)] + public float Time; + + [FieldOffset(32)] + public float GridSpacing; + + [FieldOffset(36)] + public float InverseGridSpacing; + + [FieldOffset(40)] + public float FlipRatio; + + [FieldOffset(44)] + public float VelocityDamping; + + [FieldOffset(48)] + public float WaveAmplitude; + + [FieldOffset(52)] + public float WaveFrequency; + + [FieldOffset(56)] + public float InteractionRadius; + + [FieldOffset(60)] + public float InteractionStrength; + + [FieldOffset(64)] + public Vector3 InteractionOrigin; + + [FieldOffset(76)] + public float ParticleRadius; + + [FieldOffset(80)] + public Vector3 InteractionDirection; + + [FieldOffset(92)] + public float RestDensity; + + [FieldOffset(96)] + public uint ParticleCount; + + [FieldOffset(100)] + public uint CellCount; + + [FieldOffset(104)] + public uint GridPointCount; + + [FieldOffset(108)] + public uint WaveMakerEnabled; + + [FieldOffset(112)] + public uint GridX; + + [FieldOffset(116)] + public uint GridY; + + [FieldOffset(120)] + public uint GridZ; + + [FieldOffset(124)] + public uint PressureIterations; + + [FieldOffset(128)] + public uint DamX; + + [FieldOffset(132)] + public uint DamY; + + [FieldOffset(136)] + public uint DamZ; + + [FieldOffset(140)] + public uint Substeps; + + [FieldOffset(144)] + public ResourceHandle Particles; + + [FieldOffset(152)] + public ResourceHandle PreviousPositions; + + [FieldOffset(160)] + public ResourceHandle ParticleAffine; + + [FieldOffset(168)] + public ResourceHandle GridAccumulation; + + [FieldOffset(176)] + public ResourceHandle GridVelocity; + + [FieldOffset(184)] + public ResourceHandle GridVelocityOld; + + [FieldOffset(192)] + public ResourceHandle CellTypes; + + [FieldOffset(200)] + public ResourceHandle Divergence; + + [FieldOffset(208)] + public ResourceHandle PressureA; +} diff --git a/sources/Experiments/FluidTank/FluidTank.csproj b/sources/Experiments/FluidTank/FluidTank.csproj new file mode 100644 index 00000000..08be1b19 --- /dev/null +++ b/sources/Experiments/FluidTank/FluidTank.csproj @@ -0,0 +1,26 @@ + + + + Exe + $(StandardTargetFramework) + + + + + + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/sources/Experiments/FluidTank/FluidTankRenderer.cs b/sources/Experiments/FluidTank/FluidTankRenderer.cs new file mode 100644 index 00000000..068a679d --- /dev/null +++ b/sources/Experiments/FluidTank/FluidTankRenderer.cs @@ -0,0 +1,897 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using FluidTank.Handlers; +using FluidTank.Helpers; +using Zenith.NET; +using Buffer = Zenith.NET.Buffer; + +namespace FluidTank; + +internal enum FluidViewMode +{ + Water, + Particles +} + +internal unsafe class FluidTankRenderer : IDisposable +{ + private const double FixedSimulationStep = 1.0 / 30.0; + private const int DepthSmoothingIterations = 4; + + private readonly FluidSimulation simulation; + private readonly Buffer sceneVertexBuffer; + private readonly Buffer sceneIndexBuffer; + private readonly Buffer glassVertexBuffer; + private readonly Buffer glassIndexBuffer; + private readonly Buffer materialBuffer; + private readonly Buffer sceneConstantBuffer; + private readonly Buffer surfaceConstantBuffer; + private readonly Buffer blurConstantBuffer; + private readonly Buffer compositeConstantBuffer; + private readonly Buffer reflectionConstantBuffer; + private readonly Buffer glassConstantBuffer; + private readonly Sampler linearSampler; + + private readonly GraphicsPipeline scenePipeline; + private readonly GraphicsPipeline fluidDepthPipeline; + private readonly GraphicsPipeline fluidThicknessPipeline; + private readonly GraphicsPipeline particlePipeline; + private readonly ComputePipeline blurPipeline; + private readonly ComputePipeline blurThicknessPipeline; + private readonly GraphicsPipeline compositePipeline; + private readonly GraphicsPipeline glassPipeline; + private readonly ComputePipeline? reflectionPipeline; + + private readonly BottomLevelAccelerationStructure? sceneBlas; + private readonly TopLevelAccelerationStructure? sceneTlas; + private readonly uint sceneIndexCount; + private readonly uint glassIndexCount; + + private Texture sceneColor = null!; + private Texture sceneLinearDepth = null!; + private Texture fluidAttributes = null!; + private Texture reconstructionDepth = null!; + private Texture smoothDepthA = null!; + private Texture smoothDepthB = null!; + private Texture smoothThicknessA = null!; + private Texture smoothThicknessB = null!; + private Texture? reflection; + + private Matrix4x4 view; + private Matrix4x4 projection; + private Vector3 cameraPosition; + private Vector3 cameraRight; + private Vector3 cameraUp; + private double totalTime; + private double simulationTime; + private double simulationAccumulator = FixedSimulationStep; + private TimelineValue simulationReady; + + public FluidTankRenderer() + { + FluidTankGeometry.CreateScene(out SceneVertex[] sceneVertices, out uint[] sceneIndices, out SceneMaterial[] materials); + FluidTankGeometry.CreateGlass(out SceneVertex[] glassVertices, out uint[] glassIndices); + + sceneIndexCount = (uint)sceneIndices.Length; + glassIndexCount = (uint)glassIndices.Length; + + CommandBuffer uploadCommandBuffer = App.Context.TransferQueue.CommandBuffer(); + sceneVertexBuffer = GraphicsHelper.LoadBuffer(uploadCommandBuffer, sceneVertices, BufferUsages.Vertex | BufferUsages.StorageReadOnly); + sceneIndexBuffer = GraphicsHelper.LoadBuffer(uploadCommandBuffer, sceneIndices, BufferUsages.Index | BufferUsages.StorageReadOnly); + glassVertexBuffer = GraphicsHelper.LoadBuffer(uploadCommandBuffer, glassVertices, BufferUsages.Vertex); + glassIndexBuffer = GraphicsHelper.LoadBuffer(uploadCommandBuffer, glassIndices, BufferUsages.Index); + materialBuffer = GraphicsHelper.LoadBuffer(uploadCommandBuffer, materials, BufferUsages.StorageReadOnly); + + uploadCommandBuffer.Submit().Wait(); + + sceneConstantBuffer = GraphicsHelper.CreateConstantBuffer(); + surfaceConstantBuffer = GraphicsHelper.CreateConstantBuffer(); + blurConstantBuffer = GraphicsHelper.CreateConstantBuffer(1024); + compositeConstantBuffer = GraphicsHelper.CreateConstantBuffer(); + reflectionConstantBuffer = GraphicsHelper.CreateConstantBuffer(); + glassConstantBuffer = GraphicsHelper.CreateConstantBuffer(); + linearSampler = App.Context.CreateSampler(SamplerDesc.LinearClamp()); + + Resize(App.Width, App.Height); + + InputLayout inputLayout = new(); + inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Position }); + inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Normal }); + + scenePipeline = GraphicsHelper.CreateGraphicsPipeline("Scene.slang", "VSMain", "FSMain", [inputLayout], new() + { + ColorFormats = [PixelFormat.R16G16B16A16Float, PixelFormat.R32Float], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, RasterizerState.CullBack(), DepthStencilState.DepthReadWrite(), BlendState.Opaque()); + + using Shader surfaceVertexShader = GraphicsHelper.LoadShader("FluidSurface.slang", "SurfaceVS"); + + fluidDepthPipeline = GraphicsHelper.CreateGraphicsPipeline(surfaceVertexShader, "FluidSurface.slang", "DepthFS", [], new() + { + ColorFormats = [PixelFormat.R32Float, PixelFormat.R16G16B16A16Float], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, RasterizerState.CullNone(), DepthStencilState.DepthReadWrite(), BlendState.Opaque(), PrimitiveTopology.TriangleStrip); + + fluidThicknessPipeline = GraphicsHelper.CreateGraphicsPipeline(surfaceVertexShader, "FluidSurface.slang", "ThicknessFS", [], new() + { + ColorFormats = [PixelFormat.R16Float], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, RasterizerState.CullNone(), DepthStencilState.DepthRead(), AdditiveBlend(), PrimitiveTopology.TriangleStrip); + + particlePipeline = GraphicsHelper.CreateGraphicsPipeline(surfaceVertexShader, "FluidSurface.slang", "ParticleFS", [], new() + { + ColorFormats = [PixelFormat.B8G8R8A8UNorm], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, RasterizerState.CullNone(), DepthStencilState.DepthReadWrite(), BlendState.Opaque(), PrimitiveTopology.TriangleStrip); + + blurPipeline = GraphicsHelper.CreateComputePipeline("FluidBlur.slang", "BlurCS"); + blurThicknessPipeline = GraphicsHelper.CreateComputePipeline("FluidBlur.slang", "BlurThicknessCS"); + + compositePipeline = GraphicsHelper.CreateGraphicsPipeline("FluidComposite.slang", "FullscreenVS", "CompositeFS", [], new() + { + ColorFormats = [PixelFormat.B8G8R8A8UNorm], + SampleCount = SampleCount.Count1 + }, RasterizerState.CullNone(), DepthStencilState.DepthNone(), BlendState.Opaque()); + + glassPipeline = GraphicsHelper.CreateGraphicsPipeline("Glass.slang", "GlassVS", "GlassFS", [inputLayout], new() + { + ColorFormats = [PixelFormat.B8G8R8A8UNorm], + DepthStencilFormat = PixelFormat.D32FloatS8UInt, + SampleCount = SampleCount.Count1 + }, RasterizerState.CullNone(), DepthStencilState.DepthRead(), BlendState.AlphaBlend()); + + simulation = new(); + + if (App.Context.Capabilities.RayTracingSupported) + { + CommandBuffer commandBuffer = App.Context.ComputeQueue.CommandBuffer(); + + sceneBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc + { + Geometries = + [ + new() + { + Type = RayTracingGeometryType.Triangle, + TriangleGeometry = new() + { + VertexBuffer = sceneVertexBuffer, + VertexFormat = PixelFormat.R32G32B32Float, + VertexCount = (uint)sceneVertices.Length, + VertexStrideInBytes = (uint)sizeof(SceneVertex), + IndexBuffer = sceneIndexBuffer, + IndexFormat = IndexFormat.UInt32, + IndexCount = sceneIndexCount, + Transform = Matrix4x4.Identity + }, + IsOpaque = true + } + ], + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace + }); + + sceneTlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc + { + Instances = + [ + new() + { + AccelerationStructure = sceneBlas, + InstanceId = 0, + VisibilityMask = 0xFF, + Transform = Matrix4x4.Identity, + Flags = RayTracingInstanceFlags.None + } + ], + BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace + }); + + commandBuffer.Submit().Wait(); + + reflectionPipeline = GraphicsHelper.CreateComputePipeline("FluidReflection.slang", "ReflectionCS"); + } + } + + public Texture Color { get; private set; } = null!; + + public Texture DepthStencil { get; private set; } = null!; + + public bool RayTracingEnabled => reflectionPipeline is not null; + + public uint ParticleCount => simulation.ParticleCount; + + public bool Paused { get; set; } + + public bool WaveMakerEnabled + { + get => simulation.WaveMakerEnabled; + set => simulation.WaveMakerEnabled = value; + } + + public float WaveAmplitude + { + get => simulation.WaveAmplitude; + set => simulation.WaveAmplitude = value; + } + + public float WaveFrequency + { + get => simulation.WaveFrequency; + set => simulation.WaveFrequency = value; + } + + public float FlipRatio + { + get => simulation.FlipRatio; + set => simulation.FlipRatio = value; + } + + public float VelocityDamping + { + get => simulation.VelocityDamping; + set => simulation.VelocityDamping = value; + } + + public int PressureIterations + { + get => simulation.PressureIterations; + set => simulation.PressureIterations = value; + } + + public float Clarity { get; set; } = 1.05f; + + public float RefractionStrength { get; set; } = 0.82f; + + public FluidViewMode ViewMode { get; set; } + + public void Update(CameraHandler camera, double delta) + { + view = camera.View; + projection = camera.Projection; + cameraPosition = camera.Position; + cameraRight = camera.Right; + cameraUp = camera.Up; + + if (!Paused) + { + double frameTime = Math.Min(delta, FixedSimulationStep); + totalTime += frameTime; + simulationAccumulator = Math.Min(simulationAccumulator + frameTime, FixedSimulationStep * 2.0); + } + } + + public void PushFluid(Vector3 origin, Vector3 direction) + { + simulation.Push(origin, direction); + } + + public void Reset() + { + simulation.Reset(); + Paused = false; + totalTime = 0.0; + simulationTime = 0.0; + simulationAccumulator = FixedSimulationStep; + } + + public TimelineValue Simulate() + { + if (Paused) + { + simulationReady = simulation.Step(simulationTime, FixedSimulationStep, true); + } + else if (simulationAccumulator >= FixedSimulationStep) + { + simulationTime += FixedSimulationStep; + simulationAccumulator -= FixedSimulationStep; + simulationReady = simulation.Step(simulationTime, FixedSimulationStep, false); + } + + UploadFrameConstants(); + + return simulationReady; + } + + public void RenderScene(CommandBuffer commandBuffer) + { + commandBuffer.Transition(sceneColor, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.Transition(sceneLinearDepth, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.Transition(DepthStencil, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment); + + commandBuffer.BeginRenderPass( + [ + ColorAttachment.Clear(sceneColor, new(0.0f, 0.0f, 0.0f, 1.0f)), + ColorAttachment.Clear(sceneLinearDepth, Vector4.Zero) + ], DepthStencilAttachment.Clear(DepthStencil, 1.0f, 0)); + commandBuffer.SetPipeline(scenePipeline); + commandBuffer.SetVertexBuffer(sceneVertexBuffer, 0, 0); + commandBuffer.SetIndexBuffer(sceneIndexBuffer, 0, IndexFormat.UInt32); + commandBuffer.SetConstantBuffer(sceneConstantBuffer, 0); + commandBuffer.DrawIndexed(sceneIndexCount, 1, 0, 0, 0); + commandBuffer.EndRenderPass(); + + commandBuffer.Transition(sceneColor, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + commandBuffer.Transition(sceneLinearDepth, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + } + + public void RenderFluid(CommandBuffer commandBuffer) + { + if (ViewMode is FluidViewMode.Particles) + { + RenderParticles(commandBuffer); + + return; + } + + commandBuffer.Transition(reconstructionDepth, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment); + commandBuffer.Transition(smoothThicknessA, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.BeginRenderPass([ColorAttachment.Clear(smoothThicknessA, Vector4.Zero)], DepthStencilAttachment.Clear(reconstructionDepth, 1.0f, 0)); + commandBuffer.SetPipeline(fluidThicknessPipeline); + commandBuffer.SetConstantBuffer(surfaceConstantBuffer, 0); + commandBuffer.Draw(4, simulation.ParticleCount, 0, 0); + commandBuffer.EndRenderPass(); + commandBuffer.Transition(smoothThicknessA, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + + commandBuffer.Transition(smoothDepthA, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.Transition(fluidAttributes, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + + commandBuffer.BeginRenderPass( + [ + ColorAttachment.Clear(smoothDepthA, Vector4.Zero), + ColorAttachment.Clear(fluidAttributes, Vector4.Zero) + ], DepthStencilAttachment.Clear(reconstructionDepth, 1.0f, 0)); + commandBuffer.SetPipeline(fluidDepthPipeline); + commandBuffer.SetConstantBuffer(surfaceConstantBuffer, 0); + commandBuffer.Draw(4, simulation.ParticleCount, 0, 0); + commandBuffer.EndRenderPass(); + + commandBuffer.Transition(smoothDepthA, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + commandBuffer.Transition(fluidAttributes, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + + uint reconstructionWidth = smoothDepthA.Desc.Width; + uint reconstructionHeight = smoothDepthA.Desc.Height; + + commandBuffer.SetPipeline(blurPipeline); + + for (int iteration = 0; iteration < DepthSmoothingIterations; iteration++) + { + TextureLayout smoothDepthBLayout = iteration is 0 ? TextureLayout.Undefined : TextureLayout.Sampled; + commandBuffer.Transition(smoothDepthB, default, smoothDepthBLayout, TextureLayout.Storage); + commandBuffer.SetConstantBuffer(blurConstantBuffer, 0); + GraphicsHelper.Dispatch(commandBuffer, blurPipeline, reconstructionWidth, reconstructionHeight); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + commandBuffer.Transition(smoothDepthB, default, TextureLayout.Storage, TextureLayout.Sampled); + + commandBuffer.Transition(smoothDepthA, default, TextureLayout.Sampled, TextureLayout.Storage); + commandBuffer.SetConstantBuffer(blurConstantBuffer, 256); + GraphicsHelper.Dispatch(commandBuffer, blurPipeline, reconstructionWidth, reconstructionHeight); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + commandBuffer.Transition(smoothDepthA, default, TextureLayout.Storage, TextureLayout.Sampled); + } + + commandBuffer.Transition(smoothThicknessB, default, TextureLayout.Undefined, TextureLayout.Storage); + commandBuffer.SetPipeline(blurThicknessPipeline); + commandBuffer.SetConstantBuffer(blurConstantBuffer, 512); + GraphicsHelper.Dispatch(commandBuffer, blurThicknessPipeline, reconstructionWidth, reconstructionHeight); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading); + commandBuffer.Transition(smoothThicknessB, default, TextureLayout.Storage, TextureLayout.Sampled); + + commandBuffer.Transition(smoothThicknessA, default, TextureLayout.Sampled, TextureLayout.Storage); + commandBuffer.SetConstantBuffer(blurConstantBuffer, 768); + GraphicsHelper.Dispatch(commandBuffer, blurThicknessPipeline, reconstructionWidth, reconstructionHeight); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.FragmentShading); + commandBuffer.Transition(smoothThicknessA, default, TextureLayout.Storage, TextureLayout.Sampled); + + if (reflectionPipeline is not null) + { + commandBuffer.Transition(reflection!, default, TextureLayout.Undefined, TextureLayout.Storage); + commandBuffer.SetPipeline(reflectionPipeline); + commandBuffer.SetConstantBuffer(reflectionConstantBuffer, 0); + GraphicsHelper.Dispatch(commandBuffer, reflectionPipeline, reconstructionWidth, reconstructionHeight); + commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.FragmentShading); + commandBuffer.Transition(reflection!, default, TextureLayout.Storage, TextureLayout.Sampled); + } + + commandBuffer.Transition(Color, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.BeginRenderPass([ColorAttachment.DontCare(Color)], null); + commandBuffer.SetPipeline(compositePipeline); + commandBuffer.SetConstantBuffer(compositeConstantBuffer, 0); + commandBuffer.Draw(3, 1, 0, 0); + commandBuffer.EndRenderPass(); + + commandBuffer.Transition(Color, default, TextureLayout.ColorAttachment, TextureLayout.ColorAttachment); + commandBuffer.Transition(DepthStencil, default, TextureLayout.DepthStencilAttachment, TextureLayout.DepthStencilAttachment); + commandBuffer.BeginRenderPass([ColorAttachment.Load(Color)], DepthStencilAttachment.Load(DepthStencil)); + DrawGlass(commandBuffer); + commandBuffer.EndRenderPass(); + + commandBuffer.Transition(Color, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + } + + public void Resize(uint width, uint height) + { + DisposeTargets(); + + Color = GraphicsHelper.CreateTexture(PixelFormat.B8G8R8A8UNorm, width, height, TextureUsages.Sampled | TextureUsages.ColorAttachment); + DepthStencil = GraphicsHelper.CreateTexture(PixelFormat.D32FloatS8UInt, width, height, TextureUsages.DepthStencilAttachment); + sceneColor = GraphicsHelper.CreateTexture(PixelFormat.R16G16B16A16Float, width, height, TextureUsages.Sampled | TextureUsages.ColorAttachment); + sceneLinearDepth = GraphicsHelper.CreateTexture(PixelFormat.R32Float, width, height, TextureUsages.Sampled | TextureUsages.ColorAttachment); + uint reconstructionWidth = Math.Max((width + 2) / 3, 1u); + uint reconstructionHeight = Math.Max((height + 2) / 3, 1u); + reconstructionDepth = GraphicsHelper.CreateTexture(PixelFormat.D32FloatS8UInt, reconstructionWidth, reconstructionHeight, TextureUsages.DepthStencilAttachment); + fluidAttributes = GraphicsHelper.CreateTexture(PixelFormat.R16G16B16A16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.ColorAttachment); + smoothDepthA = GraphicsHelper.CreateTexture(PixelFormat.R32Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment); + smoothDepthB = GraphicsHelper.CreateTexture(PixelFormat.R32Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage); + smoothThicknessA = GraphicsHelper.CreateTexture(PixelFormat.R16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment); + smoothThicknessB = GraphicsHelper.CreateTexture(PixelFormat.R16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage); + reflection = App.Context.Capabilities.RayTracingSupported + ? GraphicsHelper.CreateTexture(PixelFormat.R16G16B16A16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage) + : null; + } + + public void Dispose() + { + simulation.Dispose(); + + sceneTlas?.Dispose(); + sceneBlas?.Dispose(); + reflectionPipeline?.Dispose(); + glassPipeline.Dispose(); + compositePipeline.Dispose(); + blurThicknessPipeline.Dispose(); + blurPipeline.Dispose(); + particlePipeline.Dispose(); + fluidThicknessPipeline.Dispose(); + fluidDepthPipeline.Dispose(); + scenePipeline.Dispose(); + + linearSampler.Dispose(); + glassConstantBuffer.Dispose(); + reflectionConstantBuffer.Dispose(); + compositeConstantBuffer.Dispose(); + blurConstantBuffer.Dispose(); + surfaceConstantBuffer.Dispose(); + sceneConstantBuffer.Dispose(); + materialBuffer.Dispose(); + glassIndexBuffer.Dispose(); + glassVertexBuffer.Dispose(); + sceneIndexBuffer.Dispose(); + sceneVertexBuffer.Dispose(); + + DisposeTargets(); + } + + private void UploadFrameConstants() + { + Matrix4x4.Invert(view, out Matrix4x4 invView); + Matrix4x4.Invert(projection, out Matrix4x4 invProjection); + Vector3 sunDirection = Vector3.Normalize(new(-0.38f, -0.83f, -0.42f)); + float surfaceRadius = FluidSimulation.ParticleRadius * (ViewMode is FluidViewMode.Particles ? 0.78f : 1.28f); + float interpolationAlpha = Paused ? 1.0f : (float)Math.Clamp(simulationAccumulator / FixedSimulationStep, 0.0, 1.0); + + SceneConstants scene = new() + { + View = view, + Projection = projection, + CameraPosition = cameraPosition, + Time = (float)totalTime, + LightDirection = sunDirection, + LightIntensity = 1.55f, + Materials = materialBuffer.StorageReadOnlyHandle + }; + GraphicsHelper.Upload(sceneConstantBuffer, 0, &scene, (uint)sizeof(SceneConstants)); + + SurfaceConstants surface = new() + { + View = view, + Projection = projection, + CameraRight = cameraRight, + ParticleRadius = surfaceRadius, + CameraUp = cameraUp, + RestDensity = FluidSimulation.RestDensity, + ParticleCount = simulation.ParticleCount, + RenderMode = (uint)ViewMode, + Width = ViewMode is FluidViewMode.Particles ? App.Width : smoothDepthA.Desc.Width, + Height = ViewMode is FluidViewMode.Particles ? App.Height : smoothDepthA.Desc.Height, + InterpolationAlpha = interpolationAlpha, + Particles = simulation.ParticleHandle, + PreviousPositions = simulation.PreviousPositionHandle, + SceneDepth = sceneLinearDepth.SampledHandle, + Sampler = linearSampler.Handle + }; + GraphicsHelper.Upload(surfaceConstantBuffer, 0, &surface, (uint)sizeof(SurfaceConstants)); + + uint reconstructionWidth = smoothDepthA.Desc.Width; + uint reconstructionHeight = smoothDepthA.Desc.Height; + + CompositeConstants composite = new() + { + InvView = invView, + InvProjection = invProjection, + CameraPosition = cameraPosition, + Time = (float)totalTime, + SunDirection = sunDirection, + Clarity = Clarity, + WaterColor = new(0.018f, 0.24f, 0.34f), + RefractionStrength = RefractionStrength, + Absorption = new(0.54f, 0.14f, 0.052f), + Ior = 1.333f, + Width = reconstructionWidth, + Height = reconstructionHeight, + RenderMode = (uint)ViewMode, + RayTracingEnabled = reflectionPipeline is not null ? 1u : 0u, + SceneColor = sceneColor.SampledHandle, + SceneDepth = sceneLinearDepth.SampledHandle, + FluidDepth = smoothDepthA.SampledHandle, + Thickness = smoothThicknessA.SampledHandle, + Attributes = fluidAttributes.SampledHandle, + Reflection = reflection?.SampledHandle ?? default, + Sampler = linearSampler.Handle + }; + GraphicsHelper.Upload(compositeConstantBuffer, 0, &composite, (uint)sizeof(CompositeConstants)); + + if (ViewMode is FluidViewMode.Water) + { + BlurConstants horizontal = new() + { + Width = reconstructionWidth, + Height = reconstructionHeight, + Direction = 0, + Radius = 18, + SpatialSigma = 4.0f, + DepthSigma = surfaceRadius * 1.4f, + ProjectedRadiusScale = surfaceRadius * projection.M22 * reconstructionHeight * 0.5f, + InputTexture = smoothDepthA.SampledHandle, + OutputTexture = smoothDepthB.StorageHandle + }; + GraphicsHelper.Upload(blurConstantBuffer, 0, &horizontal, (uint)sizeof(BlurConstants)); + + BlurConstants vertical = horizontal; + vertical.Direction = 1; + vertical.InputTexture = smoothDepthB.SampledHandle; + vertical.OutputTexture = smoothDepthA.StorageHandle; + GraphicsHelper.Upload(blurConstantBuffer, 256, &vertical, (uint)sizeof(BlurConstants)); + + BlurConstants horizontalThickness = horizontal; + horizontalThickness.Radius = 12; + horizontalThickness.SpatialSigma = 6.0f; + horizontalThickness.InputTexture = smoothThicknessA.SampledHandle; + horizontalThickness.OutputTexture = smoothThicknessB.StorageHandle; + GraphicsHelper.Upload(blurConstantBuffer, 512, &horizontalThickness, (uint)sizeof(BlurConstants)); + + BlurConstants verticalThickness = horizontalThickness; + verticalThickness.Direction = 1; + verticalThickness.InputTexture = smoothThicknessB.SampledHandle; + verticalThickness.OutputTexture = smoothThicknessA.StorageHandle; + GraphicsHelper.Upload(blurConstantBuffer, 768, &verticalThickness, (uint)sizeof(BlurConstants)); + + if (reflectionPipeline is not null) + { + ReflectionConstants reflectionParameters = new() + { + InvView = invView, + InvProjection = invProjection, + CameraPosition = cameraPosition, + Time = (float)totalTime, + SunDirection = sunDirection, + Width = reconstructionWidth, + Height = reconstructionHeight, + FluidDepth = smoothDepthA.SampledHandle, + Scene = sceneTlas!.Handle, + Vertices = sceneVertexBuffer.StorageReadOnlyHandle, + Indices = sceneIndexBuffer.StorageReadOnlyHandle, + Materials = materialBuffer.StorageReadOnlyHandle, + OutputTexture = reflection!.StorageHandle + }; + GraphicsHelper.Upload(reflectionConstantBuffer, 0, &reflectionParameters, (uint)sizeof(ReflectionConstants)); + } + } + + GlassConstants glass = new() + { + View = view, + Projection = projection, + CameraPosition = cameraPosition, + Time = (float)totalTime + }; + GraphicsHelper.Upload(glassConstantBuffer, 0, &glass, (uint)sizeof(GlassConstants)); + } + + private void RenderParticles(CommandBuffer commandBuffer) + { + commandBuffer.Transition(Color, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + commandBuffer.BeginRenderPass([ColorAttachment.DontCare(Color)], null); + commandBuffer.SetPipeline(compositePipeline); + commandBuffer.SetConstantBuffer(compositeConstantBuffer, 0); + commandBuffer.Draw(3, 1, 0, 0); + commandBuffer.EndRenderPass(); + + commandBuffer.Transition(Color, default, TextureLayout.ColorAttachment, TextureLayout.ColorAttachment); + commandBuffer.Transition(DepthStencil, default, TextureLayout.DepthStencilAttachment, TextureLayout.DepthStencilAttachment); + commandBuffer.BeginRenderPass([ColorAttachment.Load(Color)], DepthStencilAttachment.Load(DepthStencil)); + commandBuffer.SetPipeline(particlePipeline); + commandBuffer.SetConstantBuffer(surfaceConstantBuffer, 0); + commandBuffer.Draw(4, simulation.ParticleCount, 0, 0); + DrawGlass(commandBuffer); + commandBuffer.EndRenderPass(); + + commandBuffer.Transition(Color, default, TextureLayout.ColorAttachment, TextureLayout.Sampled); + } + + private void DrawGlass(CommandBuffer commandBuffer) + { + commandBuffer.SetPipeline(glassPipeline); + commandBuffer.SetVertexBuffer(glassVertexBuffer, 0, 0); + commandBuffer.SetIndexBuffer(glassIndexBuffer, 0, IndexFormat.UInt32); + commandBuffer.SetConstantBuffer(glassConstantBuffer, 0); + commandBuffer.DrawIndexed(glassIndexCount, 1, 0, 0, 0); + } + + private void DisposeTargets() + { + reflection?.Dispose(); + reconstructionDepth?.Dispose(); + smoothThicknessB?.Dispose(); + smoothThicknessA?.Dispose(); + smoothDepthB?.Dispose(); + smoothDepthA?.Dispose(); + fluidAttributes?.Dispose(); + sceneLinearDepth?.Dispose(); + sceneColor?.Dispose(); + DepthStencil?.Dispose(); + Color?.Dispose(); + } + + private static BlendState AdditiveBlend() + { + return new() + { + ColorAttachment0 = new() + { + IsBlendingEnabled = true, + SrcRgbFactor = BlendFactor.One, + DstRgbFactor = BlendFactor.One, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.One, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + } + }; + } +} + +[StructLayout(LayoutKind.Explicit, Size = 176)] +file struct SceneConstants +{ + [FieldOffset(0)] + public Matrix4x4 View; + + [FieldOffset(64)] + public Matrix4x4 Projection; + + [FieldOffset(128)] + public Vector3 CameraPosition; + + [FieldOffset(140)] + public float Time; + + [FieldOffset(144)] + public Vector3 LightDirection; + + [FieldOffset(156)] + public float LightIntensity; + + [FieldOffset(160)] + public ResourceHandle Materials; +} + +[StructLayout(LayoutKind.Explicit, Size = 224)] +file struct SurfaceConstants +{ + [FieldOffset(0)] + public Matrix4x4 View; + + [FieldOffset(64)] + public Matrix4x4 Projection; + + [FieldOffset(128)] + public Vector3 CameraRight; + + [FieldOffset(140)] + public float ParticleRadius; + + [FieldOffset(144)] + public Vector3 CameraUp; + + [FieldOffset(156)] + public float RestDensity; + + [FieldOffset(160)] + public uint ParticleCount; + + [FieldOffset(164)] + public uint RenderMode; + + [FieldOffset(168)] + public uint Width; + + [FieldOffset(172)] + public uint Height; + + [FieldOffset(176)] + public float InterpolationAlpha; + + [FieldOffset(192)] + public ResourceHandle Particles; + + [FieldOffset(200)] + public ResourceHandle PreviousPositions; + + [FieldOffset(208)] + public ResourceHandle SceneDepth; + + [FieldOffset(216)] + public ResourceHandle Sampler; +} + +[StructLayout(LayoutKind.Explicit, Size = 48)] +file struct BlurConstants +{ + [FieldOffset(0)] + public uint Width; + + [FieldOffset(4)] + public uint Height; + + [FieldOffset(8)] + public uint Direction; + + [FieldOffset(12)] + public uint Radius; + + [FieldOffset(16)] + public float SpatialSigma; + + [FieldOffset(20)] + public float DepthSigma; + + [FieldOffset(24)] + public float ProjectedRadiusScale; + + [FieldOffset(32)] + public ResourceHandle InputTexture; + + [FieldOffset(40)] + public ResourceHandle OutputTexture; +} + +[StructLayout(LayoutKind.Explicit, Size = 272)] +file struct CompositeConstants +{ + [FieldOffset(0)] + public Matrix4x4 InvView; + + [FieldOffset(64)] + public Matrix4x4 InvProjection; + + [FieldOffset(128)] + public Vector3 CameraPosition; + + [FieldOffset(140)] + public float Time; + + [FieldOffset(144)] + public Vector3 SunDirection; + + [FieldOffset(156)] + public float Clarity; + + [FieldOffset(160)] + public Vector3 WaterColor; + + [FieldOffset(172)] + public float RefractionStrength; + + [FieldOffset(176)] + public Vector3 Absorption; + + [FieldOffset(188)] + public float Ior; + + [FieldOffset(192)] + public uint Width; + + [FieldOffset(196)] + public uint Height; + + [FieldOffset(200)] + public uint RenderMode; + + [FieldOffset(204)] + public uint RayTracingEnabled; + + [FieldOffset(208)] + public ResourceHandle SceneColor; + + [FieldOffset(216)] + public ResourceHandle SceneDepth; + + [FieldOffset(224)] + public ResourceHandle FluidDepth; + + [FieldOffset(232)] + public ResourceHandle Thickness; + + [FieldOffset(240)] + public ResourceHandle Attributes; + + [FieldOffset(248)] + public ResourceHandle Reflection; + + [FieldOffset(256)] + public ResourceHandle Sampler; +} + +[StructLayout(LayoutKind.Explicit, Size = 224)] +file struct ReflectionConstants +{ + [FieldOffset(0)] + public Matrix4x4 InvView; + + [FieldOffset(64)] + public Matrix4x4 InvProjection; + + [FieldOffset(128)] + public Vector3 CameraPosition; + + [FieldOffset(140)] + public float Time; + + [FieldOffset(144)] + public Vector3 SunDirection; + + [FieldOffset(160)] + public uint Width; + + [FieldOffset(164)] + public uint Height; + + [FieldOffset(176)] + public ResourceHandle FluidDepth; + + [FieldOffset(184)] + public ResourceHandle Scene; + + [FieldOffset(192)] + public ResourceHandle Vertices; + + [FieldOffset(200)] + public ResourceHandle Indices; + + [FieldOffset(208)] + public ResourceHandle Materials; + + [FieldOffset(216)] + public ResourceHandle OutputTexture; +} + +[StructLayout(LayoutKind.Explicit, Size = 144)] +file struct GlassConstants +{ + [FieldOffset(0)] + public Matrix4x4 View; + + [FieldOffset(64)] + public Matrix4x4 Projection; + + [FieldOffset(128)] + public Vector3 CameraPosition; + + [FieldOffset(140)] + public float Time; +} diff --git a/sources/Experiments/FluidTank/Handlers/CameraHandler.cs b/sources/Experiments/FluidTank/Handlers/CameraHandler.cs new file mode 100644 index 00000000..a062aa82 --- /dev/null +++ b/sources/Experiments/FluidTank/Handlers/CameraHandler.cs @@ -0,0 +1,175 @@ +using System.Numerics; +using Silk.NET.Input; + +namespace FluidTank.Handlers; + +internal class CameraHandler +{ + private readonly HashSet keyDowns = []; + + private Vector2? lastMousePosition; + private Vector2? clickPosition; + + public CameraHandler(IInputContext input, Vector3 position, Vector3 target) + { + IMouse mouse = input.Mice[0]; + mouse.MouseDown += OnMouseDown; + mouse.MouseUp += OnMouseUp; + mouse.MouseMove += OnMouseMove; + + IKeyboard keyboard = input.Keyboards[0]; + keyboard.KeyDown += OnKeyDown; + keyboard.KeyUp += OnKeyUp; + + Position = position; + Forward = Vector3.Normalize(target - position); + Right = Vector3.Normalize(Vector3.Cross(Forward, Vector3.UnitY)); + Up = Vector3.Normalize(Vector3.Cross(Right, Forward)); + } + + public Vector2 Size { get; private set; } = new(800, 600); + + public Vector3 Position { get; private set; } + + public Vector3 Forward { get; private set; } + + public Vector3 Right { get; private set; } + + public Vector3 Up { get; private set; } + + public float NearPlane { get; set; } = 0.1f; + + public float FarPlane { get; set; } = 200.0f; + + public float Fov { get; set; } = 45.0f; + + public float Speed { get; set; } = 12.0f; + + public float AspectRatio => Size.X / Size.Y; + + public Matrix4x4 View => Matrix4x4.CreateLookAt(Position, Position + Forward, Up); + + public Matrix4x4 Projection => Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(Fov), AspectRatio, NearPlane, FarPlane); + + public void Update(double delta, uint width, uint height) + { + Size = new(width, height); + + float distance = Speed * (float)delta; + + if (keyDowns.Contains(Key.W)) + { + Position += Forward * distance; + } + + if (keyDowns.Contains(Key.S)) + { + Position -= Forward * distance; + } + + if (keyDowns.Contains(Key.A)) + { + Position -= Right * distance; + } + + if (keyDowns.Contains(Key.D)) + { + Position += Right * distance; + } + + if (keyDowns.Contains(Key.Q)) + { + Position -= Up * distance; + } + + if (keyDowns.Contains(Key.E)) + { + Position += Up * distance; + } + } + + public bool TryConsumeClickRay(out Vector3 origin, out Vector3 direction) + { + if (!clickPosition.HasValue) + { + origin = default; + direction = default; + + return false; + } + + Vector2 position = clickPosition.Value; + clickPosition = null; + + float x = (position.X / Size.X * 2.0f) - 1.0f; + float y = 1.0f - (position.Y / Size.Y * 2.0f); + + Matrix4x4.Invert(Projection, out Matrix4x4 invProjection); + Matrix4x4.Invert(View, out Matrix4x4 invView); + + Vector4 target = Vector4.Transform(new Vector4(x, y, 1.0f, 1.0f), invProjection); + Vector3 localDirection = Vector3.Normalize(new(target.X / target.W, target.Y / target.W, target.Z / target.W)); + + origin = Position; + direction = Vector3.Normalize(Vector3.TransformNormal(localDirection, invView)); + + return true; + } + + private void OnMouseDown(IMouse mouse, MouseButton button) + { + if (button is MouseButton.Right) + { + lastMousePosition = mouse.Position; + } + else if (button is MouseButton.Left) + { + clickPosition = mouse.Position; + } + } + + private void OnMouseUp(IMouse mouse, MouseButton button) + { + if (button is MouseButton.Right) + { + lastMousePosition = null; + } + } + + private void OnMouseMove(IMouse mouse, Vector2 position) + { + const float clipRadians = 89.0f * MathF.PI / 180.0f; + + if (!lastMousePosition.HasValue) + { + return; + } + + float pixelToRadianX = MathF.PI / Size.X; + float pixelToRadianY = MathF.PI / Size.Y; + Vector2 delta = position - lastMousePosition.Value; + float yaw = -(delta.X * pixelToRadianX); + float pitch = -(delta.Y * pixelToRadianY); + float newPitch = Math.Clamp(MathF.Asin(Forward.Y) + pitch, -clipRadians, clipRadians); + + pitch = newPitch - MathF.Asin(Forward.Y); + + Forward = Vector3.TransformNormal(Forward, Matrix4x4.CreateFromAxisAngle(Up, yaw)); + Forward = Vector3.TransformNormal(Forward, Matrix4x4.CreateFromAxisAngle(Right, pitch)); + Forward = Vector3.Normalize(Forward); + Right = Vector3.Normalize(Vector3.Cross(Forward, Vector3.UnitY)); + Up = Vector3.Normalize(Vector3.Cross(Right, Forward)); + + lastMousePosition = position; + } + + private void OnKeyDown(IKeyboard keyboard, Key key, int scanCode) + { + keyDowns.Add(key); + } + + private void OnKeyUp(IKeyboard keyboard, Key key, int scanCode) + { + keyDowns.Remove(key); + } +} \ No newline at end of file diff --git a/sources/Experiments/FluidTank/Handlers/ImGuiHandler.cs b/sources/Experiments/FluidTank/Handlers/ImGuiHandler.cs new file mode 100644 index 00000000..7a98b6d7 --- /dev/null +++ b/sources/Experiments/FluidTank/Handlers/ImGuiHandler.cs @@ -0,0 +1,196 @@ +using System.Numerics; +using Hexa.NET.ImGui; +using Silk.NET.Input; +using Zenith.NET; +using Zenith.NET.Extensions.ImGui; + +namespace FluidTank.Handlers; + +internal class ImGuiHandler : ImGuiController, IImGuiPlatformBindings +{ + private readonly IMouse mouse; + private readonly IKeyboard keyboard; + + 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; + mouse.MouseUp += OnMouseUp; + mouse.MouseMove += OnMouseMove; + mouse.Scroll += OnMouseScroll; + + keyboard = input.Keyboards[0]; + keyboard.KeyDown += OnKeyDown; + keyboard.KeyUp += OnKeyUp; + keyboard.KeyChar += OnKeyChar; + + PlatformBindings = this; + } + + private void OnMouseDown(IMouse mouse, MouseButton button) + { + MouseDown(button switch + { + MouseButton.Left => ImGuiMouseButton.Left, + MouseButton.Right => ImGuiMouseButton.Right, + MouseButton.Middle => ImGuiMouseButton.Middle, + _ => (int)ImGuiMouseButton.Count + (int)button - ImGuiMouseButton.Middle + }); + } + + private void OnMouseUp(IMouse mouse, MouseButton button) + { + MouseUp(button switch + { + MouseButton.Left => ImGuiMouseButton.Left, + MouseButton.Right => ImGuiMouseButton.Right, + MouseButton.Middle => ImGuiMouseButton.Middle, + _ => (int)ImGuiMouseButton.Count + (int)button - ImGuiMouseButton.Middle + }); + } + + private void OnMouseMove(IMouse mouse, Vector2 position) + { + MouseMove(position); + } + + private void OnMouseScroll(IMouse mouse, ScrollWheel offset) + { + MouseWheel(new(offset.X, offset.Y)); + } + + private void OnKeyDown(IKeyboard keyboard, Key key, int scanCode) + { + KeyDown(TranslateInputKeyToImGuiKey(key)); + KeyDown(TranslateInputKeyToImGuiModifier(key)); + } + + private void OnKeyUp(IKeyboard keyboard, Key key, int scanCode) + { + KeyUp(TranslateInputKeyToImGuiKey(key)); + KeyUp(TranslateInputKeyToImGuiModifier(key)); + } + + private void OnKeyChar(IKeyboard keyboard, char c) + { + KeyChar(c); + } + + private static ImGuiKey TranslateInputKeyToImGuiKey(Key key) + { + return key switch + { + Key.Tab => ImGuiKey.Tab, + Key.Left => ImGuiKey.LeftArrow, + Key.Right => ImGuiKey.RightArrow, + Key.Up => ImGuiKey.UpArrow, + Key.Down => ImGuiKey.DownArrow, + Key.PageUp => ImGuiKey.PageUp, + Key.PageDown => ImGuiKey.PageDown, + Key.Home => ImGuiKey.Home, + Key.End => ImGuiKey.End, + Key.Insert => ImGuiKey.Insert, + Key.Delete => ImGuiKey.Delete, + Key.Backspace => ImGuiKey.Backspace, + Key.Space => ImGuiKey.Space, + Key.Enter => ImGuiKey.Enter, + Key.Escape => ImGuiKey.Escape, + Key.Apostrophe => ImGuiKey.Apostrophe, + Key.Comma => ImGuiKey.Comma, + Key.Minus => ImGuiKey.Minus, + Key.Period => ImGuiKey.Period, + Key.Slash => ImGuiKey.Slash, + Key.Semicolon => ImGuiKey.Semicolon, + Key.Equal => ImGuiKey.Equal, + Key.LeftBracket => ImGuiKey.LeftBracket, + Key.BackSlash => ImGuiKey.Backslash, + Key.RightBracket => ImGuiKey.RightBracket, + Key.GraveAccent => ImGuiKey.GraveAccent, + Key.CapsLock => ImGuiKey.CapsLock, + Key.ScrollLock => ImGuiKey.ScrollLock, + Key.NumLock => ImGuiKey.NumLock, + Key.PrintScreen => ImGuiKey.PrintScreen, + Key.Pause => ImGuiKey.Pause, + Key.Keypad0 => ImGuiKey.Keypad0, + Key.Keypad1 => ImGuiKey.Keypad1, + Key.Keypad2 => ImGuiKey.Keypad2, + Key.Keypad3 => ImGuiKey.Keypad3, + Key.Keypad4 => ImGuiKey.Keypad4, + Key.Keypad5 => ImGuiKey.Keypad5, + Key.Keypad6 => ImGuiKey.Keypad6, + Key.Keypad7 => ImGuiKey.Keypad7, + Key.Keypad8 => ImGuiKey.Keypad8, + Key.Keypad9 => ImGuiKey.Keypad9, + Key.KeypadDecimal => ImGuiKey.KeypadDecimal, + Key.KeypadDivide => ImGuiKey.KeypadDivide, + Key.KeypadMultiply => ImGuiKey.KeypadMultiply, + Key.KeypadSubtract => ImGuiKey.KeypadSubtract, + Key.KeypadAdd => ImGuiKey.KeypadAdd, + Key.KeypadEnter => ImGuiKey.KeypadEnter, + Key.KeypadEqual => ImGuiKey.KeypadEqual, + Key.ShiftLeft => ImGuiKey.LeftShift, + Key.ControlLeft => ImGuiKey.LeftCtrl, + Key.AltLeft => ImGuiKey.LeftAlt, + Key.SuperLeft => ImGuiKey.LeftSuper, + Key.ShiftRight => ImGuiKey.RightShift, + Key.ControlRight => ImGuiKey.RightCtrl, + Key.AltRight => ImGuiKey.RightAlt, + Key.SuperRight => ImGuiKey.RightSuper, + Key.Menu => ImGuiKey.Menu, + >= Key.Number0 and <= Key.Number9 => ImGuiKey.Key0 + (key - Key.Number0), + >= Key.A and <= Key.Z => ImGuiKey.A + (key - Key.A), + >= Key.F1 and <= Key.F24 => ImGuiKey.F1 + (key - Key.F1), + _ => ImGuiKey.None + }; + } + + private static ImGuiKey TranslateInputKeyToImGuiModifier(Key key) + { + return key switch + { + Key.ShiftLeft or Key.ShiftRight => ImGuiKey.ModShift, + Key.ControlLeft or Key.ControlRight => ImGuiKey.ModCtrl, + Key.AltLeft or Key.AltRight => ImGuiKey.ModAlt, + Key.SuperLeft or Key.SuperRight => ImGuiKey.ModSuper, + _ => ImGuiKey.None + }; + } + + private static unsafe void OtherSetup(ImGuiIOPtr io) + { + io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; + io.DisplayFramebufferScale = App.DpiScale; + io.IniFilename = null; + } + + public void SetCursor(ImGuiMouseCursor cursor) + { + mouse.Cursor.StandardCursor = cursor switch + { + ImGuiMouseCursor.Arrow => StandardCursor.Arrow, + ImGuiMouseCursor.TextInput => StandardCursor.IBeam, + ImGuiMouseCursor.ResizeAll => StandardCursor.ResizeAll, + ImGuiMouseCursor.ResizeNs => StandardCursor.VResize, + ImGuiMouseCursor.ResizeEw => StandardCursor.HResize, + ImGuiMouseCursor.ResizeNesw => StandardCursor.NeswResize, + ImGuiMouseCursor.ResizeNwse => StandardCursor.NwseResize, + ImGuiMouseCursor.Hand => StandardCursor.Hand, + ImGuiMouseCursor.NotAllowed => StandardCursor.NotAllowed, + _ => StandardCursor.Default + }; + } + + public string GetClipboardText() + { + return keyboard.ClipboardText; + } + + public void SetClipboardText(string text) + { + keyboard.ClipboardText = text; + } + + public void SetImeData(ImGuiViewportPtr viewport, ImGuiPlatformImeDataPtr data) + { + } +} \ No newline at end of file diff --git a/sources/Experiments/FluidTank/Helpers/CocoaHelper.cs b/sources/Experiments/FluidTank/Helpers/CocoaHelper.cs new file mode 100644 index 00000000..dfe56d56 --- /dev/null +++ b/sources/Experiments/FluidTank/Helpers/CocoaHelper.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace FluidTank.Helpers; + +internal static partial class CocoaHelper +{ + private const string LibObjC = "/usr/lib/libobjc.A.dylib"; + + [LibraryImport(LibObjC, EntryPoint = "objc_getClass")] + private static partial nint GetClass([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [LibraryImport(LibObjC, EntryPoint = "sel_registerName")] + private static partial nint Selector([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint Send(nint receiver, nint selector); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint Send(nint receiver, nint selector, [MarshalAs(UnmanagedType.I1)] bool arg); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint Send(nint receiver, nint selector, nint arg); + + public static nint CreateLayer(nint cocoa) + { + nint layer = Send(GetClass("CAMetalLayer"), Selector("layer")); + nint view = Send(cocoa, Selector("contentView")); + + Send(view, Selector("setWantsLayer:"), true); + Send(view, Selector("setLayer:"), layer); + + return layer; + } +} \ No newline at end of file diff --git a/sources/Experiments/FluidTank/Helpers/FluidTankGeometry.cs b/sources/Experiments/FluidTank/Helpers/FluidTankGeometry.cs new file mode 100644 index 00000000..0344ef6d --- /dev/null +++ b/sources/Experiments/FluidTank/Helpers/FluidTankGeometry.cs @@ -0,0 +1,190 @@ +using System.Numerics; +using System.Runtime.InteropServices; + +namespace FluidTank.Helpers; + +internal static class FluidTankGeometry +{ + public static void CreateScene(out SceneVertex[] vertices, out uint[] indices, out SceneMaterial[] materials) + { + List verticesList = []; + List indicesList = []; + + AddBox(verticesList, indicesList, new(-6.25f, -0.28f, -3.25f), new(6.25f, 0.0f, 3.25f), 0); + AddBox(verticesList, indicesList, new(-2.17f, 0.0f, 0.23f), new(-0.73f, 1.64f, 1.67f), 1); + AddCylinder(verticesList, indicesList, new(1.15f, 0.0f, -0.85f), 0.62f, 1.84f, 32, 2); + AddTransformedBox(verticesList, + indicesList, + new(3.45f, 0.62f, 0.55f), + new(1.15f, 0.16f, 1.05f), + Matrix4x4.CreateRotationZ(-0.35f), + 3); + + const float frameWidth = 0.075f; + + AddBox(verticesList, indicesList, new(-6.10f, 0.0f, -3.10f), new(6.10f, frameWidth, -2.95f), 4); + AddBox(verticesList, indicesList, new(-6.10f, 0.0f, 2.95f), new(6.10f, frameWidth, 3.10f), 4); + AddBox(verticesList, indicesList, new(-6.10f, 0.0f, -2.95f), new(-5.95f, frameWidth, 2.95f), 4); + AddBox(verticesList, indicesList, new(5.95f, 0.0f, -2.95f), new(6.10f, frameWidth, 2.95f), 4); + + AddBox(verticesList, indicesList, new(-6.10f, 0.0f, -3.10f), new(-5.95f, 5.25f, -2.95f), 4); + AddBox(verticesList, indicesList, new(5.95f, 0.0f, -3.10f), new(6.10f, 5.25f, -2.95f), 4); + AddBox(verticesList, indicesList, new(-6.10f, 0.0f, 2.95f), new(-5.95f, 5.25f, 3.10f), 4); + AddBox(verticesList, indicesList, new(5.95f, 0.0f, 2.95f), new(6.10f, 5.25f, 3.10f), 4); + + AddBox(verticesList, indicesList, new(-6.10f, 5.12f, -3.10f), new(6.10f, 5.27f, -2.95f), 4); + AddBox(verticesList, indicesList, new(-6.10f, 5.12f, 2.95f), new(6.10f, 5.27f, 3.10f), 4); + AddBox(verticesList, indicesList, new(-6.10f, 5.12f, -2.95f), new(-5.95f, 5.27f, 2.95f), 4); + AddBox(verticesList, indicesList, new(5.95f, 5.12f, -2.95f), new(6.10f, 5.27f, 2.95f), 4); + + vertices = [.. verticesList]; + indices = [.. indicesList]; + materials = + [ + new() { Albedo = new(0.38f, 0.52f, 0.55f), Roughness = 0.48f, Metallic = 0.04f }, + new() { Albedo = new(0.12f, 0.28f, 0.31f), Roughness = 0.24f, Metallic = 0.18f }, + new() { Albedo = new(0.56f, 0.63f, 0.66f), Roughness = 0.16f, Metallic = 0.72f }, + new() { Albedo = new(0.08f, 0.20f, 0.28f), Roughness = 0.18f, Metallic = 0.35f }, + new() { Albedo = new(0.55f, 0.68f, 0.72f), Roughness = 0.10f, Metallic = 0.88f } + ]; + } + + public static void CreateGlass(out SceneVertex[] vertices, out uint[] indices) + { + List verticesList = []; + List indicesList = []; + + AddQuad(verticesList, indicesList, new(-6.0f, 0.0f, -3.0f), new(6.0f, 0.0f, -3.0f), new(6.0f, 5.2f, -3.0f), new(-6.0f, 5.2f, -3.0f), 0); + AddQuad(verticesList, indicesList, new(6.0f, 0.0f, 3.0f), new(-6.0f, 0.0f, 3.0f), new(-6.0f, 5.2f, 3.0f), new(6.0f, 5.2f, 3.0f), 0); + AddQuad(verticesList, indicesList, new(-6.0f, 0.0f, 3.0f), new(-6.0f, 0.0f, -3.0f), new(-6.0f, 5.2f, -3.0f), new(-6.0f, 5.2f, 3.0f), 0); + AddQuad(verticesList, indicesList, new(6.0f, 0.0f, -3.0f), new(6.0f, 0.0f, 3.0f), new(6.0f, 5.2f, 3.0f), new(6.0f, 5.2f, -3.0f), 0); + + vertices = [.. verticesList]; + indices = [.. indicesList]; + } + + private static void AddBox(List vertices, List indices, Vector3 minimum, Vector3 maximum, uint materialId) + { + AddTransformedBox(vertices, indices, (minimum + maximum) * 0.5f, (maximum - minimum) * 0.5f, Matrix4x4.Identity, materialId); + } + + private static void AddTransformedBox(List vertices, + List indices, + Vector3 center, + Vector3 halfExtents, + Matrix4x4 rotation, + uint materialId) + { + Vector3[] local = + [ + new(-halfExtents.X, -halfExtents.Y, -halfExtents.Z), + new(halfExtents.X, -halfExtents.Y, -halfExtents.Z), + new(halfExtents.X, halfExtents.Y, -halfExtents.Z), + new(-halfExtents.X, halfExtents.Y, -halfExtents.Z), + new(-halfExtents.X, -halfExtents.Y, halfExtents.Z), + new(halfExtents.X, -halfExtents.Y, halfExtents.Z), + new(halfExtents.X, halfExtents.Y, halfExtents.Z), + new(-halfExtents.X, halfExtents.Y, halfExtents.Z) + ]; + + Vector3[] points = new Vector3[local.Length]; + for (int index = 0; index < local.Length; index++) + { + points[index] = Vector3.Transform(local[index], rotation) + center; + } + + AddQuad(vertices, indices, points[1], points[0], points[3], points[2], materialId); + AddQuad(vertices, indices, points[4], points[5], points[6], points[7], materialId); + AddQuad(vertices, indices, points[0], points[4], points[7], points[3], materialId); + AddQuad(vertices, indices, points[5], points[1], points[2], points[6], materialId); + AddQuad(vertices, indices, points[3], points[7], points[6], points[2], materialId); + AddQuad(vertices, indices, points[0], points[1], points[5], points[4], materialId); + } + + private static void AddCylinder(List vertices, + List indices, + Vector3 baseCenter, + float radius, + float height, + int segments, + uint materialId) + { + for (int segment = 0; segment < segments; segment++) + { + float angle0 = segment * MathF.Tau / segments; + float angle1 = (segment + 1) * MathF.Tau / segments; + Vector3 radial0 = new(MathF.Cos(angle0) * radius, 0.0f, MathF.Sin(angle0) * radius); + Vector3 radial1 = new(MathF.Cos(angle1) * radius, 0.0f, MathF.Sin(angle1) * radius); + + AddQuad(vertices, indices, baseCenter + radial1, baseCenter + radial0, baseCenter + radial0 + (Vector3.UnitY * height), baseCenter + radial1 + (Vector3.UnitY * height), materialId); + AddTriangle(vertices, indices, baseCenter + (Vector3.UnitY * height), baseCenter + radial1 + (Vector3.UnitY * height), baseCenter + radial0 + (Vector3.UnitY * height), materialId); + } + } + + private static void AddTriangle(List vertices, List indices, Vector3 v0, Vector3 v1, Vector3 v2, 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 }); + + indices.Add(startIndex); + indices.Add(startIndex + 1); + indices.Add(startIndex + 2); + } + + private static void AddQuad(List vertices, + List indices, + Vector3 v0, + Vector3 v1, + Vector3 v2, + Vector3 v3, + 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 }); + + indices.Add(startIndex); + indices.Add(startIndex + 1); + indices.Add(startIndex + 2); + indices.Add(startIndex); + indices.Add(startIndex + 2); + indices.Add(startIndex + 3); + } +} + +[StructLayout(LayoutKind.Explicit, Size = 32)] +internal struct SceneVertex +{ + [FieldOffset(0)] + public Vector3 Position; + + [FieldOffset(16)] + public Vector3 Normal; + + [FieldOffset(28)] + public uint MaterialId; +} + +[StructLayout(LayoutKind.Explicit, Size = 32)] +internal struct SceneMaterial +{ + [FieldOffset(0)] + public Vector3 Albedo; + + [FieldOffset(12)] + public float Roughness; + + [FieldOffset(16)] + public float Metallic; + + [FieldOffset(20)] + public float Emission; +} diff --git a/sources/Experiments/FluidTank/Helpers/GraphicsHelper.cs b/sources/Experiments/FluidTank/Helpers/GraphicsHelper.cs new file mode 100644 index 00000000..374fdfef --- /dev/null +++ b/sources/Experiments/FluidTank/Helpers/GraphicsHelper.cs @@ -0,0 +1,151 @@ +using Zenith.NET; +using Buffer = Zenith.NET.Buffer; + +namespace FluidTank.Helpers; + +internal static unsafe class GraphicsHelper +{ + public static string ShaderPath(params string[] paths) + { + return Path.Combine([AppContext.BaseDirectory, "Assets", "Shaders", .. paths]); + } + + public static Buffer CreateBuffer(uint count, uint strideInBytes, BufferUsages usages) + { + return App.Context.CreateBuffer(new() + { + SizeInBytes = count * strideInBytes, + StrideInBytes = strideInBytes, + Usages = usages, + Residency = MemoryResidency.GpuOnly + }); + } + + public static Buffer LoadBuffer(CommandBuffer commandBuffer, T[] data, BufferUsages usages) where T : unmanaged + { + Buffer buffer = CreateBuffer((uint)data.Length, (uint)sizeof(T), usages | BufferUsages.TransferDst); + + fixed (T* pointer = data) + { + commandBuffer.Upload(buffer, 0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(T) * data.Length) + }); + } + + return buffer; + } + + public static Buffer CreateConstantBuffer(uint sizeInBytes) + { + return App.Context.CreateBuffer(new() + { + SizeInBytes = sizeInBytes, + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly + }); + } + + public static Buffer CreateConstantBuffer() where T : unmanaged + { + return CreateConstantBuffer((uint)sizeof(T)); + } + + public static Texture CreateTexture(PixelFormat format, uint width, uint height, TextureUsages usages) + { + return App.Context.CreateTexture(new() + { + Type = TextureType.Texture2D, + Format = format, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = usages + }); + } + + public static Shader LoadShader(string file, string entryPoint) + { + return App.Context.CreateShader(ZenithCompiler.CompileFromFile(App.Context.GraphicsApi, ShaderPath(file), entryPoint)); + } + + public static GraphicsPipeline CreateGraphicsPipeline(string file, + string vertexEntryPoint, + string fragmentEntryPoint, + InputLayout[] inputLayouts, + AttachmentFormats attachmentFormats, + RasterizerState rasterizer, + DepthStencilState depthStencil, + BlendState blend, + PrimitiveTopology primitiveTopology = PrimitiveTopology.TriangleList) + { + using Shader vertexShader = LoadShader(file, vertexEntryPoint); + + return CreateGraphicsPipeline(vertexShader, + file, + fragmentEntryPoint, + inputLayouts, + attachmentFormats, + rasterizer, + depthStencil, + blend, + primitiveTopology); + } + + public static GraphicsPipeline CreateGraphicsPipeline(Shader vertexShader, + string file, + string fragmentEntryPoint, + InputLayout[] inputLayouts, + AttachmentFormats attachmentFormats, + RasterizerState rasterizer, + DepthStencilState depthStencil, + BlendState blend, + PrimitiveTopology primitiveTopology = PrimitiveTopology.TriangleList) + { + using Shader fragmentShader = LoadShader(file, fragmentEntryPoint); + + return App.Context.CreateGraphicsPipeline(new() + { + VertexShader = vertexShader, + FragmentShader = fragmentShader, + InputLayouts = inputLayouts, + PrimitiveTopology = primitiveTopology, + AttachmentFormats = attachmentFormats, + RenderState = new() + { + Rasterizer = rasterizer, + DepthStencil = depthStencil, + Blend = blend + } + }); + } + + public static ComputePipeline CreateComputePipeline(string file, string entryPoint) + { + using Shader shader = LoadShader(file, entryPoint); + + return App.Context.CreateComputePipeline(new() { ComputeShader = shader }); + } + + public static void Dispatch(CommandBuffer commandBuffer, ComputePipeline pipeline, uint width, uint height) + { + ThreadGroupSize groupSize = pipeline.Desc.ComputeShader.Desc.ThreadGroupSize; + + commandBuffer.Dispatch((width + groupSize.X - 1) / groupSize.X, + (height + groupSize.Y - 1) / groupSize.Y, + 1); + } + + public static void Upload(Buffer buffer, uint offsetInBytes, void* data, uint sizeInBytes) + { + buffer.Upload(offsetInBytes, new() + { + Pointer = (nint)data, + SizeInBytes = sizeInBytes + }); + } +} diff --git a/sources/Experiments/FluidTank/Helpers/ImGuiHelper.cs b/sources/Experiments/FluidTank/Helpers/ImGuiHelper.cs new file mode 100644 index 00000000..76a90407 --- /dev/null +++ b/sources/Experiments/FluidTank/Helpers/ImGuiHelper.cs @@ -0,0 +1,32 @@ +using Hexa.NET.ImGui; + +namespace FluidTank.Helpers; + +internal static class ImGuiHelper +{ + public static void Overlay(Action action) + { + ImGui.SetNextWindowPos(new(10, 10), ImGuiCond.Always, new(0, 0)); + ImGui.SetNextWindowBgAlpha(0.35f); + + if (ImGui.Begin("Overlay", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoDecoration)) + { + action(); + } + + ImGui.End(); + } + + public static void Settings(Action action) + { + ImGui.SetNextWindowPos(new(ImGui.GetIO().DisplaySize.X - 10, 10), ImGuiCond.Always, new(1, 0)); + ImGui.SetNextWindowCollapsed(true, ImGuiCond.FirstUseEver); + + if (ImGui.Begin("Settings", ImGuiWindowFlags.AlwaysAutoResize)) + { + action(); + } + + ImGui.End(); + } +} diff --git a/sources/Experiments/FluidTank/Program.cs b/sources/Experiments/FluidTank/Program.cs new file mode 100644 index 00000000..4e172a48 --- /dev/null +++ b/sources/Experiments/FluidTank/Program.cs @@ -0,0 +1,3 @@ +using FluidTank; + +App.Run(); \ No newline at end of file diff --git a/sources/Experiments/MemoryPlayground/Properties/launchSettings.json b/sources/Experiments/FluidTank/Properties/launchSettings.json similarity index 62% rename from sources/Experiments/MemoryPlayground/Properties/launchSettings.json rename to sources/Experiments/FluidTank/Properties/launchSettings.json index c8bb0840..a915f1e6 100644 --- a/sources/Experiments/MemoryPlayground/Properties/launchSettings.json +++ b/sources/Experiments/FluidTank/Properties/launchSettings.json @@ -1,9 +1,9 @@ { "profiles": { - "MemoryPlayground": { + "FluidTank": { "commandName": "Project" }, - "MemoryPlayground (WSL)": { + "FluidTank (WSL)": { "commandName": "WSL2" } } diff --git a/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj b/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj deleted file mode 100644 index 1a30047f..00000000 --- a/sources/Experiments/MemoryPlayground/MemoryPlayground.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - $(StandardTargetFramework) - - - - - - - diff --git a/sources/Experiments/MemoryPlayground/Program.cs b/sources/Experiments/MemoryPlayground/Program.cs deleted file mode 100644 index 1ce2f53f..00000000 --- a/sources/Experiments/MemoryPlayground/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Zenith.NET; - -string[] values = ["Hello, World!", "你好,世界!", "こんにちは、世界!"]; - -using ZenithMarshal.Scope scope = new(); - -nint pointer = ZenithMarshal.StringArrayToPointer(scope, values, StringEncoding.UTF8); - -foreach (string value in ZenithMarshal.StringArrayFromPointer(pointer, (uint)values.Length, StringEncoding.UTF8)) -{ - Console.WriteLine(value); -} \ No newline at end of file diff --git a/sources/Experiments/PlatformDetection/Program.cs b/sources/Experiments/PlatformDetection/Program.cs index 6cec30be..43018cd8 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}"); } catch (Exception) { - Console.WriteLine($"Backend {backend} is not supported."); + Console.WriteLine($"GraphicsApi {graphicsApi} is not supported."); } Console.WriteLine(); diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs index 455a570f..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; @@ -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 99b5f875..fd3dc5ad 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; @@ -6,212 +7,116 @@ namespace Zenith.NET.Extensions.ImGui; internal unsafe class ImGuiRenderer : DisposableObject { - public const string Source = @" + public const string Source = """ struct VSInput { float2 Position : POSITION0; - + float2 UV : TEXCOORD0; - + float4 Color : COLOR0; }; struct VSOutput { float4 Position : SV_POSITION; - + float2 UV : TEXCOORD0; - + float4 Color : COLOR0; }; struct Constants { float4x4 Projection; + + DescriptorHandle Texture; + + DescriptorHandle Sampler; }; -uniform Constants constants; -uniform Texture2D texture; -uniform SamplerState sampler; +ConstantBuffer constants; float3 SrgbToLinear(float3 srgb) { return srgb * (srgb * (srgb * 0.305306011 + 0.682171111) + 0.012522878); } +[shader("vertex")] 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; } -float4 PSMain(VSOutput input) : SV_TARGET +[shader("fragment")] +float4 FSMain(VSOutput input) : SV_TARGET { - return input.Color * texture.Sample(sampler, input.UV); + return input.Color * (*constants.Texture).Sample(*constants.Sampler, input.UV); } -"; +"""; - private readonly string[] Dxil = - [ - // Vertex Shader - Legacy - "4458424322A36D6E02DEF08BE6B9959E5635FACE0100000038120000070000003C0000004C000000D40000006001000090020000DC090000F8090000534649300800000000000000000000004953473180000000030000000800000000000000680000000000000000000000030000000000000003030000000000000000000071000000000000000000000003000000010000000303000000000000000000007A000000000000000000000003000000020000000F0F000000000000504F534954494F4E00544558434F4F524400434F4C4F52004F5347318400000003000000080000000000000068000000000000000100000003000000000000000F00000000000000000000007400000000000000000000000300000001000000030C000000000000000000007D000000000000000000000003000000020000000F0000000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200005053563028010000340000000100000000000000000000000000000000000000FFFFFFFF010000000303000303000000000000000000000000000000280000000100000018000000020000000000000000000000000000000D000000000000003000000000504F534954494F4E00544558434F4F524400434F4C4F5200544558434F4F524400434F4C4F520056534D61696E0000010000000000000010000000010000000000000001004200030000000A000000000000000101420003000000130000000000000001024400030000000000000000000000010044030304000019000000000000000101420003020000220000000000000001024400030200000F0000000F00000000000000000000001000000020000000000000000000000000010000000200000004000000080000535441544407000066000100D10100004458494C06010000100000002C0700004243C0DE210C0000C80100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010016000000321E981419114C908C092647C60443224AA01846000A30A00C8AA010CAA124CAA3200AAC28A82889118022288432A0B000010111A89B01206F0680BE190002C77218E679000002030000111002C1000405E2660000000000791800009F0000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241300C0A76731B06842026081F47E78CED4D2C8C0D2A4C2EAC6DEE0B668240281B1043590C6360800D41B3810000079820741A97B137B739BA3037BAB92F980902B16C208848323608CC3441E03C3250726F6A656374696F6E5F30130482992010CD8601C3860902E14C108867034258572619DAB64160B80982B76D208845323608CCB7E13020AAEA3C30982008C00660C3608CC1186C08C860C3308841194C10C0A0DB109C010DABA9A6B0343722504F534954494F4E1384829A2014D586C0982014D6064192362C861AAC011BB4011B0C6E60B0C11B10A12AC21A7A7A9222DAB00C71B0066CD0066C30B8C1C0066FC062E889E9496A82505C130402DA204875B061B9E6600DD8A00DD860A0838B0DEC60C300077270075CA6ACBEA0DEE6D2E8D2DEDC260805B66131F2600DF4A00DE860A003830DEC60C332C4C11AB0411BB8C1E006031BBCC186E59A83356083367083810E2E36B0830DC31EF0411F6C18F0C00F80094218641B04031436146290067FF084020D33B6B730BAB9090211B148739BA39B9B2010128DB9B4B32F36321A7369675F7374130462DA808C02299482299C0229A0422A546163B36B7349232B73A39B120455C8F05CECCAE4E6D2DEDCA6044413323C17BB3036BB32B9298151870CCF650E2D8CAC4CAEE98DAC8C6D4A809421C373912B9B7BAB931B2B9B9B123895C8F05CE8F2E0CA82DCDCDEE8C2E8D2DEDCE6A60860500675C8F05CECD2CAEE92C8A6E8C2E8CAA6046750870CCFA5CC8D4E2E0FEA2DCD8D6E6E4A100A5DC8F05CC6DEEADCE8CAE4E6A604A900791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C38CC821077C70037210877370037B080779608770C88777A8077A98813CE4800F6E400FE5D00EF0000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC360000000000000048415348140000000000000082D156255E85851D3995C0A0E0DA81FE4458494C38080000660001000E0200004458494C0601000010000000200800004243C0DE210C0000050200000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010011000000321E981419114C908C092647C60443224AA0188A6204A00003CAA008CA838A92180128824228030A0B10101081C0B11C86791E0080C000004004844030004181BE19000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241180C0A76731B0684202608843241F82C02130462D980280BA32843036C089C0D04003CC004010CAA0D41344110001A56534D61696E44A09EA692A8929E9C2608C5334128A00D81324128A20902C16C10346DC3A254D6855D43A65C1B11AA22ACA1A72729A20DCBD05917760DD9706D1304A26131F4C4F424354128A40902E16C103431D8B07C60605DD83584C1778DC18681F3C880CB94D517D4DB5C1A5DDA9BDB04A198362C8A1958678085C11006CA35061B96A1B32E2C1BB2E1DA362C1F185817960D61F05D63B06140833450830D4319AC01304108036A83A0B4C18662A2D80072832A6C6C766D2E6964656E745382A00A199E8B5D99DC5CDA9BDB9480684286E76217C6665726372530EA90E1B9CCA1859195C935BD9195B14D09903264782E7265736F75726365735382A70E199E8B5D5AD95D12D9145D185DD99420AA4386E752E646279707F596E646373725700300000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C38CC821077C70037210877370037B080779608770C88777A8077A98813CE4800F6E400FE5D00EF0000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC36000000612000007A0000001304412C100000000900000044944221CC001457D995EC40C10E9406152540D508005173108AA24414CD21780B000000230608008260607587900D230607008260207D47208C18240008828141060DB655CC8841028020181865E064DCD18C18240008828161068FD659CE8841028020181867006D5EF58C182400088281810691187C1934629000200806461A486300064B34629000200806861A4C6110069B34629000200806C61A50622006CD346270002008068D1A48C9188C2604C06882108C260CC26802318C181C00088241F30697B306A30901309A2004A30983309A400C230607008260D0D00137ADC16842008C2608C168C2208C26108339917C460C100004C1E0C983318894C08C003A0651F219314000100483870FCC8062020B10E89874C967C4000140100C9E3F4883CB092C50A06394269F1103040041307844810D3428B08081CE88410280201820A610077EE00778A08C182400088201620A71E0077E9006C788410280201820A610077EE00777408C182400088201620A71E0077E5007C188410280201820A61007A1E00778F08D182400088201620A71100A7E9006DE88410280201820A610077BE0077830062306090082608098421CEC811FA481188C182400088201620A71B0077E700761306290002008068829C4C11EF8411D80010200000000", - - // Vertex Shader - Linear - "44584243D753A620DDB4C72961AF9BB49563BC6A0100000094120000070000003C0000004C000000D40000006001000090020000E0090000FC090000534649300800000000000000000000004953473180000000030000000800000000000000680000000000000000000000030000000000000003030000000000000000000071000000000000000000000003000000010000000303000000000000000000007A000000000000000000000003000000020000000F0F000000000000504F534954494F4E00544558434F4F524400434F4C4F52004F5347318400000003000000080000000000000068000000000000000100000003000000000000000F00000000000000000000007400000000000000000000000300000001000000030C000000000000000000007D000000000000000000000003000000020000000F0000000000000053565F506F736974696F6E00544558434F4F524400434F4C4F5200005053563028010000340000000100000000000000000000000000000000000000FFFFFFFF010000000303000303000000000000000000000000000000280000000100000018000000020000000000000000000000000000000D000000000000003000000000504F534954494F4E00544558434F4F524400434F4C4F5200544558434F4F524400434F4C4F520056534D61696E0000010000000000000010000000010000000000000001004200030000000A000000000000000101420003000000130000000000000001024400030000000000000000000000010044030304000019000000000000000101420003020000220000000000000001024400030200000F0000000F00000000000000000000001000000020000000000000000000000000010000000200000004000000080000535441544807000066000100D20100004458494C0601000010000000300700004243C0DE210C0000C90100000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4098000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010016000000321E981819114C908C092647C60443224AA01846000A30A00C8AA010CAA124CAA35CCAAF208A828A92180128824228030A0B10101081BA1900F26600E89B0120702C87619E070020300000100121100C4050206E0600000079180000A00000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241300C0A76731B06842026085F47E78CED4D2C8C0D2A4C2EAC6DEE0B668240281B1043590C6360800D41B3810000079820741B97B137B739BA3037BAB92F980902B16C208848323608CC3441E03E3250726F6A656374696F6E5F30130482992010CD8601C3860902E14C108867034258572619DAB64160B80982C76D208845323608CCB7E13020AAEA3C30982008C00660C3608CC1186C08C860C3308841194C10C0C0DB109C010DABA9A6B0343722504F534954494F4E1384A29A2014D686C0982014D7064192362C861AAC011BB4011B0C6E60B0C11B10A12AC21A7A7A9222DAB00C71B0066CD0066C30B8C1C0066FC062E889E9496A825060130402DA204875B061B9E6600DD8A00DD860A0838B0DEC60C300077270075CA6ACBEA0DEE6D2E8D2DEDC260845B66131F2600DF4A00DE860A003830DEC60C332C4C11AB0411BB8C1E006031BBCC186E59A83356083367083810E2E36B0830DC31EF0411F6C18F0C00F80094218681B04031436146290067FF084020D33B6B730BAB9090211B148739BA39B9B2010128DB9B4B32F36B2090231D1984B3BFB9AA39B2010D406641448A1144CE11450211554A10A1B9B5D9B4B1A59991BDD9420A84286E76257263797F6E63625209A90E1B9D885B1D995C94D098C3A64782E7368616465724D6F64656C5302A40C199E8B5CD9DC5B9DDC58D9DC94C0A94486E74297075716E4E6F646174697F6E6363745008332A84386E76297567697443645174657362538833A64782E656E747279506F696E7473538250E84286E732F656E7465726373725500500791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF4000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC3600000000000000484153481400000000000000F9DD1AE5250EB9E439EBECBF95D9943F4458494C9008000066000100240200004458494C0601000010000000780800004243C0DE210C00001B0200000B82200002000000130000000781239141C80449061032399201840C250508191E048B628018450242920B42C41032143808184B0A32628848901420434688A500193242E4480E901123C4504151818CE183E58A0431460651180000080000001B8CE0FFFFFFFF074002A80D84F0FFFFFFFF03206D3086FFFFFFFF1F0009A800491800000300000013826042204C080600000000892000003F00000032228809206485041323A484041323E384A19014124C8C8C0B84C44C1088C1084009000A6600E608C0608E0029C64010444190510C8020886220E4A8E1F227EC21249FDBA8622526BFB86D440CC3305071CF70F913F610921F02CDB01028580AA1100C414D29066218067A6E1B2E7FC21E42F2574272A84820D2C879886842080909045108866022920E1A2E7FC21E42F25742DA906640044110C51C41500A86A0888AAC81806104624882EC2C610124C9678029422EBF581C60F2711F4781769234459430F99CD34813D04CD24FA360BB4A9A224A987C7071026049810860A49F4601778EB13801B0A4400430D24FA3A0232F1D8839025000000000131472C08774608736688779680372C0870DAE500E6DD00E7A500E6D000F7A300772A0077320076D900E71A0077320076D900E78A00778D006E9100776A0077160076D900E7320077A300772D006E9600774A0077640076D600E7160077A100776D006E6300772A0077320076D600E7640077A600774D006EE80077A100776A0077320077A60077430E4090000000000000000000060C86300011000000000000000C090070102400000000000000080214F0304C00000000000000000439E0708800000000000000000863C12100001000000000000000C792C20000200000000000000C802010011000000321E981419114C908C092647C60443224AA0188A6204A00003CAA008CA838A92180128824228030A0B10101081C0B11C86791E0080C000004004844030004181BE19000079180000650000001A034C90460213C43120C31B4381934BB30BA32B4B018971C1718171A989919901419931C3A991C9A919334BD910041304E2982010C8066120260844B241180C0A76731B0684202608843241F82C02130462D980280BA32843036C089C0D04003CC004010CAA0D41344110001A56534D61696E44A09EA692A8929E9C2608C5334128A00D81324128A20902C16C10346DC3A254D6855D43A65C1B11AA22ACA1A72729A20DCBD05917760DD9706D1304A26131F4C4F424354128A40902E16C103431D8B07C60605DD83584C1778DC18681F3C880CB94D517D4DB5C1A5DDA9BDB04A198362C8A1958678085C11006CA35061B96A1B32E2C1BB2E1DA362C1F185817960D61F05D63B06140833450830D4319AC01304108036A83A0B4C18662A2D80072832A6C6C766D2E6964656E745382A00A199E8B5D99DC5CDA9BDB9480684286E76217C6665726372530EA90E1B9CCA1859195C935BD9195B14D09903264782E7265736F75726365735382A70E199E8B5D5AD95D12D9145D185DD99420AA4386E752E646279707F596E646373725700300000000791800004C0000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC421077C70037A288776808719D1430EF8E006E4200EE7E006F6100EF2C00EE1900FEF500FF4000000712000001F0000000660BCAC09208D1550C3E53B8F0F348D3301131102CDB01036B00D97EF3CBE1050454144A5030C25610002E617B76D06DD70F9CEE30B11014C440834C3427C91C36C4833208D6101D370F9CEE32F0E3088CD434D7E71DB26500D97EF3CBE3439118152D3434D7E71DB46200D97EF3CFE4444130244985FDC3600000061200000900000001304412C100000000E000000548D005051024494462914C20C4071955DC90E14EC000D6304A2B9EAA4374640DA682F7F14CD21640B5173108AA244348C11C8288DA71F002306080082606081C12170CF88C10180201848627004C48841028020181867D06C1EC68C18240008828181060EF745CD8841028020181869F07460903923060900826060A801E48501F68C182400088281B10651198801078D182400088281C10692198C41158D182400088281D10613199081278D182400088281E106541994C1358D181C00088241D306526206A30901309A2004A30983309A400C230607008260D0C8C1E5B8C16842008C2608C168C2208C2610C388C1018020183477C04D6E309A1000A30942309A3008A309C4604E249F11030400413078F8600C22253023808E41947C460C100004C1E0F90333A098C002043A265DF2193140001004834714D2E072020B14E818A5C967C4000140100C9E5260030D0A2C60A063636006F2B1313803F9D818A0817C6C6003F8D8D006F0B1C10DE063431AC8C78634908F0D69201F1BE6003E36D0017C6CA803F8D8F006F2B1E10DE463C31BC867C4200140100C105C000558800555C8460C120004C100C1055080055810056BC4200140100C105C00055880855498460C120004C100C10550800558380568C4200140100C105C00855980055570831183040041304070011466011644A10D460C120004C100C10550680558508561C4200140100C105C00855680055110460C120004C100C10550680558488560C4200140100C105C0085568085537803040000000000", - - // Pixel Shader - "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 - "030223070006010000002800490000000000000011000200010000000E00030000000000010000000F000C00000000000200000056534D61696E0000360000003A0000003D000000150000002B000000310000001C000000030003000B00000001000000050009000B000000436F6E7374616E74735F7374643134305F6C6F676963616C00000000060006000B0000000000000050726F6A656374696F6E00000500060015000000696E7075742E506F736974696F6E0000050007001A000000436F6E7374616E74735F73746431343000000000060006001A0000000000000050726F6A656374696F6E00000500070019000000476C6F62616C506172616D735F73746431343000060006001900000000000000636F6E7374616E7473000000050006001C000000676C6F62616C506172616D7300000000050005002B000000696E7075742E5556000000000500050031000000696E7075742E436F6C6F7200050009003A000000656E747279506F696E74506172616D5F56534D61696E2E555600000005000A003D000000656E747279506F696E74506172616D5F56534D61696E2E436F6C6F7200000000050004000200000056534D61696E000047000400150000001E00000000000000480005001A000000000000002300000000000000480004001A0000000000000005000000480005001A0000000000000007000000100000004700030019000000020000004800050019000000000000002300000000000000470004001C0000002100000000000000470004001C0000002200000000000000470004002B0000001E0000000100000047000400310000001E0000000200000047000400360000000B00000000000000470004003A0000001E00000000000000470004003D0000001E0000000100000013000200010000002100030003000000010000001600030006000000200000001700040007000000060000000400000017000400080000000600000002000000180004000C00000007000000040000001E0003000B0000000C000000150004000F00000020000000010000002B0004000F0000001000000000000000200004001400000001000000080000002B0004000600000017000000000000002B00040006000000180000000000803F1E0003001A0000000C0000001E000300190000001A000000200004001B0000000200000019000000200004001D000000020000001A0000002000040030000000010000000700000020000400350000000300000007000000200004003900000003000000080000003B0004001400000015000000010000003B0004001B0000001C000000020000003B000400140000002B000000010000003B0004003000000031000000010000003B0004003500000036000000030000003B000400390000003A000000030000003B000400350000003D000000030000003600050001000000020000000000000003000000F8000200040000003D000400080000001300000015000000500006000700000016000000130000001700000018000000410005001D0000001E0000001C000000100000003D0004001A0000001F0000001E000000900104000B000000200000001F000000510005000C00000048000000200000000000000091000500070000002500000048000000160000003D000400080000002A0000002B0000003D000400070000002F000000310000003E00030036000000250000003E0003003A0000002A0000003E0003003D0000002F000000FD00010038000100", - - // Vertex Shader - Linear - "030223070006010000002800750000000000000011000200010000000E00030000000000010000000F000C00000000000200000056534D61696E0000500000005400000057000000150000002B000000310000001C000000030003000B00000001000000050009000B000000436F6E7374616E74735F7374643134305F6C6F676963616C00000000060006000B0000000000000050726F6A656374696F6E00000500060015000000696E7075742E506F736974696F6E0000050007001A000000436F6E7374616E74735F73746431343000000000060006001A0000000000000050726F6A656374696F6E00000500070019000000476C6F62616C506172616D735F73746431343000060006001900000000000000636F6E7374616E7473000000050006001C000000676C6F62616C506172616D7300000000050005002B000000696E7075742E5556000000000500050031000000696E7075742E436F6C6F72000500090054000000656E747279506F696E74506172616D5F56534D61696E2E555600000005000A0057000000656E747279506F696E74506172616D5F56534D61696E2E436F6C6F7200000000050004000200000056534D61696E000047000400150000001E00000000000000480005001A000000000000002300000000000000480004001A0000000000000005000000480005001A0000000000000007000000100000004700030019000000020000004800050019000000000000002300000000000000470004001C0000002100000000000000470004001C0000002200000000000000470004002B0000001E0000000100000047000400310000001E0000000200000047000400500000000B0000000000000047000400540000001E0000000000000047000400570000001E0000000100000013000200010000002100030003000000010000001600030006000000200000001700040007000000060000000400000017000400080000000600000002000000180004000C00000007000000040000001E0003000B0000000C000000150004000F00000020000000010000002B0004000F0000001000000000000000200004001400000001000000080000002B0004000600000017000000000000002B00040006000000180000000000803F1E0003001A0000000C0000001E000300190000001A000000200004001B0000000200000019000000200004001D000000020000001A00000020000400300000000100000007000000170004003300000006000000030000002B000400060000003B00000012519C3E2B000400060000003D000000C4A22E3F2B0004000600000041000000C22C4D3C200004004F0000000300000007000000200004005300000003000000080000003B0004001400000015000000010000003B0004001B0000001C000000020000003B000400140000002B000000010000003B0004003000000031000000010000003B0004004F00000050000000030000003B0004005300000054000000030000003B0004004F00000057000000030000002C000600330000006D0000003D0000003D0000003D0000002C000600330000006E0000004100000041000000410000003600050001000000020000000000000003000000F8000200040000003D000400080000001300000015000000500006000700000016000000130000001700000018000000410005001D0000001E0000001C000000100000003D0004001A0000001F0000001E000000900104000B000000200000001F000000510005000C0000006C00000020000000000000009100050007000000250000006C000000160000003D000400080000002A0000002B0000003D000400070000002F000000310000004F00080033000000340000002F0000002F0000000000000001000000020000008E000500330000005D000000340000003B00000081000500330000005F0000005D0000006D000000850005003300000060000000340000005F000000810005003300000062000000600000006E00000085000500330000006300000034000000620000005100050006000000470000006300000000000000520006000700000070000000470000002F00000000000000510005000600000049000000630000000100000052000600070000007200000049000000700000000100000051000500060000004B00000063000000020000005200060007000000740000004B00000072000000020000003E00030050000000250000003E000300540000002A0000003E0003005700000074000000FD00010038000100", - - // Pixel Shader - "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 - "4D544C4201800200070000810E000000181300000000000058000000000000008A00000000000000160100000000000039000000000000004F0100000000000008000000000000005701000000000000300E000000000000010000008A0000004E414D45070056534D61696E00545950450100004841534820000EAC3941BE1430927847F5EEDB1D3E2EC5534205A80228D8C9BA1EB0FC38937B4F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800300E00000000000052464C5408000400000000000000454E4454524C53541000870F000000000000910300000000000055554944100064EF3F7D1C4B30FF8EC74A1716027506454E4454390000005641545420000300506F736974696F6E5F3100008055565F31000180436F6C6F725F310002805641545905000300040406454E445408000000454E4454DEC0170B0000000014000000140E0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C00004C0300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C800000089200000250000003222480920624600212B249814212524981419270C85A4906052645C20246582209A0118462080610401B843104109C24CD43CD0833CD4C338D0831BB44339D04338B0831EE8413B84033DC8433AE0430A084AD21451C2E4734E234D4033493F9D82829534459430F9E0E204C0920211C0483F9D0233887008A6184204446920608E000CE608406110211086110866040000000051180000630000001BFA23F8FFFFFFFF013005C00F003800FE00908009A0803E20C2011EE0411EDE011FDAC01CEAC11DC6A10DCC011EDAA01DC2811ED001A00779A8877200087390877068877268037878877470077A28077900C2811DD80120DA211DDCA10DD8A11CCE211CD8A10DECA11CC6811EDE411EDAE01ED2811CE8011D803890033C00067778873610877A480776A08774708779000877788736480777308779680373808736688770A0077400E8411EEAA11C00C21DDEA10DDC211CDC611EDAC01CE0A10DDA211CE8011D007A90877A280780708777688379488773708772208736D0877290877798873630077868837608077A4007801EE4A11ECA0120DCE11DDA801EE4211CE0011ED2C11DCEA10DDA211CE8011D007A90877A28078098077A08877158873680077978077A288771A0877790873610877A30077328077968837948077D2807000F00A21EDC611EC2C11CCAA10DCC011EDAA01DC2811ED001A00779A8877200367402012C002900D5100EE9200F6D200EF5600EE6500EF2D006EEF00E6D100EEC900EE1300F00000049180000010000001384400013B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69BCAC0920CD900A30120000020000000100000000000390D82050D4690000200B0400000A000000321E981019114C908C092647C6044362255008E55004E5538002055120C530025006051800000000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A80778980700000000792000001D010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A398066400560000004A63611BB44171506C1C193091C128916310CB01490A7128484421CCB24497E30000000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E706F736974696F6E6169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D65506F736974696F6E5F306169722E7665727465785F6F75747075747573657228544558434F4F524429666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E7665727465785F696E7075746169722E6C6F636174696F6E5F696E646578506F736974696F6E5F3155565F31436F6C6F725F316169722E6275666665726169722E726561646169722E616464726573735F73706163656169722E7374727563745F747970655F696E666F666C6F617434783450726F6A656374696F6E5F30436F6E7374616E74735F30636F6E7374616E74735F306169722E6172675F747970655F73697A656169722E6172675F747970655F616C69676E5F73697A65476C6F62616C506172616D735F30676C6F62616C506172616D735F3026760000000000003082C00423088C3082C00C23080C3182C01423088C3182C01C23080C3282C02423088C3282C02C23080A3082C030330C6A10ACC10C031B086D30C3C006831BCC30B001E106330C6C50B8C10C031B186F30C3C006071CCC30B00112073304C90C431BC8C11CCC402874A006733043B0CC10303304CD0C85F34091348331514F15593318D3F5401136C3500AA6700A332473906973A0064F156D33246A90696AA0064F157133246D90696DA0060F147533147360077320066330C31106AC3007763007645006334874E06973A0061F18B44118B48219D8C119C4C18306511ACC40A4822AAC822BCC30D4012ABCC29D01C0711CC7711CC7711CC7B9811BB8811BB8811BB8811B5874A0079665B9011DD0011DE0022EE0022B90836A80828C042628233636BB3697B637B23AB6321733B6B0B3B951103BB8033CC8033DD8033EE88354D8D8ECDA5CD2C8CADCE84609FC2097B0343917BB32B9B9B437B751823F482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D940014720A4B9373197B6B834B632BFB7A83A34B7B739B1B65080551188554C2D2E45CECCAE4E8CAF046095E01000000A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000360000001304412C1000000006000000D446004AA00CE88D008C45044110909801A0310300000000F1300000140000002247C890510A841C00000000980500006169722D616C6961732D73636F7065732856534D61696E296169722D616C6961732D73636F70652D61726728332900002B8459888515032DCC822C6C08680100FD18C8E138CE414128830C0D61A010887F3F06B34892A45010CA204384242804E28F4101FEFD1811745D974341283804E03FDBD004C06C437005B30DC1256C1010030000040000005B86E0A0852D4371D0C2964139680100000000007120000003000000320E1022840096060000000000000000650C00001F000000120394F0000000000300000006000000090000004C000000010000005800000000000000580000000100000070000000000000000F0000001C00000000000000060000000600000000000000700000000000000000000000010000000000000000000000060000000000000006000000FFFFFFFF00240000000000005D0C00000E0000001203946B0000000056534D61696E33323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000000000000000000010000008D030000524255467D03000000000000000000000018000000414952521000180004001000000000000000140010000000000000000400000000000000100000000400000001000000000000000A000000E00200009002000034020000F0010000AC0100007C0100004C010000D40000006800000004000000F4FDFFFF01000800040000009EFFFFFF04000000010000001400000010001000000000000400000008000C0010000000400000001C000000040000000C00000050726F6A656374696F6E5F300000000008000000666C6F617434783400000000B0FDFFFF010008000C0000000000060008000400060000000400000001000000140000001000140004000000080000000C00100010000000090000004000000018000000040000000B000000636F6E7374616E74735F30000B000000436F6E7374616E74735F3000BCFEFFFF00000400200000001C0024000000000008000C000600070010000000140018001C0020001C0000000000010200000000010000000800000040000000100000001C000000040000000E000000676C6F62616C506172616D735F3000000E000000476C6F62616C506172616D735F30000030FFFFFF0420040004000000A6FFFFFF0200000001000000500100000400000007000000436F6C6F725F31005CFFFFFF0420040004000000D2FFFFFF0100000001000000CC000000040000000400000055565F3100000000E4FEFFFF042004001400000000000E0014000000040008000C0010000E000000000000000100000090000000040000000A000000506F736974696F6E5F310000C8FFFFFF0400020004000000C0FFFFFF18000000BC0000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290008000C000400080008000000040002001400000010001000000004000000000008000C00100000002400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000BCFFFFFF020002001400000000000E000C00000000000000040008000E00000018000000040000000A000000506F736974696F6E5F30000006000000666C6F617434000008000E000400080008000000030000001000000000000A001000040008000C000A000000300000001C000000040000000400000004000000050000000600000007000000030000000100000002000000030000000600000056534D61696E0000454E4454", - - // Vertex Shader - Linear - "4D544C4201800200070000810E000000881300000000000058000000000000008A00000000000000160100000000000039000000000000004F0100000000000008000000000000005701000000000000A00E000000000000010000008A0000004E414D45070056534D61696E005459504501000048415348200018279E3BBD163C92118F96D26DD622BB504BD02519E79C64165EE9EDB89C0D444F464654180000000000000000000000000000000000000000000000000056455253080002000600030001004D44535A0800A00E00000000000052464C5408000400000000000000454E4454524C53541000F70F0000000000009103000000000000555549441000C24CC1BA27A533429FAEB8AF316BA7F7454E4454390000005641545420000300506F736974696F6E5F3100008055565F31000180436F6C6F725F310002805641545905000300040406454E445408000000454E4454DEC0170B0000000014000000840E0000FFFFFFFF4243C0DE3514000003000000620C3024801005C814000000210C0000680300000B02210002000000160000000781239141C80449061032399201840C250508191E048B628014450242920B42A41032143808184B0A3252884870C421234412878C1041920264C808B1142043468820C901325284182A282A90317CB05C9120C5C800000089200000270000003222480920624600212B249814212524981419270C85A4906052645C20246582609A0118462080610401B843104109C24CD43CD0833CD4C338D0831BB44339D04338B0831EE8413B84033DC8433AE0430A084AD21451C2E4734E234D4033493F9D82829534459430F9E0E204C0920211C0483F9D0233887008A6184204446920608E000CE6084061102110861108661861608611066004000000000051180000630000001BFA23F8FFFFFFFF013005C00F003800FE00908009A0803E20C2011EE0411EDE011FDAC01CEAC11DC6A10DCC011EDAA01DC2811ED001A00779A8877200087390877068877268037878877470077A28077900C2811DD80120DA211DDCA10DD8A11CCE211CD8A10DECA11CC6811EDE411EDAE01ED2811CE8011D803890033C00067778873610877A480776A08774708779000877788736480777308779680373808736688770A0077400E8411EEAA11C00C21DDEA10DDC211CDC611EDAC01CE0A10DDA211CE8011D007A90877A280780708777688379488773708772208736D0877290877798873630077868837608077A4007801EE4A11ECA0120DCE11DDA801EE4211CE0011ED2C11DCEA10DDA211CE8011D007A90877A28078098077A08877158873680077978077A288771A0877790873610877A30077328077968837948077D2807000F00A21EDC611EC2C11CCAA10DCC011EDAA01DC2811ED001A00779A8877200367402012C002900D5100EE9200F6D200EF5600EE6500EF2D006EEF00E6D100EEC900EE1300F00000049180000010000001384400013B870480779B0033AF8057B90033B688370800778608772688376088771788779C08738A0033780033780830DB7510E6D000F7A600774A0077640077A600774D006E910077A80077A80076D900E78A00778A00778D006E9100776A0077160077A100776D006E9300772A0077320077A300772D006E9600774A0077640077A600774D006E6300772A0077320077A300772D006E6600774A0077640077A600774D006F6100776A0077160077A100776D006F6200774A0077320077A300772D006F6300772A0077320077A300772D006F6400778A0077640077A600774D006F6600774A0077640077A600774D006F6900776A00771200778A00771200778D006F610077280077A10077280077A10077280076D600F71900772A00772500776A00772500776D006F620077560077A20077560077A20077560076D600F75100772A00775100772A00775100772D006F6100770200774A0077100077240077A100770200774D006EE80077A100776A0077320071A210C69BCAC0920CD900A30120000020000000100000000000390D82050546D0000200B0400000A000000321E981019114C908C092647C6044362255008E55004E5538002055120C530025006051800000000B1180000A50000003308801CC4E11C6614013D88433884C38C4280077978077398710CE6000FED100EF4800E330C421EC2C11DCEA11C6630053D88433884831BCC033DC8433D8C033DCC788C7470077B08077948877070077A700376788770208719CC110EEC900EE1300F6E300FE3F00EF0500E3310C41DDE211CD8211DC2611E6630893BBC833BD04339B4033CBC833C84033BCCF0147660077B680737688772680737808770908770600776280776F8057678877780875F08877118877298877998812CEEF00EEEE00EF5C00EEC300362C8A11CE4A11CCCA11CE4A11CDC611CCA211CC4811DCA6106D6904339C84339984339C84339B8C33894433888033B94C32FBC833CFC823BD4033BB0C30CC7698770588772708374680778608774188774A08719CE530FEE000FF2500EE4900EE3400FE1200EEC500E3320281DDCC11EC2411ED2211CDC811EDCE01CE4E11DEA011E66185138B0433A9C833BCC50247660077B68073760877778077898514CF4900FF0500E331E6A1ECA611CE8211DDEC11D7E011EE4A11CCC211DF0610654858338CCC33BB0433DD04339FCC23CE4433B88C33BB0C38CC50A877998877718877408077A28077298815CE3100EECC00EE5500EF33023C1D2411EE4E117D8E11DDE011E6648193BB0833DB4831B84C3388C4339CCC33CB8C139C8C33BD4033CCC48B471080776600771088771588719DBC60EEC600FEDE006F0200FE5300FE5200FF6500E6E100EE3300EE5300FF3E006E9E00EE4500EF83023E2EC611CC2811DD8E117EC211DE6211DC4211DD8211DE8211F66209D3BBC433DB80339948339CC58BC7070077778077A08077A488777708719CE870EE5100EF0100EECC00EEF300EF3900EF4500E33283008877490073730877A708771A08774780777F88573908777A80778980700000000792000001D010000721E482043880C19097232482023818C9191D144A01028643C3132428E9021A398066400560000004A63611BB44171506C1C193091C128916310CB01490A7128484421CCB24497E30000000077636861725F73697A656672616D652D706F696E7465726169722E6D61785F6465766963655F627566666572736169722E6D61785F636F6E7374616E745F627566666572736169722E6D61785F74687265616467726F75705F627566666572736169722E6D61785F74657874757265736169722E6D61785F726561645F77726974655F74657874757265736169722E6D61785F73616D706C6572734170706C65206D6574616C2076657273696F6E2033323032332E38363420286D6574616C66652D33323032332E383634294D6574616C6169722E636F6D70696C652E64656E6F726D735F64697361626C656169722E636F6D70696C652E666173745F6D6174685F656E61626C656169722E636F6D70696C652E6672616D656275666665725F66657463685F656E61626C656169722E706F736974696F6E6169722E6172675F747970655F6E616D65666C6F6174346169722E6172675F6E616D65506F736974696F6E5F306169722E7665727465785F6F75747075747573657228544558434F4F524429666C6F61743255565F307573657228434F4C4F5229436F6C6F725F306169722E7665727465785F696E7075746169722E6C6F636174696F6E5F696E646578506F736974696F6E5F3155565F31436F6C6F725F316169722E6275666665726169722E726561646169722E616464726573735F73706163656169722E7374727563745F747970655F696E666F666C6F617434783450726F6A656374696F6E5F30436F6E7374616E74735F30636F6E7374616E74735F306169722E6172675F747970655F73697A656169722E6172675F747970655F616C69676E5F73697A65476C6F62616C506172616D735F30676C6F62616C506172616D735F3026760000000000003082C00423088C3082C00C23080C3182C01423088C3182C01C23080C3282C02423088C3282C02C23080A3082C030330C6A10ACC10C031B086D30C3C006831BCC30B001E106330C6C50B8C10C031B186F30C3C006071CCC30B00112073304C90C431BC8C11CCC402874A006733043B0CC10303304CD0C85F34091348331514F15593318D3F5401136C3500AA6700A332473906973A0064F156D33246A90696AA0064F157133246D90696DA0060F147533147360077320066330C31106AC3007763007645006334874E06973A0061F18B44118B48219D8C119C4C18306511ACC40A4822AAC822BCC30D4012ABCC29D01C0711CC7711CC7711CC7B9811BB8811BB8811BB8811B5874A0079665B9011DD0011DE0022EE0022B90836A80828C042628233636BB3697B637B23AB6321733B6B0B3B951103BB8033CC8033DD8033EE88354D8D8ECDA5CD2C8CADCE84609FC2097B0343917BB32B9B9B437B751823F482A2C4DCE852DCCEDAC2EECACECCBAE4C6E2EEDCD6D940014720A4B9373197B6B834B632BFB7A83A34B7B739B1B65080551188554C2D2E45CECCAE4E8CAF046095E01000000A9180000250000000B0A7228877780077A587098433DB8C338B04339D0C382E61CC6A10DE8411EC2C11DE6211DE8211DDEC11D1634E3600EE7500FE1200FE4400FE1200FE7500EF4B08081077928877060077678877108077A28077258709CC338B4013BA4833D94C3026B1CD8211CDCE11CDC201CE4611CDC201CE8811EC2611CD0A11CC8611CC2811DD861C1010FF4200FE1500FF4800E00000000D11000000600000007CC3CA4833B9C033B94033DA0833C94433890C30100000061200000500000001304412C1000000018000000C46600A88D00944019D01B01188B08822098835002221A8B0002E12038D600040285D100123300346600288E35C8288DA79F8CD278FAC9288DA7DF580369A3BDFC9136DACB1F69A3BDFC8D3588E6AA939E68AE3AE989E6AA93DE680000000000F1300000140000002247C890510A841C00000000980500006169722D616C6961732D73636F7065732856534D61696E296169722D616C6961732D73636F70652D61726728332900002B8459888515032DCC822C6C08680100FD18D1735DD7434128830C16E2A010887F3F46356DDB265110CA2083C6442804E28F4101FEFD181A06066000061605A1E01080FF208337511844E28F4104FE180CE28F8104FE1814E23FC8104CDC20435006DC6C439600B30D011A04B30DC1206C101003040000005B86E0A0852D4371D0C2964139680100000000007120000003000000320E1022840098060000000000000000650C00001F000000120394F0000000000300000006000000090000004C000000010000005800000000000000580000000100000070000000000000000F0000001C00000000000000060000000600000000000000700000000000000000000000010000000000000000000000060000000000000006000000FFFFFFFF00240000000000005D0C00000E0000001203946B0000000056534D61696E33323032332E38363461697236345F7632362D6170706C652D6D61636F737831342E302E3000000000000000000000000000010000008D030000524255467D03000000000000000000000018000000414952521000180004001000000000000000140010000000000000000400000000000000100000000400000001000000000000000A000000E00200009002000034020000F0010000AC0100007C0100004C010000D40000006800000004000000F4FDFFFF01000800040000009EFFFFFF04000000010000001400000010001000000000000400000008000C0010000000400000001C000000040000000C00000050726F6A656374696F6E5F300000000008000000666C6F617434783400000000B0FDFFFF010008000C0000000000060008000400060000000400000001000000140000001000140004000000080000000C00100010000000090000004000000018000000040000000B000000636F6E7374616E74735F30000B000000436F6E7374616E74735F3000BCFEFFFF00000400200000001C0024000000000008000C000600070010000000140018001C0020001C0000000000010200000000010000000800000040000000100000001C000000040000000E000000676C6F62616C506172616D735F3000000E000000476C6F62616C506172616D735F30000030FFFFFF0420040004000000A6FFFFFF0200000001000000500100000400000007000000436F6C6F725F31005CFFFFFF0420040004000000D2FFFFFF0100000001000000CC000000040000000400000055565F3100000000E4FEFFFF042004001400000000000E0014000000040008000C0010000E000000000000000100000090000000040000000A000000506F736974696F6E5F310000C8FFFFFF0400020004000000C0FFFFFF18000000BC0000000400000007000000436F6C6F725F30000B0000007573657228434F4C4F52290008000C000400080008000000040002001400000010001000000004000000000008000C00100000002400000014000000040000000400000055565F300000000006000000666C6F61743200000E0000007573657228544558434F4F5244290000BCFFFFFF020002001400000000000E000C00000000000000040008000E00000018000000040000000A000000506F736974696F6E5F30000006000000666C6F617434000008000E000400080008000000030000001000000000000A001000040008000C000A000000300000001C000000040000000400000004000000050000000600000007000000030000000100000002000000030000000600000056534D61696E0000454E4454", - - // Pixel Shader - "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 Buffer constants; private readonly Sampler sampler; - private readonly ResourceLayout resourceLayout; private readonly GraphicsPipeline graphicsPipeline; private readonly Dictionary textureBindings = []; private readonly Dictionary textureViewBindings = []; - private readonly Dictionary resourceTableBindings = []; + private readonly SortedList resourceHandleBindings = new(Comparer.Create(static (x, y) => x.Handle.CompareTo(y.Handle))); 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 = []; - 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; - } + string source = Source.Replace("#if 0", $"#if {(colorSpace is ImGuiColorSpace.Legacy ? 0 : 1)}"); - using Shader vertex = context.CreateShader(new() - { - ShaderBytes = vertexShaderBytes, - EntryPoint = "VSMain", - Stage = ShaderStageFlags.Vertex - }); + sampler = context.CreateSampler(SamplerDesc.PointClamp()); + + using Shader vertex = context.CreateShader(ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "VSMain")); + using Shader fragment = context.CreateShader(ZenithCompiler.CompileFromSource(context.GraphicsApi, source, "FSMain")); - using Shader pixel = context.CreateShader(new() + InputLayout inputLayout = new(); + inputLayout.Add(new() { - ShaderBytes = pixelShaderBytes, - EntryPoint = "PSMain", - Stage = ShaderStageFlags.Pixel + Format = ElementFormat.Float2, + Semantic = ElementSemantic.Position }); - constants = context.CreateBuffer(new() + inputLayout.Add(new() { - SizeInBytes = (uint)sizeof(Constants), - StrideInBytes = (uint)sizeof(Constants), - Flags = BufferUsageFlags.Constant | BufferUsageFlags.MapWrite + Format = ElementFormat.Float2, + Semantic = ElementSemantic.TexCoord }); - sampler = context.CreateSampler(new() + inputLayout.Add(new() { - U = AddressMode.Clamp, - V = AddressMode.Clamp, - W = AddressMode.Clamp, - Filter = Filter.MinPointMagPointMipPoint + Format = ElementFormat.UByte4UNorm, + Semantic = ElementSemantic.Color }); - 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 }); - graphicsPipeline = context.CreateGraphicsPipeline(new() { - RenderStates = new() - { - RasterizerState = RasterizerStates.Default, - DepthStencilState = DepthStencilStates.None, - BlendState = BlendStates.AlphaBlend - }, - Vertex = vertex, - Pixel = pixel, - ResourceLayout = resourceLayout = context.CreateResourceLayout(resourceLayoutDesc), + VertexShader = vertex, + FragmentShader = fragment, InputLayouts = [inputLayout], PrimitiveTopology = PrimitiveTopology.TriangleList, - Output = output + AttachmentFormats = attachmentFormats, + RenderState = new() + { + Rasterizer = RasterizerState.CullNone(), + DepthStencil = DepthStencilState.DepthNone(), + Blend = BlendState.NonPremultiplied() + } }); Context = context; @@ -224,16 +129,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++; } - resourceTableBindings[textureBindings[texture] = textureID = id] = Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constants, texture, sampler] - }); + resourceHandleBindings[textureBindings[texture] = textureID = id] = texture.SampledHandle; } return textureID; @@ -244,22 +145,18 @@ public ImTextureID Binding(TextureView textureView) if (!textureViewBindings.TryGetValue(textureView, out ImTextureID textureID)) { ulong id = 0; - while (resourceTableBindings.ContainsKey(id)) + while (resourceHandleBindings.ContainsKey(id)) { id++; } - resourceTableBindings[textureViewBindings[textureView] = textureID = id] = Context.CreateResourceTable(new() - { - Layout = resourceLayout, - Resources = [constants, textureView, sampler] - }); + resourceHandleBindings[textureViewBindings[textureView] = textureID = id] = textureView.SampledHandle; } 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) { @@ -276,32 +173,46 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa { case ImTextureStatus.WantCreate: { - Texture texture = Context.CreateTexture(new() + 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() { - 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 - }); - - TextureExtent extent = new() { Width = (uint)textureData.Width, Height = (uint)textureData.Height, Depth = 1 }; + Depth = 1 + }; if (textureData.Format is ImTextureFormat.Rgba32) { - ReadOnlySpan pixels = new(textureData.Pixels, textureData.Width * textureData.Height); - - commandBuffer.Upload(texture, default, default, extent, pixels); + TextureData data = new() + { + Pointer = (nint)textureData.Pixels, + 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.Transition(texture, default, TextureLayout.Undefined, TextureLayout.CopyDst); + commandBuffer.Upload(texture, default, default, extent, data); + commandBuffer.Transition(texture, default, TextureLayout.CopyDst, TextureLayout.Sampled); } else { - ReadOnlySpan pixels = new(textureData.Pixels, textureData.Width * textureData.Height); - - commandBuffer.Upload(texture, default, default, extent, pixels); + TextureData data = new() + { + Pointer = (nint)textureData.Pixels, + 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.Transition(texture, default, TextureLayout.Undefined, TextureLayout.CopyDst); + commandBuffer.Upload(texture, default, default, extent, data); + commandBuffer.Transition(texture, default, TextureLayout.CopyDst, TextureLayout.Sampled); } textureData.SetTexID(Binding(texture)); @@ -319,32 +230,63 @@ 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, + 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.Transition(texture, default, TextureLayout.Sampled, TextureLayout.CopyDst); + commandBuffer.Upload(texture, default, offset, extent, data); + commandBuffer.Transition(texture, default, TextureLayout.CopyDst, TextureLayout.Sampled); } 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, + 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.Transition(texture, default, TextureLayout.Sampled, TextureLayout.CopyDst); + commandBuffer.Upload(texture, default, offset, extent, data); + commandBuffer.Transition(texture, default, TextureLayout.CopyDst, TextureLayout.Sampled); } } } @@ -373,12 +315,11 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa 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 + Usages = BufferUsages.Vertex, + Residency = MemoryResidency.CpuWriteOnly }); } @@ -389,8 +330,20 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa indexBuffer = Context.CreateBuffer(new() { SizeInBytes = totalIndexSizeInBytes, - StrideInBytes = sizeof(ushort), - Flags = BufferUsageFlags.Index | BufferUsageFlags.MapWrite + Usages = BufferUsages.Index, + Residency = MemoryResidency.CpuWriteOnly + }); + } + + 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 = totalConstantSizeInBytes, + Usages = BufferUsages.Constant, + Residency = MemoryResidency.CpuWriteOnly }); } @@ -398,29 +351,55 @@ 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((uint)(sizeof(ImDrawVert) * vertexOffset), new() + { + Pointer = (nint)drawListPtr.VtxBuffer.Data, + SizeInBytes = (uint)(sizeof(ImDrawVert) * drawListPtr.VtxBuffer.Size) + }); - vertexBuffer.Upload(verts, (uint)(sizeof(ImDrawVert) * vertexOffset)); - indexBuffer.Upload(indices, (uint)(sizeof(ushort) * indexOffset)); + 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 + 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 = ArrayPool.Shared.Rent(resourceHandleBindings.Count); + + for (int i = 0; i < resourceHandleBindings.Count; i++) + { + constants[i] = new() + { + Projection = projection, + Texture = resourceHandleBindings.Values[i], + Sampler = sampler.Handle + }; + } + + fixed (Constants* pointer = constants) { - 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) - }], 0); + constantBuffer.Upload(0, new() + { + Pointer = (nint)pointer, + SizeInBytes = (uint)(sizeof(Constants) * resourceHandleBindings.Count) + }); + } + + ArrayPool.Shared.Return(constants); commandBuffer.BeginDebugEvent("ImGui"); - commandBuffer.BeginRenderPass(frameBuffer, clearValue, resourceTableBindings.Values); + commandBuffer.BeginRenderPass([colorAttachment], null); commandBuffer.SetPipeline(graphicsPipeline); commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0); @@ -456,7 +435,7 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa } commandBuffer.SetScissors([scissor]); - commandBuffer.SetResourceTable(resourceTableBindings[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); } } @@ -472,6 +451,7 @@ public void Render(CommandBuffer commandBuffer, FrameBuffer frameBuffer, ClearVa protected override void Destroy() { + constantBuffer?.Dispose(); indexBuffer?.Dispose(); vertexBuffer?.Dispose(); @@ -481,23 +461,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(); - resourceLayout.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) { @@ -514,10 +488,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) { @@ -532,9 +503,15 @@ private void RemoveDestroyedBindings() } } -[StructLayout(LayoutKind.Explicit, Size = 64)] +[StructLayout(LayoutKind.Explicit, Size = 256)] file struct Constants { [FieldOffset(0)] public Matrix4x4 Projection; + + [FieldOffset(64)] + public ResourceHandle Texture; + + [FieldOffset(72)] + public ResourceHandle Sampler; } diff --git a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs index bfb4c7fe..7ae2ef48 100644 --- a/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs +++ b/sources/Extensions/Zenith.NET.Extensions.ImageSharp/Extensions.cs @@ -14,39 +14,75 @@ 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.Upload(texture, default, default, new() { Width = (uint)image.Width, Height = (uint)image.Height, Depth = 1 }, pixels); + CommandBuffer commandBuffer = context.GraphicsQueue.CommandBuffer(); - for (uint i = 1; i < mipLevels; i++) + unsafe { - ZenithHelper.MipDimensions((uint)image.Width, (uint)image.Height, 1, i, out uint mipWidth, out uint mipHeight, out _); + fixed (Rgba32* pPixels = pixels) + { + Extent3D extent = new() + { + Width = (uint)image.Width, + Height = (uint)image.Height, + Depth = 1 + }; + + TextureData data = new() + { + Pointer = (nint)pPixels, + 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.Transition(texture, default, TextureLayout.Undefined, TextureLayout.CopyDst); + commandBuffer.Upload(texture, default, default, extent, data); + commandBuffer.Transition(texture, default, TextureLayout.CopyDst, TextureLayout.Sampled); + } + + 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)); + + pixels = new Rgba32[mipWidth * mipHeight]; + mipImage.CopyPixelDataTo(pixels); - using Image mipImage = image.Clone(ctx => ctx.Resize((int)mipWidth, (int)mipHeight, KnownResamplers.MitchellNetravali)); + fixed (Rgba32* pPixels = pixels) + { + Extent3D extent = new() + { + Width = mipWidth, + Height = mipHeight, + Depth = 1 + }; - pixels = new Rgba32[mipWidth * mipHeight]; - mipImage.CopyPixelDataTo(pixels); + TextureData data = new() + { + Pointer = (nint)pPixels, + 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, new() { Width = mipWidth, Height = mipHeight, Depth = 1 }, pixels); + commandBuffer.Transition(texture, new() { MipLevel = i }, TextureLayout.Undefined, TextureLayout.CopyDst); + commandBuffer.Upload(texture, new() { MipLevel = i }, default, extent, data); + commandBuffer.Transition(texture, new() { MipLevel = i }, TextureLayout.CopyDst, TextureLayout.Sampled); + } + } } - commandBuffer.Submit(true); + commandBuffer.Submit().Wait(); return texture; } 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) 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/NuGet.Packaging.props b/sources/NuGet.Packaging.props index 116220c4..f80d76e1 100644 --- a/sources/NuGet.Packaging.props +++ b/sources/NuGet.Packaging.props @@ -7,10 +7,10 @@ $(MSBuildThisFileDirectory)..\.nuget - 0.0.8 + 1.0.0-rc qian-o Copyright (c) 2026 qian-o - Zenith.NET is a modern, cross-platform graphics and compute library for .NET. It provides a unified GPU programming interface supporting DirectX12, Metal, and Vulkan backends. + Zenith.NET is a modern rendering hardware interface for .NET with one consistent C# API for graphics and compute across DirectX 12, Metal 4, and Vulkan 1.4. Git diff --git a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs index 78cb390a..e693f9b6 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/Surface.cs @@ -1,121 +1,67 @@ -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(GraphicsContext graphicsContext, uint width, uint height) : DisposableObject { - private readonly Texture color; - private readonly Texture depthStencil; - private readonly Buffer pixels; - - public Surface(GraphicsContext graphicsContext, uint width, uint height) + public Texture Drawable { get; } = graphicsContext.CreateTexture(new() { - color = 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 - }); - - 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); - - GraphicsContext = graphicsContext; - Width = width; - Height = height; - } - - public FrameBuffer FrameBuffer { get; } - - public WriteableBitmap WriteableBitmap { get; } + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferSrc | TextureUsages.TransferDst + }); - public GraphicsContext GraphicsContext { get; } + public WriteableBitmap Bitmap { get; } = new(new((int)width, (int)height), new(96, 96), DrawableFormat(), AlphaFormat.Premul); - public uint Width { get; } + public uint Width { get; } = width; - public uint Height { get; } + public uint Height { get; } = height; - public void Present() + public void Flush(CommandBuffer commandBuffer) { - 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) + Extent3D extent = new() { - Unsafe.CopyBlock((void*)lockedFramebuffer.Address, (void*)mappedMemory.Pointer, mappedMemory.SizeInBytes); - } - else + Width = Width, + Height = Height, + Depth = 1 + }; + + TextureData data = new() { - Parallel.For(0, Height, y => - { - byte* srcPtr = (byte*)mappedMemory.Pointer + (rowPitchInBytes * y); - byte* dstPtr = (byte*)lockedFramebuffer.Address + (lockedFramebuffer.RowBytes * y); + Pointer = lockedFramebuffer.Address, + SizeInBytes = (uint)(lockedFramebuffer.RowBytes * Height), + RowStrideInBytes = (uint)lockedFramebuffer.RowBytes, + SliceStrideInBytes = (uint)(lockedFramebuffer.RowBytes * Height) + }; - Unsafe.CopyBlock(dstPtr, srcPtr, (uint)lockedFramebuffer.RowBytes); - }); - } + commandBuffer.Download(Drawable, default, default, extent, data); - pixels.Unmap(); + commandBuffer.Submit().Wait(); } protected override void Destroy() { - WriteableBitmap.Dispose(); - FrameBuffer.Dispose(); - - pixels.Dispose(); - depthStencil.Dispose(); - color.Dispose(); + Bitmap.Dispose(); + Drawable.Dispose(); } - private static AvaloniaPixelFormat ColorFormat() + private static AvaloniaPixelFormat DrawableFormat() { - return ZenithViewHelper.ColorFormat switch + return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => AvaloniaPixelFormat.Rgba8888, PixelFormat.B8G8R8A8UNorm => AvaloniaPixelFormat.Bgra8888, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") + _ => default }; } } diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index c8a9b38b..6557c768 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) @@ -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, @@ -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)); @@ -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() @@ -104,19 +104,25 @@ void IZenithView.EnsureResources() void IZenithView.Tick() { - if (surface is null) + if (GraphicsContext is null || surface is null) { return; } + CommandBuffer commandBuffer = GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + 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, commandBuffer, surface.Drawable)); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.CopySrc); + + surface.Flush(commandBuffer); } void IZenithView.Present() { - surface?.Present(); - InvalidateVisual(); } 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..8dd4bcf5 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 + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -38,13 +37,21 @@ public void EnsureResources() public void Tick() { - if (!ValidateSurface() || swapChain is null) + if (!ValidateSurface() || handler.VirtualView.GraphicsContext is null || swapChain is null) { return; } + CommandBuffer commandBuffer = handler.VirtualView.GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(commandBuffer, swapChain.Drawable); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit().Wait(); } 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..35d0d1c2 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 + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -39,13 +38,21 @@ public void EnsureResources() public void Tick() { - if (swapChain is null) + if (handler.VirtualView.GraphicsContext is null || swapChain is null) { return; } + CommandBuffer commandBuffer = handler.VirtualView.GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(commandBuffer, swapChain.Drawable); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit().Wait(); } 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..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,29 +7,29 @@ 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(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 deleted file mode 100644 index 9e17d24a..00000000 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/D3DTexture.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D11; -using Silk.NET.DXGI; - -namespace Zenith.NET.Views.Maui.Platforms.Windows; - -internal unsafe partial class D3DTexture : DisposableObject -{ - [LibraryImport("kernel32")] - private static partial int CloseHandle(nint hObject); - - public ComPtr SwapChain; - - public ComPtr Texture; - - public ComPtr Mutex; - - public nint Handle; - - public nint SharedHandle; - - private ulong key; - - public D3DTexture(uint width, uint height) - { - SwapChainDesc1 swapChainDesc = new() - { - Width = width, - Height = height, - Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, - 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())); - - Texture2DDesc texture2DDesc = new() - { - Width = width, - Height = height, - MipLevels = 1, - ArraySize = 1, - Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, - BindFlags = (uint)BindFlag.RenderTarget, - MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) - }; - - D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, null, ref Texture)); - - D3D.Success(Texture.QueryInterface(out Mutex)); - - using ComPtr resource = Texture.QueryInterface(); - - void* sharedHandle = null; - D3D.Success(resource.CreateSharedHandle((SecurityAttributes*)null, DXGI.SharedResourceRead | DXGI.SharedResourceWrite, (char*)null, &sharedHandle)); - - Handle = (nint)Texture.Handle; - SharedHandle = (nint)sharedHandle; - - Width = width; - Height = height; - } - - public uint Width { get; } - - public uint Height { get; } - - public void AcquireSync() - { - D3D.Success(Mutex.AcquireSync(key++, uint.MaxValue)); - } - - public void ReleaseSync() - { - D3D.Success(Mutex.ReleaseSync(key)); - } - - public void Present() - { - D3D.Success(SwapChain.GetBuffer(0, out ComPtr backBuffer)); - - AcquireSync(); - - D3D.DeviceContext.CopyResource((ID3D11Resource*)backBuffer.Handle, (ID3D11Resource*)Texture.Handle); - D3D.DeviceContext.Flush(); - - ReleaseSync(); - - backBuffer.Dispose(); - - D3D.Success(SwapChain.Present(1, 0)); - } - - protected override void Destroy() - { - if (CloseHandle(SharedHandle) is 0) - { - Debug.WriteLine("Failed to close shared handle."); - } - - Mutex.Dispose(); - Texture.Dispose(); - SwapChain.Dispose(); - } - - private static Format ColorFormat() - { - return ZenithViewHelper.ColorFormat switch - { - PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, - PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} 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 5242cf0e..8bc71bdd 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,50 +17,47 @@ 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), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat - }); - - this.As().SetSwapChain(texture.SwapChain); + this.As().SetSwapChain(surface.SwapChain); } } public void Tick() { - if (texture is null || swapChain is null) + if (handler.VirtualView.GraphicsContext is null || surface is null) { return; } - texture.AcquireSync(); + surface.AcquireSync(); + + CommandBuffer commandBuffer = handler.VirtualView.GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(commandBuffer, surface.Drawable); - texture.ReleaseSync(); + commandBuffer.Transition(surface.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Common); + + commandBuffer.Submit().Wait(); + + 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/Surface.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs new file mode 100644 index 00000000..52163433 --- /dev/null +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -0,0 +1,130 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; + +namespace Zenith.NET.Views.Maui.Platforms.Windows; + +internal unsafe partial class Surface : DisposableObject +{ + [LibraryImport("kernel32")] + private static partial int CloseHandle(nint hObject); + + public ComPtr SwapChain = new(); + + public ComPtr Texture = new(); + + public ComPtr Mutex = new(); + + public nint SharedHandle; + + private ulong key; + + public Surface(GraphicsContext graphicsContext, uint width, uint height) + { + SwapChainDesc1 swapChainDesc = new() + { + Width = width, + Height = height, + Format = DrawableFormat(), + SampleDesc = new() { Count = 1 }, + BufferUsage = DXGI.UsageRenderTargetOutput, + BufferCount = 3, + Scaling = Scaling.Stretch, + SwapEffect = SwapEffect.FlipSequential + }; + + D3D.Success(D3D.Factory.CreateSwapChainForComposition((IUnknown*)D3D.Device.Handle, &swapChainDesc, default(IDXGIOutput*), SwapChain.GetAddressOf())); + + Texture2DDesc texture2DDesc = new() + { + Width = width, + Height = height, + MipLevels = 1, + ArraySize = 1, + Format = DrawableFormat(), + SampleDesc = new() { Count = 1 }, + BindFlags = (uint)BindFlag.RenderTarget, + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) + }; + + D3D.Success(D3D.Device.CreateTexture2D(&texture2DDesc, default(SubresourceData*), Texture.GetAddressOf())); + + D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)Mutex.GetAddressOf())); + + 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)); + + Drawable = graphicsContext.CreateTexture(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.TransferDst + }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); + } + + public uint Width { get; } + + public uint Height { get; } + + public Texture Drawable { get; } + + public void AcquireSync() + { + D3D.Success(Mutex.AcquireSync(key++, uint.MaxValue)); + } + + public void ReleaseSync() + { + D3D.Success(Mutex.ReleaseSync(key)); + } + + public void Present() + { + using ComPtr backBuffer = new(); + D3D.Success(SwapChain.GetBuffer(0, SilkMarshal.GuidPtrOf(), (void**)backBuffer.GetAddressOf())); + + AcquireSync(); + + D3D.DeviceContext.CopyResource((ID3D11Resource*)backBuffer.Handle, (ID3D11Resource*)Texture.Handle); + D3D.DeviceContext.Flush(); + + ReleaseSync(); + + D3D.Success(SwapChain.Present(1, 0)); + } + + protected override void Destroy() + { + Drawable.Dispose(); + + if (CloseHandle(SharedHandle) is 0) + { + Debug.WriteLine("Failed to close shared handle."); + } + + Mutex.Dispose(); + Texture.Dispose(); + SwapChain.Dispose(); + } + + private static Format DrawableFormat() + { + return ZenithViewHelper.DrawableFormat switch + { + PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, + PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, + _ => default + }; + } +} \ No newline at end of file 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..3a5bddc1 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 + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -39,13 +38,21 @@ public void EnsureResources() public void Tick() { - if (swapChain is null) + if (handler.VirtualView.GraphicsContext is null || swapChain is null) { return; } + CommandBuffer commandBuffer = handler.VirtualView.GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + handler.VirtualView.OnUpdateRequested(); - handler.VirtualView.OnRenderRequested(swapChain.FrameBuffer); + handler.VirtualView.OnRenderRequested(commandBuffer, swapChain.Drawable); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit().Wait(); } 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..cff11353 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 { @@ -86,9 +29,9 @@ internal void OnUpdateRequested() UpdateRequested?.Invoke(this, new(scheduler.UpdateSeconds, scheduler.TotalSeconds)); } - internal void OnRenderRequested(FrameBuffer frameBuffer) + internal void OnRenderRequested(CommandBuffer commandBuffer, Texture drawable) { - RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, frameBuffer)); + RenderRequested?.Invoke(this, new(scheduler.RenderSeconds, scheduler.TotalSeconds, commandBuffer, drawable)); } void IZenithView.UI(Action action) 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 diff --git a/sources/Views/Zenith.NET.Views.WPF/D3D.cs b/sources/Views/Zenith.NET.Views.WPF/D3D.cs index cf7bf6f5..4c8eaa64 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; @@ -10,20 +9,20 @@ 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() { 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 deleted file mode 100644 index 03615e07..00000000 --- a/sources/Views/Zenith.NET.Views.WPF/D3DTexture.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Windows.Interop; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D11; -using Silk.NET.Direct3D9; -using Silk.NET.DXGI; -using D3D9Format = Silk.NET.Direct3D9.Format; -using DXGIFormat = Silk.NET.DXGI.Format; - -namespace Zenith.NET.Views.WPF; - -internal unsafe partial class D3DTexture : DisposableObject -{ - [LibraryImport("kernel32")] - private static partial int CloseHandle(nint hObject); - - public ComPtr D3D9RenderTarget; - - public ComPtr D3D9RenderSurface; - - public ComPtr D3D9SharedTexture; - - public ComPtr D3D11RenderTarget; - - public ComPtr D3D11Mutex; - - public nint SharedHandle; - - private ulong key; - - public D3DTexture(uint width, uint height, D3DImage image) - { - void* sharedHandle = null; - D3D.Success(D3D.D3D9DeviceEx.CreateTexture(width, - height, - 1, - D3D9.UsageRendertarget, - D3D9Format.A8R8G8B8, - Pool.Default, - ref D3D9RenderTarget, - &sharedHandle)); - - D3D.Success(D3D9RenderTarget.GetSurfaceLevel(0, ref D3D9RenderSurface)); - D3D.Success(D3D.D3D11Device.OpenSharedResource(sharedHandle, out D3D9SharedTexture)); - - Texture2DDesc desc = new() - { - Width = width, - Height = height, - MipLevels = 1, - ArraySize = 1, - Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, - BindFlags = (uint)BindFlag.RenderTarget, - MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) - }; - - D3D.Success(D3D.D3D11Device.CreateTexture2D(&desc, null, ref D3D11RenderTarget)); - - 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)); - - SharedHandle = (nint)sharedHandle; - - Width = width; - Height = height; - Image = image; - } - - public uint Width { get; } - - public uint Height { get; } - - public D3DImage Image { get; } - - public void AcquireSync() - { - D3D.Success(D3D11Mutex.AcquireSync(key++, uint.MaxValue)); - } - - public void ReleaseSync() - { - D3D.Success(D3D11Mutex.ReleaseSync(key)); - } - - public void Present() - { - Image.Lock(); - Image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (nint)D3D9RenderSurface.Handle); - - AcquireSync(); - - D3D.D3D11DeviceContext.CopyResource((ID3D11Resource*)D3D9SharedTexture.Handle, (ID3D11Resource*)D3D11RenderTarget.Handle); - D3D.D3D11DeviceContext.Flush(); - - ReleaseSync(); - - Image.AddDirtyRect(new(0, 0, (int)Width, (int)Height)); - Image.Unlock(); - } - - protected override void Destroy() - { - if (CloseHandle(SharedHandle) is 0) - { - Debug.WriteLine("Failed to close shared handle."); - } - - D3D11Mutex.Dispose(); - D3D11RenderTarget.Dispose(); - D3D9SharedTexture.Dispose(); - D3D9RenderSurface.Dispose(); - D3D9RenderTarget.Dispose(); - } - - private static DXGIFormat ColorFormat() - { - return ZenithViewHelper.ColorFormat switch - { - PixelFormat.R8G8B8A8UNorm => DXGIFormat.FormatR8G8B8A8Unorm, - PixelFormat.B8G8R8A8UNorm => DXGIFormat.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") - }; - } -} \ 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 new file mode 100644 index 00000000..332cd3c7 --- /dev/null +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -0,0 +1,140 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Interop; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.Direct3D9; +using Silk.NET.DXGI; +using D3D9Format = Silk.NET.Direct3D9.Format; +using DXGIFormat = Silk.NET.DXGI.Format; + +namespace Zenith.NET.Views.WPF; + +internal unsafe partial class Surface : DisposableObject +{ + [LibraryImport("kernel32")] + private static partial int CloseHandle(nint hObject); + + public ComPtr D3D9RenderTarget = new(); + + public ComPtr D3D9RenderSurface = new(); + + public ComPtr D3D9SharedTexture = new(); + + public ComPtr D3D11RenderTarget = new(); + + public ComPtr D3D11Mutex = new(); + + public nint SharedHandle; + + private ulong key; + + public Surface(GraphicsContext graphicsContext, uint width, uint height) + { + void* sharedHandle = null; + D3D.Success(D3D.D3D9DeviceEx.CreateTexture(width, + height, + 1, + D3D9.UsageRendertarget, + D3D9Format.A8R8G8B8, + Pool.Default, + D3D9RenderTarget.GetAddressOf(), + &sharedHandle)); + + D3D.Success(D3D9RenderTarget.GetSurfaceLevel(0, D3D9RenderSurface.GetAddressOf())); + D3D.Success(D3D.D3D11Device.OpenSharedResource(sharedHandle, SilkMarshal.GuidPtrOf(), (void**)D3D9SharedTexture.GetAddressOf())); + + Texture2DDesc desc = new() + { + Width = width, + Height = height, + MipLevels = 1, + ArraySize = 1, + Format = DrawableFormat(), + SampleDesc = new() { Count = 1 }, + BindFlags = (uint)BindFlag.RenderTarget, + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) + }; + + D3D.Success(D3D.D3D11Device.CreateTexture2D(&desc, default(SubresourceData*), D3D11RenderTarget.GetAddressOf())); + + D3D.Success(D3D11RenderTarget.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)D3D11Mutex.GetAddressOf())); + + 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)); + + Drawable = graphicsContext.CreateTexture(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.TransferDst + }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); + } + + public uint Width { get; } + + public uint Height { get; } + + public Texture Drawable { get; } + + public void AcquireSync() + { + D3D.Success(D3D11Mutex.AcquireSync(key++, uint.MaxValue)); + } + + public void ReleaseSync() + { + D3D.Success(D3D11Mutex.ReleaseSync(key)); + } + + public void Present(D3DImage image) + { + image.Lock(); + image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (nint)D3D9RenderSurface.Handle); + + AcquireSync(); + + D3D.D3D11DeviceContext.CopyResource((ID3D11Resource*)D3D9SharedTexture.Handle, (ID3D11Resource*)D3D11RenderTarget.Handle); + D3D.D3D11DeviceContext.Flush(); + + ReleaseSync(); + + 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."); + } + + D3D11Mutex.Dispose(); + D3D11RenderTarget.Dispose(); + D3D9SharedTexture.Dispose(); + D3D9RenderSurface.Dispose(); + D3D9RenderTarget.Dispose(); + } + + private static DXGIFormat DrawableFormat() + { + return ZenithViewHelper.DrawableFormat switch + { + PixelFormat.R8G8B8A8UNorm => DXGIFormat.FormatR8G8B8A8Unorm, + PixelFormat.B8G8R8A8UNorm => DXGIFormat.FormatB8G8R8A8Unorm, + _ => default + }; + } +} \ 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 29b914e4..c0d80027 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() { @@ -79,8 +78,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)); @@ -102,50 +101,47 @@ 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), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat - }); + surface = new(GraphicsContext, width, height); } } void IZenithView.Tick() { - if (texture is null || swapChain is null) + if (GraphicsContext is null || surface is null) { return; } - texture.AcquireSync(); + surface.AcquireSync(); + + CommandBuffer commandBuffer = GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); 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, commandBuffer, surface.Drawable)); - texture.ReleaseSync(); + commandBuffer.Transition(surface.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Common); + + commandBuffer.Submit().Wait(); + + 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 c92671d4..97d6a1e3 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 + Format = ZenithViewHelper.DrawableFormat }); } else if (swapChain.Desc.Surface.Width != width || swapChain.Desc.Surface.Height != height) @@ -93,13 +92,21 @@ void IZenithView.EnsureResources() void IZenithView.Tick() { - if (swapChain is null) + if (GraphicsContext is null || swapChain is null) { return; } + CommandBuffer commandBuffer = GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + 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, commandBuffer, swapChain.Drawable)); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present); + + commandBuffer.Submit().Wait(); } 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..a380ff20 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.Uno.cs @@ -23,19 +23,27 @@ 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 }; } } void IZenithView.Tick() { - if (surface is null) + if (GraphicsContext is null || surface is null) { return; } + CommandBuffer commandBuffer = GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + 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, commandBuffer, surface.Drawable)); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.CopySrc); + + surface.Flush(commandBuffer); } void IZenithView.Present() @@ -50,128 +58,74 @@ void IZenithView.ReleaseResources() } } -internal unsafe class Surface : DisposableObject +internal unsafe class Surface(GraphicsContext context, uint width, uint height) : DisposableObject { - private readonly Texture color; - private readonly Texture depthStencil; - private readonly Buffer pixels; + private readonly byte[] pixels = new byte[width * height * 4]; - public Surface(GraphicsContext context, uint width, uint height) + public Texture Drawable { get; } = context.CreateTexture(new() { - color = 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 - }); - - 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); - - Context = context; - Width = width; - Height = height; - } - - public FrameBuffer FrameBuffer { get; } - - public WriteableBitmap WriteableBitmap { get; } + Type = TextureType.Texture2D, + Format = ZenithViewHelper.DrawableFormat, + Width = width, + Height = height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.ColorAttachment | TextureUsages.TransferSrc | TextureUsages.TransferDst + }); - public GraphicsContext Context { get; } + public WriteableBitmap Bitmap { get; } = new((int)width, (int)height); - public uint Width { get; } + public uint Width { get; } = width; - public uint Height { get; } + public uint Height { get; } = height; - public void Present() + public void Flush(CommandBuffer commandBuffer) { - CommandBuffer commandBuffer = Context.Graphics.CommandBuffer(); - commandBuffer.CopyTextureToBuffer(color, default, default, new() { Width = Width, Height = Height, Depth = 1 }, pixels, 0); - commandBuffer.Submit(true); + fixed (byte* pPixels = pixels) + { + Extent3D extent = new() + { + Width = Width, + Height = Height, + Depth = 1 + }; - uint rowPitchInBytes = ZenithHelper.Align(Width * 4, GraphicsContext.TextureRowPitchAlignment); + TextureData data = new() + { + Pointer = (nint)pPixels, + SizeInBytes = (uint)pixels.Length, + RowStrideInBytes = Width * 4, + SliceStrideInBytes = (uint)pixels.Length + }; - using (Stream stream = WriteableBitmap.PixelBuffer.AsStream()) - { - MappedMemory mappedMemory = pixels.Map(); + commandBuffer.Download(Drawable, default, default, extent, data); - byte* pointer = (byte*)mappedMemory.Pointer; + commandBuffer.Submit().Wait(); + } - switch (ZenithViewHelper.ColorFormat) + if (ZenithViewHelper.DrawableFormat 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); } - protected override void Destroy() + public void Present() { - WriteableBitmap.Dispose(); - FrameBuffer.Dispose(); + Bitmap.Invalidate(); + } - pixels.Dispose(); - depthStencil.Dispose(); - color.Dispose(); + protected override void Destroy() + { + Bitmap.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 043804dc..875ee498 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,79 +23,76 @@ 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), - ColorTargetFormat = ZenithViewHelper.ColorFormat, - DepthStencilTargetFormat = ZenithViewHelper.DepthStencilFormat - }); - - this.As().SetSwapChain(texture.SwapChain); + this.As().SetSwapChain(surface.SwapChain); } } void IZenithView.Tick() { - if (texture is null || swapChain is null) + if (GraphicsContext is null || surface is null) { return; } - texture.AcquireSync(); + surface.AcquireSync(); + + CommandBuffer commandBuffer = GraphicsContext.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); 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, commandBuffer, surface.Drawable)); + + commandBuffer.Transition(surface.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Common); - texture.ReleaseSync(); + commandBuffer.Submit().Wait(); + + 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; } } 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(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; } @@ -112,38 +108,36 @@ 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); - public ComPtr SwapChain; - - public ComPtr Texture; + public ComPtr SwapChain = new(); - public ComPtr Mutex; + public ComPtr Texture = new(); - public nint Handle; + public ComPtr Mutex = new(); public nint SharedHandle; private ulong key; - public D3DTexture(uint width, uint height) + public Surface(GraphicsContext graphicsContext, uint width, uint height) { SwapChainDesc1 swapChainDesc = new() { Width = width, Height = height, - Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + Format = DrawableFormat(), + SampleDesc = new() { Count = 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,32 +145,42 @@ public D3DTexture(uint width, uint height) Height = height, MipLevels = 1, ArraySize = 1, - Format = ColorFormat(), - SampleDesc = new() { Count = 1, Quality = 0 }, + Format = DrawableFormat(), + SampleDesc = new() { Count = 1 }, BindFlags = (uint)BindFlag.RenderTarget, - MiscFlags = (uint)(ResourceMiscFlag.SharedNthandle | ResourceMiscFlag.SharedKeyedmutex) + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) }; - 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(); + using ComPtr resource = new(); + D3D.Success(Texture.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf())); 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; - - Width = width; - Height = height; + Drawable = graphicsContext.CreateTexture(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.TransferDst + }, NativeTextureType.D3D11TextureNtHandle, SharedHandle = (nint)sharedHandle); } public uint Width { get; } public uint Height { get; } + public Texture Drawable { get; } + public void AcquireSync() { D3D.Success(Mutex.AcquireSync(key++, uint.MaxValue)); @@ -189,7 +193,8 @@ 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(); @@ -198,13 +203,13 @@ public void Present() ReleaseSync(); - backBuffer.Dispose(); - D3D.Success(SwapChain.Present(1, 0)); } protected override void Destroy() { + Drawable.Dispose(); + if (CloseHandle(SharedHandle) is 0) { Debug.WriteLine("Failed to close shared handle."); @@ -215,13 +220,13 @@ protected override void Destroy() SwapChain.Dispose(); } - private static Format ColorFormat() + private static Format DrawableFormat() { - return ZenithViewHelper.ColorFormat switch + return ZenithViewHelper.DrawableFormat switch { PixelFormat.R8G8B8A8UNorm => Format.FormatR8G8B8A8Unorm, PixelFormat.B8G8R8A8UNorm => Format.FormatB8G8R8A8Unorm, - _ => throw new NotSupportedException($"Pixel format {ZenithViewHelper.ColorFormat} is not supported.") + _ => default }; } } 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/Views/Zenith.NET.Views/RenderEventArgs.cs b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs index 960b3174..98916a16 100644 --- a/sources/Views/Zenith.NET.Views/RenderEventArgs.cs +++ b/sources/Views/Zenith.NET.Views/RenderEventArgs.cs @@ -1,10 +1,12 @@ namespace Zenith.NET.Views; -public class RenderEventArgs(double deltaSeconds, double totalSeconds, FrameBuffer frameBuffer) : EventArgs +public class RenderEventArgs(double deltaSeconds, double totalSeconds, CommandBuffer commandBuffer, Texture drawable) : EventArgs { public double DeltaSeconds { get; } = deltaSeconds; public double TotalSeconds { get; } = totalSeconds; - public FrameBuffer FrameBuffer { get; } = frameBuffer; + public CommandBuffer CommandBuffer { get; } = commandBuffer; + + public Texture Drawable { get; } = drawable; } diff --git a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs index 0ba58af1..095fd105 100644 --- a/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs +++ b/sources/Views/Zenith.NET.Views/ZenithViewHelper.cs @@ -2,14 +2,5 @@ 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 - }; + public static PixelFormat DrawableFormat { get; } = OperatingSystem.IsAndroid() ? PixelFormat.R8G8B8A8UNorm : PixelFormat.B8G8R8A8UNorm; } diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index cdedcc74..34534b32 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -6,126 +6,139 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXBottomLevelAccelerationStructure : BottomLevelAccelerationStructure { - public DXBottomLevelAccelerationStructure(DXGraphicsContext context, BottomLevelAccelerationStructureDesc desc, DXCommandBuffer commandBuffer) : base(context, desc) + public DXBottomLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) { using ZenithMarshal.Scope scope = new(); - uint geometryCount = (uint)desc.Geometries.Length; + Transform = new(context, new() + { + SizeInBytes = (uint)(sizeof(Matrix3X4) * desc.Geometries.Length), + Residency = MemoryResidency.CpuWriteOnly + }); + + BuildRaytracingAccelerationStructureInputs inputs = Inputs(scope, desc); + + RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); + context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - TransformBuffer = new(context, new() + AccelerationStructure = new(context, new() { - SizeInBytes = (uint)(sizeof(Matrix3X4) * geometryCount), - StrideInBytes = (uint)sizeof(Matrix3X4), - Flags = BufferUsageFlags.MapWrite + SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, + Residency = MemoryResidency.GpuOnly + }, ResourceFlags.RaytracingAccelerationStructure); + + Scratch = new(context, new() + { + SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly }); - MappedMemory mappedMemory = TransformBuffer.Map(); + BuildRaytracingAccelerationStructureDesc buildDesc = new() + { + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, + Inputs = inputs, + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress + }; + + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + } + + public DXBuffer Transform { get; } + + public DXBuffer AccelerationStructure { get; } + + public DXBuffer Scratch { get; } + + public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) + { + using ZenithMarshal.Scope scope = new(); + + BuildRaytracingAccelerationStructureInputs inputs = Inputs(scope, newDesc); + inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; + + BuildRaytracingAccelerationStructureDesc buildDesc = new() + { + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, + Inputs = inputs, + SourceAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress + }; + + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + AccelerationStructure.Name = name; + } + + protected override void Destroy() + { + Scratch.Dispose(); + AccelerationStructure.Dispose(); + Transform.Dispose(); + } - desc.Geometries.Select(static item => DXFormats.DirectX12(item.Triangles.Transform)).ToArray().CopyTo(new Span>((Matrix3X4*)mappedMemory.Pointer, (int)geometryCount)); + private BuildRaytracingAccelerationStructureInputs Inputs(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc) + { + uint geometryCount = (uint)desc.Geometries.Length; - TransformBuffer.Unmap(); + nint pointer = Transform.Map(); + Matrix3X4* transforms = (Matrix3X4*)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), - Flags = DXFormats.DirectX12(geometry.Flags), + Flags = geometry.IsOpaque ? RaytracingGeometryFlags.Opaque : RaytracingGeometryFlags.None, Anonymous = new ( - triangles: geometry.Type is RayTracingGeometryType.Triangles ? new() + triangles: geometry.Type is RayTracingGeometryType.Triangle ? 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, + 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, + VertexCount = geometry.TriangleGeometry.VertexCount, + IndexBuffer = geometry.TriangleGeometry.IndexBuffer is not null ? geometry.TriangleGeometry.IndexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.IndexOffsetInBytes : 0ul, VertexBuffer = new() { - StartAddress = geometry.Triangles.VertexBuffer.DirectX12().GPUVirtualAddress + geometry.Triangles.VertexOffsetInBytes, - StrideInBytes = geometry.Triangles.VertexStrideInBytes + StartAddress = geometry.TriangleGeometry.VertexBuffer.DirectX12().GPUVirtualAddress + geometry.TriangleGeometry.VertexOffsetInBytes, + StrideInBytes = geometry.TriangleGeometry.VertexStrideInBytes } } : null, - aABBs: geometry.Type is RayTracingGeometryType.AABBs ? new() + aABBs: geometry.Type is RayTracingGeometryType.Aabb ? new() { - AABBCount = geometry.AABBs.Count, + AABBCount = geometry.AabbGeometry.Count, AABBs = new() { - StartAddress = geometry.AABBs.Buffer.DirectX12().GPUVirtualAddress + geometry.AABBs.OffsetInBytes, - StrideInBytes = geometry.AABBs.StrideInBytes + StartAddress = geometry.AabbGeometry.Buffer.DirectX12().GPUVirtualAddress + geometry.AabbGeometry.OffsetInBytes, + StrideInBytes = geometry.AabbGeometry.StrideInBytes } } : null ) }; } - BuildRaytracingAccelerationStructureInputs inputs = new() + Transform.Unmap(); + + return new() { Type = RaytracingAccelerationStructureType.BottomLevel, - Flags = DXFormats.DirectX12(desc.Flags), + Flags = DXFormats.DirectX12(desc.BuildFlags), 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 index 5ed70956..9cf81f2c 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,74 +12,97 @@ internal unsafe class DXBuffer : Buffer 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 - }; + ResourceDesc1 resourceDesc = ResourceDesc(desc); - Heap = new(context, resourceDesc, DXFormats.DirectX12(desc.Flags).Type, HeapFlags.AllowOnlyBuffers); + HeapProperties heapProperties = new() { Type = DXFormats.DirectX12(desc.Residency) }; - context.Device.CreatePlacedResource(Heap.Heap, 0, &resourceDesc, States = DXFormats.DirectX12(desc.Flags).States, null, out Resource).Success(); + 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, - OffsetInBytes = 0, SizeInBytes = desc.SizeInBytes, StrideInBytes = desc.StrideInBytes }); } - public DXHeap Heap { get; } + public DXBuffer(DXGraphicsContext context, BufferDesc desc, ComPtr resource) : base(context, desc) + { + Resource = resource; - public DXBufferView View { get; } + GPUVirtualAddress = Resource.GetGPUVirtualAddress(); - public ResourceStates States { get; set; } + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); + } - public override MappedMemory Map() + public DXBuffer(DXGraphicsContext context, BufferDesc desc, ResourceFlags flags) : base(context, desc) { - void* pointer; - Resource.Map(0, (DxRange*)null, &pointer).Success(); + 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(); - return new() { Pointer = (nint)pointer, SizeInBytes = Desc.SizeInBytes }; + View = new(context, new() + { + Buffer = this, + SizeInBytes = desc.SizeInBytes, + StrideInBytes = desc.StrideInBytes + }); } - public override void Unmap() + public DXBufferView 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) { - Resource.Unmap(0, (DxRange*)null); + return 0; } - public void TransitionStates(DXCommandBuffer commandBuffer, ResourceStates newStates) + public override nint Map() { - 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 - } - }; + void* pointer; + Resource.Map(0, default(DxRange*), &pointer).Success(); - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); + return (nint)pointer; + } - States = newStates; + public override void Unmap() + { + Resource.Unmap(0, default(DxRange*)); } protected override void SetResourceName(string name) @@ -89,9 +113,21 @@ protected override void SetResourceName(string name) protected override void Destroy() { View.Dispose(); - Resource.Dispose(); + } - Heap.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() { Count = 1 }, + Layout = DxTextureLayout.LayoutRowMajor, + Flags = DXFormats.DirectX12(desc.Usages) + }; } } diff --git a/sources/Zenith.NET.DirectX12/DXBufferView.cs b/sources/Zenith.NET.DirectX12/DXBufferView.cs index 9f0ba1fb..a9f4ae7e 100644 --- a/sources/Zenith.NET.DirectX12/DXBufferView.cs +++ b/sources/Zenith.NET.DirectX12/DXBufferView.cs @@ -8,11 +8,16 @@ internal unsafe class DXBufferView(DXGraphicsContext context, BufferViewDesc des private DXDescriptorToken? srvToken; private DXDescriptorToken? uavToken; - public CpuDescriptorHandle CbvHandle => (cbvToken ??= CreateCbvToken()).Handle; + public override ResourceHandle ConstantHandle => (cbvToken ??= CreateCbvToken()).ResourceHandle; - public CpuDescriptorHandle SrvHandle => (srvToken ??= CreateSrvToken()).Handle; + public override ResourceHandle StorageReadOnlyHandle => (srvToken ??= CreateSrvToken()).ResourceHandle; - public CpuDescriptorHandle UavHandle => (uavToken ??= CreateUavToken()).Handle; + public override ResourceHandle StorageReadWriteHandle => (uavToken ??= CreateUavToken()).ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } protected override void SetResourceName(string name) { @@ -27,7 +32,7 @@ protected override void Destroy() private DXDescriptorToken CreateCbvToken() { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); ConstantBufferViewDesc viewDesc = new() { @@ -35,14 +40,14 @@ private DXDescriptorToken CreateCbvToken() SizeInBytes = ZenithHelper.Align(Desc.SizeInBytes, 256u) }; - context.Device.CreateConstantBufferView(&viewDesc, token.Handle); + context.Device.CreateConstantBufferView(&viewDesc, token.CpuHandle); return token; } private DXDescriptorToken CreateSrvToken() { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); ShaderResourceViewDesc viewDesc = new() { @@ -56,14 +61,14 @@ private DXDescriptorToken CreateSrvToken() } }; - context.Device.CreateShaderResourceView(Desc.Buffer.DirectX12().Resource, &viewDesc, token.Handle); + context.Device.CreateShaderResourceView(Desc.Buffer.DirectX12().Resource, &viewDesc, token.CpuHandle); return token; } private DXDescriptorToken CreateUavToken() { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); UnorderedAccessViewDesc viewDesc = new() { @@ -76,7 +81,7 @@ private DXDescriptorToken CreateUavToken() } }; - context.Device.CreateUnorderedAccessView(Desc.Buffer.DirectX12().Resource, (ID3D12Resource*)null, &viewDesc, token.Handle); + 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 13f0400a..3d99a2fb 100644 --- a/sources/Zenith.NET.DirectX12/DXCapabilities.cs +++ b/sources/Zenith.NET.DirectX12/DXCapabilities.cs @@ -9,7 +9,7 @@ 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.Device.CheckFeatureSupport(Feature.D3D12Options5, &options5, (uint)sizeof(FeatureDataD3D12Options5)).Success(); @@ -17,9 +17,9 @@ 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; + MeshShadingSupported = options7.MeshShaderTier >= MeshShaderTier.Tier1; } public override string DeviceName { get; } diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index 7f08263a..dd86760d 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -1,83 +1,100 @@ -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; namespace Zenith.NET.DirectX12; internal unsafe class DXCommandBuffer : CommandBuffer { - private readonly DXDescriptorTable? cbvSrvUavTable; - private readonly DXDescriptorTable? samplerTable; - public ComPtr CommandAllocator; - public ComPtr CommandList; - - public ComPtr GraphicsCommandList4; - - public ComPtr? GraphicsCommandList6; + public ComPtr CommandList; 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), 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.CreateCommandAllocator(DXFormats.DirectX12(queue.Type), out CommandAllocator).Success(); + public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - context.Device.CreateCommandList(0, DXFormats.DirectX12(queue.Type), CommandAllocator, (ComPtr)null, out CommandList).Success(); + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } - CommandList.QueryInterface(out GraphicsCommandList4).Success(); + protected override void BarrierImpl(BarrierStages before, BarrierStages after) + { + (BarrierSync syncBefore, BarrierAccess accessBefore) = DXFormats.DirectX12(before); + (BarrierSync syncAfter, BarrierAccess accessAfter) = DXFormats.DirectX12(after); - if (CommandList.QueryInterface(out ComPtr graphicsCommandList6).IsSuccess()) + GlobalBarrier barrier = new() { - GraphicsCommandList6 = graphicsCommandList6; - } - - CanTransitionResourceStates = queue.Type is not CommandQueueType.Copy; - } + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter + }; - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Global, + NumBarriers = 1, + PGlobalBarriers = &barrier + }; - public bool CanTransitionResourceStates { get; } + CommandList.Barrier(1, &barrierGroup); + } - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) + protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout before, TextureLayout after) { - DXBuffer dxSrc = src.DirectX12(); - DXBuffer dxDest = dest.DirectX12(); + DXTexture dxTexture = texture.DirectX12(); - ResourceStates srcOldStates = dxSrc.States; - ResourceStates destOldStates = dxDest.States; + (BarrierSync syncBefore, BarrierAccess accessBefore, BarrierLayout layoutBefore) = DXFormats.DirectX12(before); + (BarrierSync syncAfter, BarrierAccess accessAfter, BarrierLayout layoutAfter) = DXFormats.DirectX12(after); - dxSrc.TransitionStates(this, ResourceStates.CopySource); - dxDest.TransitionStates(this, ResourceStates.CopyDest); + DxTextureBarrier barrier = new() + { + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter, + LayoutBefore = layoutBefore, + LayoutAfter = layoutAfter, + PResource = dxTexture.Resource, + Subresources = new() + { + IndexOrFirstMipLevel = subresource.MipLevel, + NumMipLevels = 1, + FirstArraySlice = subresource.ArrayLayer, + NumArraySlices = 1, + NumPlanes = ZenithHelper.HasStencil(dxTexture.Desc.Format) ? 2u : 1u + } + }; - GraphicsCommandList4.CopyBufferRegion(dxDest.Resource, destOffsetInBytes, dxSrc.Resource, srcOffsetInBytes, sizeInBytes); + BarrierGroup barrierGroup = new() + { + Type = BarrierType.Texture, + NumBarriers = 1, + PTextureBarriers = &barrier + }; - dxSrc.TransitionStates(this, srcOldStates); - dxDest.TransitionStates(this, destOldStates); + CommandList.Barrier(1, &barrierGroup); } - protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { DXBuffer dxSrc = src.DirectX12(); - DXTexture dxDest = dest.DirectX12(); + DXBuffer dxDst = dst.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); + CommandList.CopyBufferRegion(dxDst.Resource, dstOffsetInBytes, dxSrc.Resource, srcOffsetInBytes, sizeInBytes); + } - 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); + 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() { @@ -88,375 +105,366 @@ protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInByte Offset = srcOffsetInBytes, Footprint = 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) + Format = DXFormats.DirectX12(dxDst.Desc.Format), + Width = dstExtent.Width, + Height = dstExtent.Height, + Depth = dstExtent.Depth, + RowPitch = srcRowStrideInBytes } } }; - Box srcBox = new(0, 0, 0, extentWidth, extentHeight, destExtent.Depth); - - TextureCopyLocation destLocation = new() + TextureCopyLocation dstLocation = new() { - PResource = dxDest.Resource, + PResource = dxDst.Resource, Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice) + SubresourceIndex = dxDst.SubresourceIndex(dstSubresource) }; - GraphicsCommandList4.CopyTextureRegion(&destLocation, offsetX, offsetY, destOffset.Z, &srcLocation, &srcBox); - - dxSrc.TransitionStates(this, srcOldStates); - dxDest.TransitionStates(this, destSlice, destOldStates); + CommandList.CopyTextureRegion(&dstLocation, dstOffset.X, dstOffset.Y, dstOffset.Z, &srcLocation, default(Box*)); } - 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 dst, TextureSubresource dstSubresource, Offset3D dstOffset, 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); + DXTexture dxDst = dst.DirectX12(); 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); + Box srcBox = new() + { + Left = srcOffset.X, + Top = srcOffset.Y, + Front = srcOffset.Z, + Right = srcOffset.X + extent.Width, + Bottom = srcOffset.Y + extent.Height, + Back = srcOffset.Z + extent.Depth + }; - TextureCopyLocation destLocation = new() + TextureCopyLocation dstLocation = new() { - PResource = dxDest.Resource, + PResource = dxDst.Resource, Type = TextureCopyType.SubresourceIndex, - SubresourceIndex = ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice) + SubresourceIndex = dxDst.SubresourceIndex(dstSubresource) }; - GraphicsCommandList4.CopyTextureRegion(&destLocation, destOffset.X, destOffset.Y, destOffset.Z, &srcLocation, &srcBox); - - dxSrc.TransitionStates(this, srcSlice, srcOldStates); - dxDest.TransitionStates(this, destSlice, destOldStates); + CommandList.CopyTextureRegion(&dstLocation, dstOffset.X, dstOffset.Y, dstOffset.Z, &srcLocation, &srcBox); } - 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 dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { 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); - uint offsetY = ZenithHelper.Align(srcOffset.Y, blockHeight); - uint extentWidth = ZenithHelper.Align(srcExtent.Width, blockWidth); - uint extentHeight = ZenithHelper.Align(srcExtent.Height, blockHeight); + DXBuffer dxDst = dst.DirectX12(); TextureCopyLocation srcLocation = new() { 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); + Box srcBox = new() + { + Left = srcOffset.X, + Top = srcOffset.Y, + Front = srcOffset.Z, + Right = srcOffset.X + srcExtent.Width, + Bottom = srcOffset.Y + srcExtent.Height, + Back = srcOffset.Z + srcExtent.Depth + }; - TextureCopyLocation destLocation = new() + TextureCopyLocation dstLocation = new() { - PResource = dxDest.Resource, + PResource = dxDst.Resource, Type = TextureCopyType.PlacedFootprint, PlacedFootprint = new() { - Offset = destOffsetInBytes, + Offset = dstOffsetInBytes, Footprint = new() { Format = DXFormats.DirectX12(dxSrc.Desc.Format), - Width = extentWidth, - Height = extentHeight, + Width = srcExtent.Width, + Height = srcExtent.Height, Depth = srcExtent.Depth, - RowPitch = ZenithHelper.Align(ZenithHelper.SizeInBytes(dxSrc.Desc.Format) * blocksWide, GraphicsContext.TextureRowPitchAlignment) + RowPitch = dstRowStrideInBytes } } }; - GraphicsCommandList4.CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &srcBox); - - dxSrc.TransitionStates(this, srcSlice, srcOldStates); - dxDest.TransitionStates(this, destOldStates); + CommandList.CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, &srcBox); } - protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { DXTexture dxSrc = src.DirectX12(); - DXTexture dxDest = dest.DirectX12(); + DXTexture dxDst = dst.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); - - GraphicsCommandList4.ResolveSubresource(dxDest.Resource, ZenithHelper.SubresourceIndex(dxDest.Desc, destSlice), dxSrc.Resource, ZenithHelper.SubresourceIndex(dxSrc.Desc, srcSlice), DXFormats.DirectX12(dxDest.Desc.Format)); - - dxSrc.TransitionStates(this, srcSlice, srcOldStates); - dxDest.TransitionStates(this, destSlice, destOldStates); + CommandList.ResolveSubresource(dxDst.Resource, dxDst.SubresourceIndex(dstSubresource), dxSrc.Resource, dxSrc.SubresourceIndex(srcSubresource), DXFormats.DirectX12(dxDst.Desc.Format)); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) { - return new DXBottomLevelAccelerationStructure(Context, desc, this); + return new DXBottomLevelAccelerationStructure(Context, this, desc); } protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) { - return new DXTopLevelAccelerationStructure(Context, desc, this); + return new DXTopLevelAccelerationStructure(Context, this, desc); } - protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) + protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) { accelerationStructure.DirectX12().Update(this, newDesc); } - protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue) + protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { - DXFrameBuffer dxFrameBuffer = frameBuffer.DirectX12(); - - dxFrameBuffer.PrepareAttachments(this); - - bool clearColor = clearValue.Flags.HasFlag(ClearFlags.Color); - bool clearDepth = clearValue.Flags.HasFlag(ClearFlags.Depth); - bool clearStencil = clearValue.Flags.HasFlag(ClearFlags.Stencil); + accelerationStructure.DirectX12().Update(this, newDesc); + } - for (int i = 0; i < dxFrameBuffer.ColorAttachmentCount; i++) + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + RenderPassRenderTargetDesc* pRenderTargets = stackalloc RenderPassRenderTargetDesc[colorAttachments.Length]; + for (int i = 0; i < colorAttachments.Length; i++) { - ref RenderPassRenderTargetDesc renderTarget = ref dxFrameBuffer.RenderTargets[i]; + ColorAttachment attachment = colorAttachments[i]; - renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Preserve; + DXTexture texture = attachment.Texture.DirectX12(); - if (clearColor) - { - renderTarget.BeginningAccess.Type = RenderPassBeginningAccessType.Clear; + ClearValue clearValue = new() { Format = DXFormats.DirectX12(texture.Desc.Format) }; + *(Vector4*)clearValue.Anonymous.Color = attachment.ClearColor; - fixed (float* colorPtr = renderTarget.BeginningAccess.Clear.ClearValue.Anonymous.Color) + pRenderTargets[i] = new() + { + CpuDescriptor = texture.GetRtvHandle(attachment.Subresource), + BeginningAccess = new() { - clearValue.ColorValues[i].CopyTo(new Span(colorPtr, 4)); - } - } + Type = DXFormats.DirectX12(attachment.LoadOp), + Clear = new() { ClearValue = clearValue } + }, + EndingAccess = new() { Type = DXFormats.DirectX12(attachment.StoreOp) } + }; } - if (dxFrameBuffer.HasDepthStencilAttachment) + RenderPassDepthStencilDesc* pDepthStencil = stackalloc RenderPassDepthStencilDesc[depthStencilAttachment.HasValue ? 1 : 0]; + if (depthStencilAttachment.HasValue) { - ref RenderPassDepthStencilDesc depthStencil = ref dxFrameBuffer.DepthStencil[0]; + DepthStencilAttachment attachment = depthStencilAttachment.Value; - if (depthStencil.DepthBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) - { - depthStencil.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Preserve; + DXTexture texture = attachment.Texture.DirectX12(); - if (clearDepth) + ClearValue clearValue = new() + { + Format = DXFormats.DirectX12(texture.Desc.Format), + DepthStencil = new() { - depthStencil.DepthBeginningAccess.Type = RenderPassBeginningAccessType.Clear; - depthStencil.DepthBeginningAccess.Clear.ClearValue.DepthStencil.Depth = clearValue.Depth; + Depth = attachment.ClearDepth, + Stencil = attachment.ClearStencil } - } + }; - if (depthStencil.StencilBeginningAccess.Type is not RenderPassBeginningAccessType.NoAccess) + pDepthStencil[0] = new() { - depthStencil.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Preserve; - - if (clearStencil) + CpuDescriptor = texture.GetDsvHandle(attachment.Subresource), + DepthBeginningAccess = new() { - depthStencil.StencilBeginningAccess.Type = RenderPassBeginningAccessType.Clear; - depthStencil.StencilBeginningAccess.Clear.ClearValue.DepthStencil.Stencil = clearValue.Stencil; - } - } + Type = DXFormats.DirectX12(attachment.DepthLoadOp), + Clear = new() { ClearValue = clearValue } + }, + DepthEndingAccess = new() { Type = ZenithHelper.HasDepth(texture.Desc.Format) ? DXFormats.DirectX12(attachment.DepthStoreOp) : RenderPassEndingAccessType.NoAccess }, + StencilBeginningAccess = new() + { + Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilLoadOp) : RenderPassBeginningAccessType.NoAccess, + Clear = new() { ClearValue = clearValue } + }, + StencilEndingAccess = new() { Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilStoreOp) : RenderPassEndingAccessType.NoAccess } + }; } - GraphicsCommandList4.BeginRenderPass(dxFrameBuffer.ColorAttachmentCount, dxFrameBuffer.RenderTargets, dxFrameBuffer.DepthStencil, RenderPassFlags.None); + CommandList.BeginRenderPass((uint)colorAttachments.Length, pRenderTargets, pDepthStencil, RenderPassFlags.None); } - protected override void EndRenderPassImpl(FrameBuffer frameBuffer) + protected override void EndRenderPassImpl() { - GraphicsCommandList4.EndRenderPass(); - - frameBuffer.DirectX12().PresentColorAttachments(this); + CommandList.EndRenderPass(); } - protected override void SetScissorsImpl(Scissor[] scissors) + protected override void SetPipelineImpl(GraphicsPipeline pipeline) { - Box2D[] dxScissors = [.. scissors.Select(static item => new Box2D(new(item.X, item.Y), new((int)(item.X + item.Width), (int)(item.Y + item.Height))))]; + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + CommandList.SetGraphicsRootSignature(Context.RootSignature); - GraphicsCommandList4.RSSetScissorRects((uint)dxScissors.Length, ref dxScissors[0]); + CommandList.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).Topology); } - protected override void SetViewportsImpl(Viewport[] viewports) + protected override void SetPipelineImpl(ComputePipeline pipeline) { - DxViewport[] dxViewports = [.. viewports.Select(static item => new DxViewport(item.X, item.Y, item.Width, item.Height, item.MinDepth, item.MaxDepth))]; + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + CommandList.SetComputeRootSignature(Context.RootSignature); + } - GraphicsCommandList4.RSSetViewports((uint)dxViewports.Length, ref dxViewports[0]); + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + { + CommandList.SetPipelineState(pipeline.DirectX12().PipelineState); + CommandList.SetGraphicsRootSignature(Context.RootSignature); } - protected override void SetPipelineImpl(GraphicsPipeline pipeline) + protected override void SetViewportsImpl(ReadOnlySpan viewports) { - DXGraphicsPipeline dxPipeline = pipeline.DirectX12(); + DxViewport* pViewports = stackalloc DxViewport[viewports.Length]; + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; - GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); - GraphicsCommandList4.SetGraphicsRootSignature(dxPipeline.RootSignature); + pViewports[i] = new() + { + TopLeftX = viewport.X, + TopLeftY = viewport.Y, + Width = viewport.Width, + Height = viewport.Height, + MinDepth = viewport.MinDepth, + MaxDepth = viewport.MaxDepth + }; + } - GraphicsCommandList4.OMSetStencilRef(dxPipeline.Desc.RenderStates.StencilReference); + CommandList.RSSetViewports((uint)viewports.Length, pViewports); + } - if (dxPipeline.Desc.RenderStates.BlendFactor.HasValue) + protected override void SetScissorsImpl(ReadOnlySpan scissors) + { + Box2D* pRects = stackalloc Box2D[scissors.Length]; + for (int i = 0; i < scissors.Length; i++) { - 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]); + Scissor scissor = scissors[i]; + + 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) + } + }; } - GraphicsCommandList4.IASetPrimitiveTopology(DXFormats.DirectX12(pipeline.Desc.PrimitiveTopology).PrimitiveTopology); + CommandList.RSSetScissorRects((uint)scissors.Length, pRects); } - protected override void SetPipelineImpl(ComputePipeline pipeline) + protected override void SetBlendConstantImpl(Vector4 blendConstant) { - DXComputePipeline dxPipeline = pipeline.DirectX12(); - - GraphicsCommandList4.SetPipelineState(dxPipeline.PipelineState); - GraphicsCommandList4.SetComputeRootSignature(dxPipeline.RootSignature); + CommandList.OMSetBlendFactor(&blendConstant.X); } - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + protected override void SetStencilReferenceImpl(uint stencilReference) { - DXMeshShadingPipeline dxPipeline = pipeline.DirectX12(); - - 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]); - } + CommandList.OMSetStencilRef(stencilReference); } - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) + 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[index].StrideInBytes + StrideInBytes = pipeline.Desc.InputLayouts[slot].StrideInBytes }; - GraphicsCommandList4.IASetVertexBuffers(index, 1, &view); + CommandList.IASetVertexBuffers(slot, 1, &view); } - protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format) + 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(format) + Format = DXFormats.DirectX12(indexFormat) }; - GraphicsCommandList4.IASetIndexBuffer(&view); + CommandList.IASetIndexBuffer(&view); } - protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) + protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) { - if (cbvSrvUavTable is null || samplerTable is null) + if (pipeline is ComputePipeline) { - return; + CommandList.SetComputeRootConstantBufferView(0, buffer.DirectX12().GPUVirtualAddress + offsetInBytes); + } + else + { + CommandList.SetGraphicsRootConstantBufferView(0, buffer.DirectX12().GPUVirtualAddress + offsetInBytes); } - - 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); + CommandList.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); + 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) { - GraphicsCommandList4.DrawIndexedInstanced(indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); + CommandList.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); + CommandList.ExecuteIndirect(Context.DrawIndexedSignature, drawCount, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void DispatchImpl(ComputePipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - GraphicsCommandList4.Dispatch(groupCountX, groupCountY, groupCountZ); + CommandList.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); + CommandList.ExecuteIndirect(Context.DispatchSignature, 1, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void DispatchMeshImpl(MeshShadingPipeline pipeline, uint groupCountX, uint groupCountY, uint groupCountZ) { - GraphicsCommandList6?.DispatchMesh(groupCountX, groupCountY, groupCountZ); + CommandList.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); + CommandList.ExecuteIndirect(Context.DispatchMeshSignature, dispatchCount, indirectBuffer.DirectX12().Resource, offsetInBytes, default(ID3D12Resource*), 0); } protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { - GraphicsCommandList4.BeginQuery(queryHeap.DirectX12().QueryHeap, DXFormats.DirectX12(queryHeap.Desc.Type).Type, 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(); - 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); + 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(); - GraphicsCommandList4.EndQuery(dxQueryHeap.QueryHeap, DxQueryType.Timestamp, index); - - GraphicsCommandList4.ResolveQueryData(dxQueryHeap.QueryHeap, DxQueryType.Timestamp, index, 1, dxQueryHeap.Buffer.Resource, sizeof(ulong) * index); + 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) @@ -469,12 +477,12 @@ protected override void BeginDebugEventImpl(string label) PixHelpers.FormatEventToBuffer(buffer, PixHelpers.Event, 0, label); - GraphicsCommandList4.BeginEvent(PixHelpers.Version, buffer, size); + CommandList.BeginEvent(PixHelpers.Version, buffer, size); } protected override void EndDebugEventImpl() { - GraphicsCommandList4.EndEvent(); + CommandList.EndEvent(); } protected override void InsertDebugMarkerImpl(string label) @@ -487,36 +495,30 @@ protected override void InsertDebugMarkerImpl(string label) PixHelpers.FormatEventToBuffer(buffer, PixHelpers.Marker, 0, label); - GraphicsCommandList4.SetMarker(PixHelpers.Version, buffer, size); + CommandList.SetMarker(PixHelpers.Version, buffer, size); } protected override void BeginImpl() { - if (cbvSrvUavTable is null || samplerTable is null) + if (Queue.Type is CommandQueueType.Transfer) { return; } - ComPtr[] descriptorHeaps = [cbvSrvUavTable.Heap, samplerTable.Heap]; + ID3D12DescriptorHeap** ppDescriptorHeaps = stackalloc ID3D12DescriptorHeap*[] { Context.CbvSrvUavHeap.Heap, Context.SamplerHeap.Heap }; - fixed (ID3D12DescriptorHeap** ppDescriptorHeaps = descriptorHeaps[0]) - { - GraphicsCommandList4.SetDescriptorHeaps((uint)descriptorHeaps.Length, ppDescriptorHeaps); - } + CommandList.SetDescriptorHeaps(2, ppDescriptorHeaps); } protected override void EndImpl() { - GraphicsCommandList4.Close().Success(); + CommandList.Close().Success(); } protected override void ResetImpl() { - cbvSrvUavTable?.Reset(); - samplerTable?.Reset(); - CommandAllocator.Reset().Success(); - GraphicsCommandList4.Reset(CommandAllocator, (ID3D12PipelineState*)null).Success(); + CommandList.Reset(CommandAllocator, default(ID3D12PipelineState*)).Success(); } protected override void SetResourceName(string name) @@ -528,12 +530,7 @@ protected override void Destroy() { base.Destroy(); - GraphicsCommandList6?.Dispose(); - GraphicsCommandList4.Dispose(); CommandList.Dispose(); CommandAllocator.Dispose(); - - samplerTable?.Dispose(); - cbvSrvUavTable?.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index a8029f29..4eef6297 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -3,34 +3,62 @@ namespace Zenith.NET.DirectX12; -internal unsafe class DXCommandQueue(DXGraphicsContext context, CommandQueueType type, ComPtr queue) : CommandQueue(context, type) +internal unsafe class DXCommandQueue : CommandQueue { - private readonly DXFence fence = new(context); + public ComPtr CommandQueue; + + 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(); + + 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 CommandBuffer CreateCommandBuffer() { - return new DXCommandBuffer(context, this); + return new DXCommandBuffer(Context, this); } - protected override void WaitIdleImpl() + protected override double GetTimestampPeriod(out uint validBits) { - fence.Wait(queue); + validBits = 64; + + ulong frequency = 0; + CommandQueue.GetTimestampFrequency(&frequency).Success(); + + return 1_000_000_000.0 / frequency; } - protected override void SubmitImpl(CommandBuffer commandBuffer) + protected override void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer) { - queue.ExecuteCommandLists(1, commandBuffer.DirectX12().CommandList.GetAddressOf()); + foreach (TimelineValue wait in waits) + { + CommandQueue.Wait(wait.Timeline.DirectX12().Fence, wait.Value).Success(); + } + + CommandQueue.ExecuteCommandLists(1, (ID3D12CommandList**)commandBuffer.DirectX12().CommandList.GetAddressOf()); } protected override void SetResourceName(string name) { - queue.SetName(name).Success(); + CommandQueue.SetName(name).Success(); } protected override void Destroy() { base.Destroy(); - fence.Dispose(); + CommandQueue.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs index 8d08337b..cf77c69c 100644 --- a/sources/Zenith.NET.DirectX12/DXComputePipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXComputePipeline.cs @@ -5,8 +5,6 @@ namespace Zenith.NET.DirectX12; internal unsafe class DXComputePipeline : ComputePipeline { - public ComPtr RootSignature; - public ComPtr PipelineState; public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) @@ -15,59 +13,16 @@ public DXComputePipeline(DXGraphicsContext context, ComputePipelineDesc desc) : ComputePipelineStateDesc computePipelineStateDesc = new() { - CS = desc.Compute.DirectX12().GetShaderBytecode(scope) + PRootSignature = context.RootSignature, + CS = desc.ComputeShader.DirectX12().GetShaderBytecode(scope) }; - // ResourceLayout - { - List parameters = []; - if (desc.ResourceLayout is not null && desc.ResourceLayout.DirectX12().DescriptorRanges(ShaderStageFlags.None, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) - { - 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, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + } - context.Device.CreateComputePipelineState(&computePipelineStateDesc, out PipelineState).Success(); + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -78,6 +33,5 @@ protected override void SetResourceName(string name) 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/DXDescriptorHeap.cs b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs new file mode 100644 index 00000000..0e674bb4 --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXDescriptorHeap.cs @@ -0,0 +1,60 @@ +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Zenith.NET.DirectX12; + +internal unsafe class DXDescriptorHeap : DisposableObject +{ + private readonly Lock @lock = new(); + private readonly Stack recycled = []; + + 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) + { + DescriptorHeapDesc desc = new() + { + Type = type, + NumDescriptors = numDescriptors, + Flags = shaderVisible ? DescriptorHeapFlags.ShaderVisible : DescriptorHeapFlags.None + }; + + context.Device.CreateDescriptorHeap(&desc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).Success(); + + CpuStartHandle = Heap.GetCPUDescriptorHandleForHeapStart(); + GpuStartHandle = shaderVisible ? Heap.GetGPUDescriptorHandleForHeapStart() : default; + IncrementSize = context.Device.GetDescriptorHandleIncrementSize(type); + } + + public DXDescriptorToken Allocate() + { + using Lock.Scope _ = @lock.EnterScope(); + + if (!recycled.TryPop(out uint slot)) + { + slot = head++; + } + + return new(this, slot); + } + + public void Free(DXDescriptorToken token) + { + using Lock.Scope _ = @lock.EnterScope(); + + recycled.Push(token.Slot); + } + + protected override void Destroy() + { + Heap.Dispose(); + } +} 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 index 75dad3a5..ee80e461 100644 --- a/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs +++ b/sources/Zenith.NET.DirectX12/DXDescriptorToken.cs @@ -2,23 +2,18 @@ namespace Zenith.NET.DirectX12; -internal record struct DXDescriptorToken : IDisposable +internal readonly struct DXDescriptorToken(DXDescriptorHeap heap, uint slot) : IDisposable { - public DXDescriptorPool Pool; + public readonly uint Slot = slot; - public CpuDescriptorHandle Handle; + public readonly ResourceHandle ResourceHandle = new(slot, 0); - public uint Length; + public readonly CpuDescriptorHandle CpuHandle = new() { Ptr = heap.CpuStartHandle.Ptr + (slot * heap.IncrementSize) }; - public readonly CpuDescriptorHandle this[uint index] => index >= Length ? default : new(Handle.Ptr + (Pool.DescriptorSize * index)); + public readonly GpuDescriptorHandle GpuHandle = new() { Ptr = heap.GpuStartHandle.Ptr + (slot * heap.IncrementSize) }; - public readonly void Dispose() + public void Dispose() { - if (Length is 0) - { - return; - } - - Pool.Free(Handle, Length); + heap.Free(this); } -} +} \ No newline at end of file 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 index eba4d384..762a56d3 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -6,342 +6,208 @@ namespace Zenith.NET.DirectX12; -internal static unsafe class DXFormats +internal static class DXFormats { - public static (ResourceFlags Flags, ResourceStates States, HeapType Type) DirectX12(BufferUsageFlags bufferUsageFlags) + public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - 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; + RaytracingAccelerationStructureBuildFlags result = default; - 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)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - states |= ResourceStates.RaytracingAccelerationStructure; + result |= RaytracingAccelerationStructureBuildFlags.AllowUpdate; } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.ShaderResource)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) { - states |= ResourceStates.AllShaderResource; + result |= RaytracingAccelerationStructureBuildFlags.AllowCompaction; } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.UnorderedAccess)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) { - states |= ResourceStates.UnorderedAccess; + result |= RaytracingAccelerationStructureBuildFlags.PreferFastTrace; } - HeapType type = HeapType.Default; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) { - states = ResourceStates.CopyDest; - - type = HeapType.Readback; + result |= RaytracingAccelerationStructureBuildFlags.PreferFastBuild; } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) { - states = ResourceStates.GenericRead; - - type = HeapType.Upload; + result |= RaytracingAccelerationStructureBuildFlags.MinimizeMemory; } - return (flags, states, type); + return result; } - public static ResourceDimension DirectX12(TextureType textureType) + public static TextureAddressMode DirectX12(AddressMode addressMode) { - return textureType switch + return addressMode 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 + AddressMode.Wrap => TextureAddressMode.Wrap, + AddressMode.Mirror => TextureAddressMode.Mirror, + AddressMode.Clamp => TextureAddressMode.Clamp, + AddressMode.Border => TextureAddressMode.Border, + _ => default }; } - public static Format DirectX12(PixelFormat pixelFormat) + public static (BarrierSync Sync, BarrierAccess Access) DirectX12(BarrierStages barrierStages) { - return pixelFormat switch + if (barrierStages is BarrierStages.None) { - 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, + return (BarrierSync.None, BarrierAccess.NoAccess); + } - PixelFormat.BC7UNorm => Format.FormatBC7Unorm, - PixelFormat.BC7SRgb => Format.FormatBC7UnormSrgb, + BarrierSync sync = default; + BarrierAccess access = default; - _ => Format.FormatUnknown - }; - } - - public static SampleDesc DirectX12(SampleCount sampleCount) - { - return sampleCount switch + if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - 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; + sync |= BarrierSync.IndexInput | BarrierSync.VertexShading | BarrierSync.ExecuteIndirect; + access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer | BarrierAccess.IndexBuffer | BarrierAccess.UnorderedAccess | BarrierAccess.ShaderResource | BarrierAccess.IndirectArgument | BarrierAccess.RaytracingAccelerationStructureRead; + } - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget)) + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) { - flags |= ResourceFlags.AllowRenderTarget; + sync |= BarrierSync.PixelShading | BarrierSync.DepthStencil | BarrierSync.RenderTarget; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.RenderTarget | BarrierAccess.UnorderedAccess | BarrierAccess.DepthStencilWrite | BarrierAccess.DepthStencilRead | BarrierAccess.ShaderResource | BarrierAccess.RaytracingAccelerationStructureRead; } - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) { - flags |= ResourceFlags.AllowDepthStencil; + sync |= BarrierSync.ComputeShading | BarrierSync.ExecuteIndirect; + access |= BarrierAccess.ConstantBuffer | BarrierAccess.UnorderedAccess | BarrierAccess.ShaderResource | BarrierAccess.IndirectArgument | BarrierAccess.RaytracingAccelerationStructureRead; } - if (textureUsageFlags.HasFlag(TextureUsageFlags.UnorderedAccess)) + if (barrierStages.HasFlag(BarrierStages.Copy)) { - flags |= ResourceFlags.AllowUnorderedAccess; + sync |= BarrierSync.Copy; + access |= BarrierAccess.CopyDest | BarrierAccess.CopySource; } - ResourceStates states = ResourceStates.Common; - - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget)) + if (barrierStages.HasFlag(BarrierStages.Resolve)) { - states |= ResourceStates.RenderTarget; + sync |= BarrierSync.Resolve; + access |= BarrierAccess.ResolveDest | BarrierAccess.ResolveSource; } - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) + if (barrierStages.HasFlag(BarrierStages.All)) { - states |= ResourceStates.DepthWrite; + sync = BarrierSync.All; + access = BarrierAccess.Common; } - return (flags, states); + return (sync, access); } - public static (DxFilter Filter, DxComparisonFunc ComparisonFunc) DirectX12(Filter filter, ComparisonFunc comparisonFunc) + public static Blend DirectX12(BlendFactor blendFactor) { - 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 - } - ); + 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 TextureAddressMode DirectX12(AddressMode addressMode) + public static DxBlendOp DirectX12(BlendOp blendOp) { - return addressMode switch + return blendOp switch { - AddressMode.Wrap => TextureAddressMode.Wrap, - AddressMode.Mirror => TextureAddressMode.Mirror, - AddressMode.Clamp => TextureAddressMode.Clamp, - AddressMode.Border => TextureAddressMode.Border, - _ => TextureAddressMode.Wrap + BlendOp.Add => DxBlendOp.Add, + BlendOp.Subtract => DxBlendOp.Subtract, + BlendOp.ReverseSubtract => DxBlendOp.RevSubtract, + BlendOp.Min => DxBlendOp.Min, + BlendOp.Max => DxBlendOp.Max, + _ => default }; } - public static DescriptorRangeType DirectX12(ResourceType resourceType) + public static (float R, float G, float B, float A) DirectX12(BorderColor borderColor) { - return resourceType switch + return borderColor 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 + 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 PrimitiveTopologyType, D3DPrimitiveTopology PrimitiveTopology) DirectX12(PrimitiveTopology primitiveTopology) + public static ResourceFlags DirectX12(BufferUsages bufferUsages) { - return - ( - primitiveTopology switch - { - PrimitiveTopology.PointList => PrimitiveTopologyType.Point, - - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => PrimitiveTopologyType.Line, + ResourceFlags result = default; - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => PrimitiveTopologyType.Triangle, + if (bufferUsages.HasFlag(BufferUsages.StorageReadWrite)) + { + result |= ResourceFlags.AllowUnorderedAccess; + } - _ => 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 - } - ); + return result; } - public static ShaderVisibility DirectX12(ShaderStageFlags shaderStageFlags) + public static ColorWriteEnable DirectX12(ColorWrites colorWrites) { - if (shaderStageFlags.HasFlag(ShaderStageFlags.Vertex)) + ColorWriteEnable result = default; + + if (colorWrites.HasFlag(ColorWrites.Red)) { - return ShaderVisibility.Vertex; + result |= ColorWriteEnable.Red; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Pixel)) + if (colorWrites.HasFlag(ColorWrites.Green)) { - return ShaderVisibility.Pixel; + result |= ColorWriteEnable.Green; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Amplification)) + if (colorWrites.HasFlag(ColorWrites.Blue)) { - return ShaderVisibility.Amplification; + result |= ColorWriteEnable.Blue; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Mesh)) + if (colorWrites.HasFlag(ColorWrites.Alpha)) { - return ShaderVisibility.Mesh; + result |= ColorWriteEnable.Alpha; } - return ShaderVisibility.All; + return result; } - public static DxFillMode DirectX12(FillMode fillMode) + public static CommandListType DirectX12(CommandQueueType commandQueueType) { - return fillMode switch + return commandQueueType switch { - FillMode.Solid => DxFillMode.Solid, - FillMode.Wireframe => DxFillMode.Wireframe, - _ => DxFillMode.None + CommandQueueType.Graphics => CommandListType.Direct, + CommandQueueType.Compute => CommandListType.Compute, + CommandQueueType.Transfer => CommandListType.Copy, + _ => 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 }; } @@ -352,86 +218,10 @@ public static DxCullMode DirectX12(CullMode cullMode) 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 + _ => default }; } - public static ColorWriteEnable DirectX12(ColorComponentFlags colorComponentFlags) - { - ColorWriteEnable result = ColorWriteEnable.None; - - if (colorComponentFlags.HasFlag(ColorComponentFlags.Red)) - { - result |= ColorWriteEnable.Red; - } - - if (colorComponentFlags.HasFlag(ColorComponentFlags.Green)) - { - result |= ColorWriteEnable.Green; - } - - if (colorComponentFlags.HasFlag(ColorComponentFlags.Blue)) - { - result |= ColorWriteEnable.Blue; - } - - if (colorComponentFlags.HasFlag(ColorComponentFlags.Alpha)) - { - result |= ColorWriteEnable.Alpha; - } - - return result; - } - public static Format DirectX12(ElementFormat elementFormat) { return elementFormat switch @@ -444,13 +234,13 @@ public static Format DirectX12(ElementFormat elementFormat) ElementFormat.Byte2 => Format.FormatR8G8Sint, ElementFormat.Byte4 => Format.FormatR8G8B8A8Sint, - ElementFormat.UByte1Normalized => Format.FormatR8Unorm, - ElementFormat.UByte2Normalized => Format.FormatR8G8Unorm, - ElementFormat.UByte4Normalized => Format.FormatR8G8B8A8Unorm, + ElementFormat.UByte1UNorm => Format.FormatR8Unorm, + ElementFormat.UByte2UNorm => Format.FormatR8G8Unorm, + ElementFormat.UByte4UNorm => Format.FormatR8G8B8A8Unorm, - ElementFormat.Byte1Normalized => Format.FormatR8SNorm, - ElementFormat.Byte2Normalized => Format.FormatR8G8SNorm, - ElementFormat.Byte4Normalized => Format.FormatR8G8B8A8SNorm, + ElementFormat.Byte1SNorm => Format.FormatR8SNorm, + ElementFormat.Byte2SNorm => Format.FormatR8G8SNorm, + ElementFormat.Byte4SNorm => Format.FormatR8G8B8A8SNorm, ElementFormat.UShort1 => Format.FormatR16Uint, ElementFormat.UShort2 => Format.FormatR16G16Uint, @@ -460,13 +250,13 @@ public static Format DirectX12(ElementFormat elementFormat) ElementFormat.Short2 => Format.FormatR16G16Sint, ElementFormat.Short4 => Format.FormatR16G16B16A16Sint, - ElementFormat.UShort1Normalized => Format.FormatR16Unorm, - ElementFormat.UShort2Normalized => Format.FormatR16G16Unorm, - ElementFormat.UShort4Normalized => Format.FormatR16G16B16A16Unorm, + ElementFormat.UShort1UNorm => Format.FormatR16Unorm, + ElementFormat.UShort2UNorm => Format.FormatR16G16Unorm, + ElementFormat.UShort4UNorm => Format.FormatR16G16B16A16Unorm, - ElementFormat.Short1Normalized => Format.FormatR16SNorm, - ElementFormat.Short2Normalized => Format.FormatR16G16SNorm, - ElementFormat.Short4Normalized => Format.FormatR16G16B16A16SNorm, + ElementFormat.Short1SNorm => Format.FormatR16SNorm, + ElementFormat.Short2SNorm => Format.FormatR16G16SNorm, + ElementFormat.Short4SNorm => Format.FormatR16G16B16A16SNorm, ElementFormat.Half1 => Format.FormatR16Float, ElementFormat.Half2 => Format.FormatR16G16Float, @@ -487,18 +277,48 @@ public static Format DirectX12(ElementFormat elementFormat) ElementFormat.Int3 => Format.FormatR32G32B32Sint, ElementFormat.Int4 => Format.FormatR32G32B32A32Sint, - _ => Format.FormatUnknown + _ => default }; } - public static CommandListType DirectX12(CommandQueueType commandQueueType) + public static DxFillMode DirectX12(FillMode fillMode) { - return commandQueueType switch + return fillMode switch { - CommandQueueType.Graphics => CommandListType.Direct, - CommandQueueType.Compute => CommandListType.Compute, - CommandQueueType.Copy => CommandListType.Copy, - _ => CommandListType.None + FillMode.Solid => DxFillMode.Solid, + FillMode.Wireframe => DxFillMode.Wireframe, + _ => 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 }; } @@ -508,138 +328,314 @@ public static Format DirectX12(IndexFormat indexFormat) { IndexFormat.UInt16 => Format.FormatR16Uint, IndexFormat.UInt32 => Format.FormatR32Uint, - _ => Format.FormatUnknown + _ => 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 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, + + _ => default }; } + public static (PrimitiveTopologyType TopologyType, D3DPrimitiveTopology Topology) 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 + }, + 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) { return ( queryType switch { - QueryType.Occlusion or - QueryType.BinaryOcclusion => QueryHeapType.Occlusion, - + QueryType.Occlusion or QueryType.BinaryOcclusion => QueryHeapType.Occlusion, QueryType.Timestamp => QueryHeapType.Timestamp, - - _ => QueryHeapType.Occlusion + _ => default }, queryType switch { QueryType.Occlusion => DxQueryType.Occlusion, QueryType.BinaryOcclusion => DxQueryType.BinaryOcclusion, QueryType.Timestamp => DxQueryType.Timestamp, - _ => DxQueryType.Occlusion + _ => default } ); } - 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 + RayTracingGeometryType.Triangle => RaytracingGeometryType.Triangles, + RayTracingGeometryType.Aabb => RaytracingGeometryType.ProceduralPrimitiveAabbs, + _ => default }; } - public static RaytracingGeometryFlags DirectX12(RayTracingGeometryFlags rayTracingGeometryFlags) + public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) { - RaytracingGeometryFlags result = RaytracingGeometryFlags.None; + RaytracingInstanceFlags result = default; - if (rayTracingGeometryFlags.HasFlag(RayTracingGeometryFlags.Opaque)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { - result |= RaytracingGeometryFlags.Opaque; + result |= RaytracingInstanceFlags.TriangleFrontCounterclockwise; } - return result; - } - - public static RaytracingAccelerationStructureBuildFlags DirectX12(AccelerationStructureBuildFlags accelerationStructureBuildFlags) - { - RaytracingAccelerationStructureBuildFlags result = RaytracingAccelerationStructureBuildFlags.None; - - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) { - result |= RaytracingAccelerationStructureBuildFlags.AllowUpdate; + result |= RaytracingInstanceFlags.TriangleCullDisable; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) { - result |= RaytracingAccelerationStructureBuildFlags.AllowCompaction; + result |= RaytracingInstanceFlags.ForceOpaque; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) { - result |= RaytracingAccelerationStructureBuildFlags.PreferFastTrace; + result |= RaytracingInstanceFlags.ForceNonOpaque; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + return result; + } + + public static SampleDesc DirectX12(SampleCount sampleCount) + { + return sampleCount switch { - result |= RaytracingAccelerationStructureBuildFlags.PreferFastBuild; - } + 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 + }; + } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + public static DxStencilOp DirectX12(StencilOp stencilOp) + { + return stencilOp switch { - result |= RaytracingAccelerationStructureBuildFlags.MinimizeMemory; - } + 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 + }; + } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PerformUpdate)) + public static RenderPassEndingAccessType DirectX12(StoreOp storeOp) + { + return storeOp switch { - result |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; - } + StoreOp.Store => RenderPassEndingAccessType.Preserve, + StoreOp.DontCare => RenderPassEndingAccessType.Discard, + _ => default + }; + } - return result; + public static (BarrierSync Sync, BarrierAccess Access, BarrierLayout Layout) DirectX12(TextureLayout textureLayout) + { + return textureLayout switch + { + TextureLayout.Undefined => (BarrierSync.None, BarrierAccess.NoAccess, BarrierLayout.Undefined), + TextureLayout.Common => (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.Common), + TextureLayout.CopyDst => (BarrierSync.Copy, BarrierAccess.CopyDest, BarrierLayout.Common), + TextureLayout.ResolveSrc => (BarrierSync.Resolve, BarrierAccess.ResolveSource, BarrierLayout.ResolveSource), + TextureLayout.ResolveDst => (BarrierSync.Resolve, BarrierAccess.ResolveDest, BarrierLayout.ResolveDest), + TextureLayout.Present => (BarrierSync.All, BarrierAccess.Common, BarrierLayout.Present), + _ => (default, default, default) + }; } - public static RaytracingInstanceFlags DirectX12(RayTracingInstanceFlags rayTracingInstanceFlags) + 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 ResourceFlags DirectX12(TextureUsages textureUsages) { - RaytracingInstanceFlags result = RaytracingInstanceFlags.None; + ResourceFlags result = default; - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleCullDisable)) + if (!textureUsages.HasFlag(TextureUsages.Sampled)) { - result |= RaytracingInstanceFlags.TriangleCullDisable; + result |= ResourceFlags.DenyShaderResource; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleFrontCounterClockwise)) + if (textureUsages.HasFlag(TextureUsages.Storage)) { - result |= RaytracingInstanceFlags.TriangleFrontCounterclockwise; + result |= ResourceFlags.AllowUnorderedAccess; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) { - result |= RaytracingInstanceFlags.ForceOpaque; + result |= ResourceFlags.AllowRenderTarget; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNoOpaque)) + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { - result |= RaytracingInstanceFlags.ForceNonOpaque; + result |= ResourceFlags.AllowDepthStencil; } return result; 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 9a5ab3a3..8b6d4cd2 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -4,29 +4,19 @@ namespace Zenith.NET.DirectX12; -internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(Backend.DirectX12, useValidationLayer) +internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.DirectX12, useValidationLayer) { - public const uint SwapChainBufferCount = 3; + public const ulong DefaultHeapAlignment = 4194304; public const uint Shader4ComponentMapping = 0x1688; - public ComPtr Factory7; + public ComPtr Factory; - public ComPtr Adapter4; + public ComPtr Adapter; - public ComPtr Device; + public ComPtr Device; - public ComPtr? Device2; - - public ComPtr? Device5; - - public ComPtr? InfoQueue1; - - public ComPtr GraphicsQueue; - - public ComPtr ComputeQueue; - - public ComPtr CopyQueue; + public ComPtr RootSignature; public ComPtr DrawSignature; @@ -40,78 +30,92 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public D3D12 D3D12 { get; } = D3D12.GetApi(); - public DXDescriptorAllocator RtvAllocator => field ??= new(this, DescriptorHeapType.Rtv); + public DXDescriptorHeap RtvHeap => field ??= new(this, DescriptorHeapType.Rtv, 1024, false); - public DXDescriptorAllocator DsvAllocator => field ??= new(this, DescriptorHeapType.Dsv); + public DXDescriptorHeap DsvHeap => field ??= new(this, DescriptorHeapType.Dsv, 256, false); - public DXDescriptorAllocator CbvSrvUavAllocator => field ??= new(this, DescriptorHeapType.CbvSrvUav); + public DXDescriptorHeap CbvSrvUavHeap => field ??= new(this, DescriptorHeapType.CbvSrvUav, 1000000, true); - public DXDescriptorAllocator SamplerAllocator => field ??= new(this, DescriptorHeapType.Sampler); + public DXDescriptorHeap SamplerHeap => field ??= new(this, DescriptorHeapType.Sampler, 2048, true); + + public override nint GetNativeObject(NativeObjectType type) + { + 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 transferQueue, 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(useValidationLayer ? DXGI.CreateFactoryDebug : 0u, out Factory7).Success(); + DXGI.CreateDXGIFactory2(Convert.ToUInt32(useValidationLayer), SilkMarshal.GuidPtrOf(), (void**)Factory.GetAddressOf()).Success(); - Factory7.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, out Adapter4).Success(); + Factory.EnumAdapterByGpuPreference(0, GpuPreference.HighPerformance, SilkMarshal.GuidPtrOf(), (void**)Adapter.GetAddressOf()).Success(); - D3D12.CreateDevice(Adapter4, D3DFeatureLevel.Level120, out Device).Success(); - - if (Device.QueryInterface(out ComPtr device2).IsSuccess()) + if (!D3D12.CreateDevice((IUnknown*)Adapter.Handle, D3DFeatureLevel.Level120, SilkMarshal.GuidPtrOf(), (void**)Device.GetAddressOf()).IsSuccess()) { - Device2 = device2; + throw new NotSupportedException("This device does not support DirectX 12.0 or higher."); } - if (Device.QueryInterface(out ComPtr device5).IsSuccess()) + RootParameter1 rootParameter = new() { - Device5 = device5; - } + ParameterType = RootParameterType.TypeCbv, + ShaderVisibility = ShaderVisibility.All, + Descriptor = new() { Flags = RootDescriptorFlags.DataVolatile } + }; - if (Device.QueryInterface(out ComPtr infoQueue1).IsSuccess()) + RootSignatureDesc2 rootSignatureDesc = new() { - InfoQueue1 = infoQueue1; - } + NumParameters = 1, + PParameters = &rootParameter, + Flags = RootSignatureFlags.AllowInputAssemblerInputLayout | RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed | RootSignatureFlags.SamplerHeapDirectlyIndexed + }; - CommandQueueDesc commandQueueDesc = new() { Type = CommandListType.Direct }; - Device.CreateCommandQueue(&commandQueueDesc, out GraphicsQueue).Success(); - - commandQueueDesc.Type = CommandListType.Compute; - Device.CreateCommandQueue(&commandQueueDesc, out ComputeQueue).Success(); + VersionedRootSignatureDesc versionedRootSignatureDesc = new() + { + Version = D3DRootSignatureVersion.Version12, + Desc12 = rootSignatureDesc + }; - commandQueueDesc.Type = CommandListType.Copy; - Device.CreateCommandQueue(&commandQueueDesc, out CopyQueue).Success(); + using ComPtr rootSignatureBlob = new(); + using ComPtr rootSignatureError = new(); + 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 }; - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawSignature).Success(); + + 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; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDrawIndexedArgs); - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DrawIndexedSignature).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DrawIndexedSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.Dispatch; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchArgs); - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchSignature).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchSignature.GetAddressOf()).Success(); indirectArgumentDesc.Type = IndirectArgumentType.DispatchMesh; commandSignatureDesc.ByteStride = (uint)sizeof(IndirectDispatchMeshArgs); - Device.CreateCommandSignature(&commandSignatureDesc, (ComPtr)null, out DispatchMeshSignature).Success(); + Device.CreateCommandSignature(&commandSignatureDesc, default(ID3D12RootSignature*), SilkMarshal.GuidPtrOf(), (void**)DispatchMeshSignature.GetAddressOf()).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); + graphicsQueue = new DXCommandQueue(this, CommandQueueType.Graphics); + computeQueue = new DXCommandQueue(this, CommandQueueType.Compute); + transferQueue = new DXCommandQueue(this, CommandQueueType.Transfer); validationLayer = useValidationLayer ? new DXValidationLayer(this) : null; } @@ -120,14 +124,27 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) return new DXSwapChain(this, desc); } - protected override FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc) + protected override Heap CreateHeapImpl(HeapDesc desc) { - return new DXFrameBuffer(this, desc); + return new DXHeap(this, desc); } - protected override Shader CreateShaderImpl(ShaderDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - return new DXShader(this, desc); + ResourceDesc1 resourceDesc = DXBuffer.ResourceDesc(desc); + + ResourceAllocationInfo info = Device.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); + + return new(info.SizeInBytes, info.Alignment); + } + + protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) + { + ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); + + ResourceAllocationInfo info = Device.GetResourceAllocationInfo2(0, 1, &resourceDesc, default(ResourceAllocationInfo1*)); + + return new(info.SizeInBytes, info.Alignment); } protected override Buffer CreateBufferImpl(BufferDesc desc) @@ -145,6 +162,14 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new DXTexture(this, desc); } + protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) + { + ComPtr resource = new(); + Device.OpenSharedHandle((void*)nativeTexture, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); + + return new DXTexture(this, desc, resource); + } + protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) { return new DXTextureView(this, desc); @@ -155,14 +180,9 @@ 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) + protected override Shader CreateShaderImpl(ShaderDesc desc) { - return new DXResourceTable(this, desc); + return new DXShader(this, desc); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) @@ -189,27 +209,21 @@ protected override void Destroy() { base.Destroy(); - SamplerAllocator.Dispose(); - CbvSrvUavAllocator.Dispose(); - DsvAllocator.Dispose(); - RtvAllocator.Dispose(); + SamplerHeap.Dispose(); + CbvSrvUavHeap.Dispose(); + DsvHeap.Dispose(); + RtvHeap.Dispose(); DispatchMeshSignature.Dispose(); DispatchSignature.Dispose(); DrawIndexedSignature.Dispose(); DrawSignature.Dispose(); - CopyQueue.Dispose(); - ComputeQueue.Dispose(); - GraphicsQueue.Dispose(); - - InfoQueue1?.Dispose(); - Device5?.Dispose(); - Device2?.Dispose(); + RootSignature.Dispose(); Device.Dispose(); - Adapter4.Dispose(); - Factory7.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 342ac305..9e6ccd1e 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsPipeline.cs @@ -1,198 +1,153 @@ 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() + PipelineStateStream2 pipelineStateStream = new() { - SampleMask = uint.MaxValue, - VS = desc.Vertex.DirectX12().GetShaderBytecode(scope), - PS = desc.Pixel.DirectX12().GetShaderBytecode(scope), - PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).PrimitiveTopologyType + PRootSignature = (nint)context.RootSignature.Handle, + PrimitiveTopologyType = DXFormats.DirectX12(desc.PrimitiveTopology).TopologyType, + VS = desc.VertexShader.DirectX12().GetShaderBytecode(scope), + PS = desc.FragmentShader.DirectX12().GetShaderBytecode(scope), + SampleMask = uint.MaxValue }; - // RenderStates - Output + // InputLayouts { - 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() + List inputElementDescs = []; + for (int i = 0; i < desc.InputLayouts.Length; i++) { - 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 - }; + InputLayout inputLayout = desc.InputLayouts[i]; - 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() + foreach (InputElement element in inputLayout.Elements) { - 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 + 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.BlendState = new() + pipelineStateStream.InputLayout = new() { - AlphaToCoverageEnable = desc.RenderStates.BlendState.AlphaToCoverageEnable, - IndependentBlendEnable = desc.RenderStates.BlendState.IndependentBlendEnable + PInputElementDescs = (InputElementDesc*)ZenithMarshal.AllocateAndFill(scope, [.. inputElementDescs]), + NumElements = (uint)inputElementDescs.Count }; + } - 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) - }; - } + // AttachmentFormats + { + pipelineStateStream.DSVFormat = DXFormats.DirectX12(desc.AttachmentFormats.DepthStencilFormat ?? PixelFormat.Unknown); - graphicsPipelineStateDesc.NumRenderTargets = (uint)desc.Output.ColorAttachments.Length; + pipelineStateStream.RTVFormats.NumRenderTargets = (uint)desc.AttachmentFormats.ColorFormats.Length; - for (int i = 0; i < desc.Output.ColorAttachments.Length; i++) + for (int i = 0; i < desc.AttachmentFormats.ColorFormats.Length; i++) { - graphicsPipelineStateDesc.RTVFormats[i] = DXFormats.DirectX12(desc.Output.ColorAttachments[i]); + pipelineStateStream.RTVFormats.RTFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); } - graphicsPipelineStateDesc.DSVFormat = desc.Output.DepthStencilAttachment.HasValue ? DXFormats.DirectX12(desc.Output.DepthStencilAttachment.Value) : Format.FormatUnknown; - - graphicsPipelineStateDesc.SampleDesc = DXFormats.DirectX12(desc.Output.SampleCount); + pipelineStateStream.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); } - // ResourceLayout + // RenderState { - List parameters = []; - if (desc.ResourceLayout is not null) + 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 + ]; + + pipelineStateStream.RasterizerState = new() { - DXResourceLayout resourceLayout = desc.ResourceLayout.DirectX12(); + 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 + }; - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) + pipelineStateStream.DepthStencilState = new() + { + 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() { - if (resourceLayout.DescriptorRanges(stage, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) - { - if (cbvSrvUavRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } - - if (samplerRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)samplerRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, samplerRanges) - } - }); - } - } + 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.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) } - } + }; - RootSignatureDesc rootSignatureDesc = new() + pipelineStateStream.BlendState = new() { - NumParameters = (uint)parameters.Count, - PParameters = (RootParameter*)ZenithMarshal.AllocateAndFill(scope, [.. parameters]), - Flags = RootSignatureFlags.AllowInputAssemblerInputLayout + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled, + IndependentBlendEnable = desc.RenderState.Blend.IsIndependentBlendEnabled }; - 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 = []; - for (int i = 0; i < desc.InputLayouts.Length; i++) + for (int i = 0; i < states.Length; i++) { - InputLayout inputLayout = desc.InputLayouts[i]; + ColorAttachmentBlendState blend = states[i]; - foreach (InputElement element in inputLayout.Elements) + pipelineStateStream.BlendState.RenderTarget[i] = new() { - 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 - }); - } + 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) + }; } - - graphicsPipelineStateDesc.InputLayout = new() - { - PInputElementDescs = (InputElementDesc*)ZenithMarshal.AllocateAndFill(scope, [.. inputElementDescs]), - NumElements = (uint)inputElementDescs.Count - }; } - context.Device.CreateGraphicsPipelineState(&graphicsPipelineStateDesc, out PipelineState).Success(); + PipelineStateStreamDesc pipelineStateStreamDesc = new() + { + SizeInBytes = (uint)sizeof(PipelineStateStream2), + PPipelineStateSubobjectStream = &pipelineStateStream + }; + + context.Device.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -203,6 +158,5 @@ protected override void SetResourceName(string name) protected override void Destroy() { PipelineState.Dispose(); - RootSignature.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index 9c68ea23..eeb420dc 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -1,29 +1,71 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; -internal unsafe class DXHeap : GraphicsResource +internal unsafe class DXHeap : Heap { public ComPtr Heap; - public DXHeap(DXGraphicsContext context, ResourceDesc resourceDesc, HeapType type, HeapFlags flags) : base(context) + public DXHeap(DXGraphicsContext context, HeapDesc desc) : base(context, desc) { - ResourceAllocationInfo allocationInfo = context.Device.GetResourceAllocationInfo(0, 1, ref resourceDesc); - - HeapDesc desc = new() + DxHeapDesc heapDesc = new() { - SizeInBytes = allocationInfo.SizeInBytes, - Properties = new(type), - Alignment = allocationInfo.Alignment, - Flags = flags + SizeInBytes = ZenithHelper.Align(desc.SizeInBytes, DXGraphicsContext.DefaultHeapAlignment), + Properties = new() { Type = DXFormats.DirectX12(desc.Residency) }, + Alignment = DXGraphicsContext.DefaultHeapAlignment }; - context.Device.CreateHeap(&desc, out Heap).Success(); + context.Device.CreateHeap(&heapDesc, SilkMarshal.GuidPtrOf(), (void**)Heap.GetAddressOf()).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); + + ComPtr resource = new(); + 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); + } + + protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc desc) + { + ResourceDesc1 resourceDesc = DXTexture.ResourceDesc(desc); + + ComPtr resource = new(); + 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); } protected override void SetResourceName(string name) { + Heap.SetName(name).Success(); } protected override void Destroy() diff --git a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs index 2bf0201e..4b0329aa 100644 --- a/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs +++ b/sources/Zenith.NET.DirectX12/DXMeshShadingPipeline.cs @@ -1,192 +1,127 @@ 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() + PipelineStateStream2 pipelineStateStream = new() { + PRootSignature = (nint)context.RootSignature.Handle, + 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), SampleMask = uint.MaxValue }; - // RenderStates - Output + // AttachmentFormats { - BlendStateRenderTarget[] blendStateRenderTargets = + 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++) + { + pipelineStateStream.RTVFormats.RTFormats[i] = DXFormats.DirectX12(desc.AttachmentFormats.ColorFormats[i]); + } + + pipelineStateStream.SampleDesc = DXFormats.DirectX12(desc.AttachmentFormats.SampleCount); + } + + // RenderState + { + ColorAttachmentBlendState[] states = [ - 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 + 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 ]; - graphicsPipelineStateDesc.RasterizerState = new() + pipelineStateStream.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 + 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 }; - graphicsPipelineStateDesc.DepthStencilState = new() + pipelineStateStream.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, + 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.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 + 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.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 + 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) } }; - graphicsPipelineStateDesc.BlendState = new() + pipelineStateStream.BlendState = new() { - AlphaToCoverageEnable = desc.RenderStates.BlendState.AlphaToCoverageEnable, - IndependentBlendEnable = desc.RenderStates.BlendState.IndependentBlendEnable + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled, + IndependentBlendEnable = desc.RenderState.Blend.IsIndependentBlendEnabled }; - 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); - } - - // ResourceLayout - { - List parameters = []; - if (desc.ResourceLayout is not null) + for (int i = 0; i < states.Length; i++) { - DXResourceLayout resourceLayout = desc.ResourceLayout.DirectX12(); + ColorAttachmentBlendState blend = states[i]; - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) + pipelineStateStream.BlendState.RenderTarget[i] = new() { - if (resourceLayout.DescriptorRanges(stage, out DescriptorRange[] cbvSrvUavRanges, out DescriptorRange[] samplerRanges)) - { - if (cbvSrvUavRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - DescriptorTable = new() - { - NumDescriptorRanges = (uint)cbvSrvUavRanges.Length, - PDescriptorRanges = (DescriptorRange*)ZenithMarshal.AllocateAndFill(scope, cbvSrvUavRanges) - } - }); - } - - if (samplerRanges.Length > 0) - { - parameters.Add(new() - { - ParameterType = RootParameterType.TypeDescriptorTable, - ShaderVisibility = DXFormats.DirectX12(stage), - 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; - } - - PipelineStateStream2 pipelineStateStream2 = (PipelineStateStream2)graphicsPipelineStateDesc; - - // Amplification - Mesh - Pixel - { - if (desc.Amplification is not null) - { - pipelineStateStream2.AS.Data = desc.Amplification.DirectX12().GetShaderBytecode(scope); + 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.MS.Data = desc.Mesh.DirectX12().GetShaderBytecode(scope); - pipelineStateStream2.PS.Data = desc.Pixel.DirectX12().GetShaderBytecode(scope); } PipelineStateStreamDesc pipelineStateStreamDesc = new() { SizeInBytes = (uint)sizeof(PipelineStateStream2), - PPipelineStateSubobjectStream = &pipelineStateStream2 + PPipelineStateSubobjectStream = &pipelineStateStream }; - context.Device2?.CreatePipelineState(&pipelineStateStreamDesc, out PipelineState).Success(); + context.Device.CreatePipelineState(&pipelineStateStreamDesc, SilkMarshal.GuidPtrOf(), (void**)PipelineState.GetAddressOf()).Success(); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -197,6 +132,5 @@ protected override void SetResourceName(string name) protected override void Destroy() { PipelineState.Dispose(); - RootSignature.Dispose(); } } diff --git a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs index e9b391a9..da3b98d2 100644 --- a/sources/Zenith.NET.DirectX12/DXQueryHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXQueryHeap.cs @@ -15,23 +15,27 @@ public DXQueryHeap(DXGraphicsContext context, QueryHeapDesc desc) : base(context Count = desc.Count }; - context.Device.CreateQueryHeap(&queryHeapDesc, out QueryHeap).Success(); + context.Device.CreateQueryHeap(&queryHeapDesc, SilkMarshal.GuidPtrOf(), (void**)QueryHeap.GetAddressOf()).Success(); Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, - StrideInBytes = sizeof(ulong), - Flags = BufferUsageFlags.MapRead + Residency = MemoryResidency.CpuReadOnly }); } public DXBuffer Buffer { get; } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + 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(); } @@ -44,7 +48,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { Buffer.Dispose(); - QueryHeap.Dispose(); } } 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 deleted file mode 100644 index d09d3ae5..00000000 --- a/sources/Zenith.NET.DirectX12/DXResourceTable.cs +++ /dev/null @@ -1,278 +0,0 @@ -using Silk.NET.Direct3D12; - -namespace Zenith.NET.DirectX12; - -internal class DXResourceTable : ResourceTable -{ - private readonly DXDescriptorToken cbvSrvUavToken; - private readonly DXDescriptorToken samplerToken; - private readonly Dictionary graphicsTokens; - - 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++) - { - ResourceBinding binding = desc.Layout.Desc.Bindings[i]; - - for (uint j = 0; j < binding.Count; j++) - { - 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()); - } - } - } - - resourceStartIndex += binding.Count; - } - - SrvTextureViews = [.. srvTextureViews]; - UavTextureViews = [.. uavTextureViews]; - } - - 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) - { - foreach (ShaderStageFlags stage in ZenithHelper.GraphicShaderStages()) - { - (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); - } - } - } - else - { - if (cbvSrvUavToken.Length > 0) - { - commandBuffer.GraphicsCommandList4.SetComputeRootDescriptorTable(offset++, cbvSrvUavTable.GpuCurrentHandle); - - cbvSrvUavTable.Write(cbvSrvUavToken); - } - - if (samplerToken.Length > 0) - { - commandBuffer.GraphicsCommandList4.SetComputeRootDescriptorTable(offset++, samplerTable.GpuCurrentHandle); - - samplerTable.Write(samplerToken); - } - } - } - - 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) - { - } - - protected override void Destroy() - { - foreach ((DXDescriptorToken cbvSrvUavToken, DXDescriptorToken samplerToken) in graphicsTokens.Values) - { - cbvSrvUavToken.Dispose(); - samplerToken.Dispose(); - } - graphicsTokens.Clear(); - - samplerToken.Dispose(); - cbvSrvUavToken.Dispose(); - } - - private (DXDescriptorToken CbvSrvUavToken, DXDescriptorToken SamplerToken) GetTokens(ShaderStageFlags stage) - { - DXDescriptorToken cbvSrvUavToken = default; - DXDescriptorToken samplerToken = default; - - 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)); - - uint index = 0; - foreach (DXResourceRange range in cbvSrvUavRanges) - { - IBindableResource[] resources = [.. Desc.Resources.Skip((int)range.Index).Take((int)range.Count)]; - - switch (range.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) - { - 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 (samplerRanges.Length > 0) - { - samplerToken = Context.SamplerAllocator.Allocate((uint)samplerRanges.Sum(static item => item.Count)); - - uint index = 0; - foreach (DXResourceRange range in samplerRanges) - { - foreach (IBindableResource resource in Desc.Resources.Skip((int)range.Index).Take((int)range.Count)) - { - if (resource is Sampler sampler) - { - Context.Device.CopyDescriptorsSimple(1, samplerToken[index], sampler.DirectX12().Token.Handle, DescriptorHeapType.Sampler); - } - - index++; - } - } - } - } - - return (cbvSrvUavToken, samplerToken); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXSampler.cs b/sources/Zenith.NET.DirectX12/DXSampler.cs index c422114e..991fee01 100644 --- a/sources/Zenith.NET.DirectX12/DXSampler.cs +++ b/sources/Zenith.NET.DirectX12/DXSampler.cs @@ -8,34 +8,31 @@ internal unsafe class DXSampler : Sampler public DXSampler(DXGraphicsContext context, SamplerDesc desc) : base(context, desc) { + Token = context.SamplerHeap.Allocate(); + 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), + 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.Filter, desc.ComparisonFunc).ComparisonFunc, + ComparisonFunc = DXFormats.DirectX12(desc.CompareOp), 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); + (samplerDesc.BorderColor[0], samplerDesc.BorderColor[1], samplerDesc.BorderColor[2], samplerDesc.BorderColor[3]) = DXFormats.DirectX12(desc.BorderColor); + + context.Device.CreateSampler(&samplerDesc, Token.CpuHandle); + } + + public override ResourceHandle Handle => Token.ResourceHandle; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.DirectX12/DXShader.cs b/sources/Zenith.NET.DirectX12/DXShader.cs index 5d425ab5..f36c6cdf 100644 --- a/sources/Zenith.NET.DirectX12/DXShader.cs +++ b/sources/Zenith.NET.DirectX12/DXShader.cs @@ -8,11 +8,16 @@ public ShaderBytecode GetShaderBytecode(ZenithMarshal.Scope scope) { return new() { - PShaderBytecode = (byte*)ZenithMarshal.AllocateAndFill(scope, Desc.ShaderBytes), - BytecodeLength = (uint)Desc.ShaderBytes.Length + PShaderBytecode = (byte*)ZenithMarshal.AllocateAndFill(scope, Desc.CodeBytes), + BytecodeLength = (uint)Desc.CodeBytes.Length }; } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + protected override void SetResourceName(string name) { } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index 14fbca20..c023f988 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -1,64 +1,55 @@ using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; internal unsafe class DXSwapChain : SwapChain { - private readonly DXFence fence; - private readonly DXSwapChainFrameBuffer swapChainFrameBuffer; + private readonly DXTexture[] textures = new DXTexture[3]; - public ComPtr SwapChain3; + public ComPtr SwapChain; - public uint BufferIndex; + private uint index; public DXSwapChain(DXGraphicsContext context, SwapChainDesc desc) : base(context, desc) { - fence = new(context); - swapChainFrameBuffer = new(context, this); - CreateSwapChain(); + CreateTextures(); } public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; - public override FrameBuffer FrameBuffer => swapChainFrameBuffer[BufferIndex]; + public override Texture Drawable => textures[index]; - public override void Present() + public override nint GetNativeObject(NativeObjectType type) { - if (SwapChain3.Handle is not null) - { - SwapChain3.Present(0, DXGI.PresentAllowTearing).Success(); + return 0; + } - fence.Wait(Context.GraphicsQueue); + protected override void PresentImpl() + { + SwapChain.Present(0, DXGI.PresentAllowTearing).Success(); - BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); - } + index = SwapChain.GetCurrentBackBufferIndex(); } protected override void ResizeImpl() { - if (SwapChain3.Handle is not null) - { - fence.Wait(Context.GraphicsQueue); - - swapChainFrameBuffer.DestroyFrameBuffers(); - - SwapChain3.ResizeBuffers(DXGraphicsContext.SwapChainBufferCount, - Desc.Surface.Width, - Desc.Surface.Height, - DXFormats.DirectX12(Desc.ColorTargetFormat), - (uint)SwapChainFlag.AllowTearing).Success(); + DestroyTextures(); - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, []); + SwapChain.ResizeBuffers((uint)textures.Length, Desc.Surface.Width, Desc.Surface.Height, DXFormats.DirectX12(Desc.Format), (uint)SwapChainFlag.AllowTearing).Success(); - BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); - } + CreateTextures(); } protected override void RefreshImpl() { + DestroyTextures(); + DestroySwapChain(); + CreateSwapChain(); + CreateTextures(); } protected override void SetResourceName(string name) @@ -67,55 +58,71 @@ protected override void SetResourceName(string name) protected override void Destroy() { + DestroyTextures(); DestroySwapChain(); - - swapChainFrameBuffer.Dispose(); - fence.Dispose(); } private void CreateSwapChain() { - DestroySwapChain(); - - if (Desc.Surface.Type is not SurfaceType.D3D11Interop) + SwapChainDesc1 swapChainDesc = new() { - 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(); - - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, []); - - BufferIndex = SwapChain3.GetCurrentBackBufferIndex(); - } - else - { - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); - } + 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.Handles[0], + &swapChainDesc, + default(SwapChainFullscreenDesc*), + default(IDXGIOutput*), + (IDXGISwapChain1**)SwapChain.GetAddressOf()).Success(); } private void DestroySwapChain() { - swapChainFrameBuffer.DestroyFrameBuffers(); + SwapChain.Dispose(); + + 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 | TextureUsages.TransferDst + }; + + 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); + } - SwapChain3.Dispose(); - SwapChain3 = default; + index = SwapChain.GetCurrentBackBufferIndex(); + } - BufferIndex = 0; + private void DestroyTextures() + { + for (int i = 0; i < textures.Length; i++) + { + textures[i].Dispose(); + } } } diff --git a/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs b/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs deleted file mode 100644 index 0757905c..00000000 --- a/sources/Zenith.NET.DirectX12/DXSwapChainFrameBuffer.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Silk.NET.Core.Native; -using Silk.NET.Direct3D12; -using Silk.NET.DXGI; - -namespace Zenith.NET.DirectX12; - -internal unsafe class DXSwapChainFrameBuffer(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 void CreateFrameBuffers(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]; - 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 - }); - } - } - 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 - }); - } - } - - public void DestroyFrameBuffers() - { - foreach (DXFrameBuffer frameBuffer in frameBuffers) - { - frameBuffer.Dispose(); - } - frameBuffers = []; - - foreach (DXTexture texture in colorTargets) - { - texture.Dispose(); - } - colorTargets = []; - - depthStencilTarget?.Dispose(); - depthStencilTarget = null; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroyFrameBuffers(); - } -} diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index fab413e8..ccb19acb 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -1,62 +1,40 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D12; +using Silk.NET.DXGI; 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) : base(context, desc) { - bool isRenderTargetOrDepthStencil = desc.Flags.HasFlag(TextureUsageFlags.RenderTarget) || desc.Flags.HasFlag(TextureUsageFlags.DepthStencil); + ResourceDesc1 resourceDesc = ResourceDesc(desc); - ResourceDesc resourceDesc = new() - { - Dimension = DXFormats.DirectX12(desc.Type), - Width = desc.Width, - Height = desc.Height, - DepthOrArraySize = (ushort)(desc.Type is TextureType.Texture3D ? desc.Depth : ZenithHelper.FlattenArrayLayerCount(desc)), - 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); + HeapProperties heapProperties = new() { Type = DxHeapType.Default }; - 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(); - } + 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() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) }); - - 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,207 +44,174 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr (DXGraphicsContext)base.Context; - public DXHeap? Heap { get; } - public DXTextureView View { get; } - public ResourceStates[] States { get; } + public override ResourceHandle SampledHandle => View.SampledHandle; + + public override ResourceHandle StorageHandle => View.StorageHandle; - public void TransitionStates(DXCommandBuffer commandBuffer, - uint firstMipLevel, - uint mipLevelCount, - uint firstArrayLayer, - uint arrayLayerCount, - uint firstFace, - uint faceCount, - ResourceStates newStates) + public uint SubresourceIndex(TextureSubresource subresource) { - if (!commandBuffer.CanTransitionResourceStates) - { - return; - } + return subresource.MipLevel + (subresource.ArrayLayer * Desc.MipLevels); + } - for (uint i = 0; i < mipLevelCount; i++) + public CpuDescriptorHandle GetRtvHandle(TextureSubresource subresource) + { + if (!rtvTokens.TryGetValue(subresource, out DXDescriptorToken token)) { - 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 }; + rtvTokens[subresource] = token = Context.RtvHeap.Allocate(); + + RenderTargetViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; - uint index = ZenithHelper.SubresourceIndex(Desc, slice); + switch (Desc.Type) + { + case TextureType.Texture1D: + viewDesc.ViewDimension = RtvDimension.Texture1D; + viewDesc.Texture1D = new() { MipSlice = subresource.MipLevel }; + break; - ResourceStates oldStates = States[index]; + case TextureType.Texture1DArray: + viewDesc.ViewDimension = RtvDimension.Texture1Darray; + viewDesc.Texture1DArray = new() + { + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + break; - if (oldStates == newStates) + case TextureType.Texture2D: + if (Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = RtvDimension.Texture2D; + viewDesc.Texture2D = new() { MipSlice = subresource.MipLevel }; + } + else { - continue; + viewDesc.ViewDimension = RtvDimension.Texture2Dms; } + break; - ResourceBarrier barrier = new() + case TextureType.Texture2DArray: + case TextureType.TextureCube: + case TextureType.TextureCubeArray: + if (Desc.SampleCount is SampleCount.Count1) { - Type = ResourceBarrierType.Transition, - Transition = new() + viewDesc.ViewDimension = RtvDimension.Texture2Darray; + viewDesc.Texture2DArray = new() { - PResource = Resource, - Subresource = index, - StateBefore = oldStates, - StateAfter = newStates - } - }; - - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + } + else + { + viewDesc.ViewDimension = RtvDimension.Texture2Dmsarray; + viewDesc.Texture2DMSArray = new() + { + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + } + break; - States[index] = newStates; - } + 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); } - } - public void TransitionStates(DXCommandBuffer commandBuffer, TextureSlice slice, ResourceStates newStates) - { - TransitionStates(commandBuffer, slice.MipLevel, 1, slice.ArrayLayer, 1, slice.Face, 1, newStates); + return token.CpuHandle; } - public DXDescriptorToken CreateRtvToken(TextureSlice slice) + public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) { - DXDescriptorToken token = Context.RtvAllocator.Allocate(1); - - RenderTargetViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; - - switch (Desc.Type) + if (!dsvTokens.TryGetValue(subresource, out DXDescriptorToken token)) { - case TextureType.Texture1D: - { - viewDesc.ViewDimension = RtvDimension.Texture1D; - viewDesc.Texture1D.MipSlice = slice.MipLevel; - } - break; - - case TextureType.Texture1DArray: - { - viewDesc.ViewDimension = RtvDimension.Texture1Darray; - viewDesc.Texture1DArray.MipSlice = slice.MipLevel; - viewDesc.Texture1DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerIndex(Desc, slice); - viewDesc.Texture1DArray.ArraySize = 1; - } - break; - - case TextureType.Texture2D: - if (Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = RtvDimension.Texture2D; - viewDesc.Texture2D.MipSlice = slice.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 = slice.MipLevel; - viewDesc.Texture2DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerIndex(Desc, slice); - viewDesc.Texture2DArray.ArraySize = 1; - } - else - { - viewDesc.ViewDimension = RtvDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerIndex(Desc, slice); - viewDesc.Texture2DMSArray.ArraySize = 1; - } - break; - - case TextureType.Texture3D: - { - viewDesc.ViewDimension = RtvDimension.Texture3D; - viewDesc.Texture3D.MipSlice = slice.MipLevel; - viewDesc.Texture3D.WSize = Desc.Depth; - } - break; - } + dsvTokens[subresource] = token = Context.DsvHeap.Allocate(); - Context.Device.CreateRenderTargetView(Resource, &viewDesc, token.Handle); + DepthStencilViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; - return token; - } + switch (Desc.Type) + { + case TextureType.Texture1D: + viewDesc.ViewDimension = DsvDimension.Texture1D; + viewDesc.Texture1D = new() { MipSlice = subresource.MipLevel }; + break; - public DXDescriptorToken CreateDsvToken(TextureSlice slice) - { - DXDescriptorToken token = Context.DsvAllocator.Allocate(1); + case TextureType.Texture1DArray: + viewDesc.ViewDimension = DsvDimension.Texture1Darray; + viewDesc.Texture1DArray = new() + { + MipSlice = subresource.MipLevel, + FirstArraySlice = subresource.ArrayLayer, + ArraySize = 1 + }; + break; - DepthStencilViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; + case TextureType.Texture2D: + if (Desc.SampleCount is SampleCount.Count1) + { + viewDesc.ViewDimension = DsvDimension.Texture2D; + viewDesc.Texture2D = new() { MipSlice = subresource.MipLevel }; + } + else + { + viewDesc.ViewDimension = DsvDimension.Texture2Dms; + } + break; - switch (Desc.Type) - { - case TextureType.Texture1D: - { - viewDesc.ViewDimension = DsvDimension.Texture1D; - viewDesc.Texture1D.MipSlice = slice.MipLevel; - } - 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; + } - case TextureType.Texture1DArray: - { - viewDesc.ViewDimension = DsvDimension.Texture1Darray; - viewDesc.Texture1DArray.MipSlice = slice.MipLevel; - viewDesc.Texture1DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerIndex(Desc, slice); - viewDesc.Texture1DArray.ArraySize = 1; - } - break; - - case TextureType.Texture2D: - case TextureType.Texture3D: - if (Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = DsvDimension.Texture2D; - viewDesc.Texture2D.MipSlice = slice.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 = slice.MipLevel; - viewDesc.Texture2DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerIndex(Desc, slice); - viewDesc.Texture2DArray.ArraySize = 1; - } - else - { - viewDesc.ViewDimension = DsvDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerIndex(Desc, slice); - viewDesc.Texture2DMSArray.ArraySize = 1; - } - break; + Context.Device.CreateDepthStencilView(Resource, &viewDesc, token.CpuHandle); } - Context.Device.CreateDepthStencilView(Resource, &viewDesc, token.Handle); + return token.CpuHandle; + } - return token; + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -276,10 +221,34 @@ protected override void SetResourceName(string name) protected override void Destroy() { - View.Dispose(); + foreach (DXDescriptorToken token in rtvTokens.Values) + { + token.Dispose(); + } + rtvTokens.Clear(); + + foreach (DXDescriptorToken token in dsvTokens.Values) + { + token.Dispose(); + } + dsvTokens.Clear(); + View.Dispose(); Resource.Dispose(); + } - Heap?.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), + Flags = DXFormats.DirectX12(desc.Usages) + }; } } diff --git a/sources/Zenith.NET.DirectX12/DXTextureView.cs b/sources/Zenith.NET.DirectX12/DXTextureView.cs index ea183168..541bdd21 100644 --- a/sources/Zenith.NET.DirectX12/DXTextureView.cs +++ b/sources/Zenith.NET.DirectX12/DXTextureView.cs @@ -1,5 +1,4 @@ using Silk.NET.Direct3D12; -using Silk.NET.DXGI; namespace Zenith.NET.DirectX12; @@ -8,20 +7,13 @@ internal unsafe class DXTextureView(DXGraphicsContext context, TextureViewDesc d private DXDescriptorToken? srvToken; private DXDescriptorToken? uavToken; - public CpuDescriptorHandle SrvHandle => (srvToken ??= CreateSrvToken()).Handle; + public override ResourceHandle SampledHandle => (srvToken ??= CreateSrvToken()).ResourceHandle; - public CpuDescriptorHandle UavHandle => (uavToken ??= CreateUavToken()).Handle; + public override ResourceHandle StorageHandle => (uavToken ??= CreateUavToken()).ResourceHandle; - public void TransitionStates(DXCommandBuffer commandBuffer, ResourceStates newStates) + public override nint GetNativeObject(NativeObjectType type) { - Desc.Texture.DirectX12().TransitionStates(commandBuffer, - Desc.FirstMipLevel, - Desc.MipLevelCount, - Desc.FirstArrayLayer, - Desc.ArrayLayerCount, - 0, - ZenithHelper.FaceCount(Desc.Texture.Desc), - newStates); + return 0; } protected override void SetResourceName(string name) @@ -36,40 +28,34 @@ protected override void Destroy() private DXDescriptorToken CreateSrvToken() { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); ShaderResourceViewDesc viewDesc = new() { - Format = Resolve(Desc.Texture.Desc.Format), + Format = DXFormats.DirectX12(Desc.Format), Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping }; - switch (Desc.Texture.Desc.Type) + switch (Desc.Type) { case TextureType.Texture1D: + viewDesc.ViewDimension = SrvDimension.Texture1D; + viewDesc.Texture1D = new() { - viewDesc.ViewDimension = SrvDimension.Texture1D; - viewDesc.Texture1D.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture1D.MipLevels = Desc.MipLevelCount; - } - 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; - } + 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.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture2D.MipLevels = Desc.MipLevelCount; + viewDesc.Texture2D = new() + { + MostDetailedMip = Desc.Range.BaseMipLevel, + MipLevels = Desc.Range.LevelCount + }; } else { @@ -77,132 +63,136 @@ private DXDescriptorToken CreateSrvToken() } 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.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture2DArray.MipLevels = Desc.MipLevelCount; - viewDesc.Texture2DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; + 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.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DMSArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; - } - break; - - case TextureType.Texture3D: - { - viewDesc.ViewDimension = SrvDimension.Texture3D; - viewDesc.Texture3D.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.Texture3D.MipLevels = Desc.MipLevelCount; - } - break; - - case TextureType.TextureCube: - { - viewDesc.ViewDimension = SrvDimension.Texturecube; - viewDesc.TextureCube.MostDetailedMip = Desc.FirstMipLevel; - viewDesc.TextureCube.MipLevels = Desc.MipLevelCount; + viewDesc.Texture2DMSArray = new() + { + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; } 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.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.Device.CreateShaderResourceView(Desc.Texture.DirectX12().Resource, &viewDesc, token.Handle); + context.Device.CreateShaderResourceView(Desc.Texture.DirectX12().Resource, &viewDesc, token.CpuHandle); return token; } private DXDescriptorToken CreateUavToken() { - DXDescriptorToken token = context.CbvSrvUavAllocator.Allocate(1); + DXDescriptorToken token = context.CbvSrvUavHeap.Allocate(); - UnorderedAccessViewDesc viewDesc = new() { Format = Resolve(Desc.Texture.Desc.Format) }; + UnorderedAccessViewDesc viewDesc = new() { Format = DXFormats.DirectX12(Desc.Format) }; - switch (Desc.Texture.Desc.Type) + switch (Desc.Type) { case TextureType.Texture1D: - { - viewDesc.ViewDimension = UavDimension.Texture1D; - viewDesc.Texture1D.MipSlice = Desc.FirstMipLevel; - } + viewDesc.ViewDimension = UavDimension.Texture1D; + viewDesc.Texture1D = new() { MipSlice = Desc.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; - } + case TextureType.Texture2D: + viewDesc.ViewDimension = UavDimension.Texture2D; + viewDesc.Texture2D = new() { MipSlice = Desc.Range.BaseMipLevel }; break; - case TextureType.Texture2D: - if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) - { - viewDesc.ViewDimension = UavDimension.Texture2D; - viewDesc.Texture2D.MipSlice = Desc.FirstMipLevel; - } - else + case TextureType.Texture3D: + viewDesc.ViewDimension = UavDimension.Texture3D; + viewDesc.Texture3D = new() { - viewDesc.ViewDimension = UavDimension.Texture2Dms; - } + MipSlice = Desc.Range.BaseMipLevel, + WSize = Desc.Texture.Desc.Depth + }; break; - case TextureType.Texture2DArray: case TextureType.TextureCube: - case TextureType.TextureCubeArray: - if (Desc.Texture.Desc.SampleCount is SampleCount.Count1) + viewDesc.ViewDimension = UavDimension.Texture2Darray; + viewDesc.Texture2DArray = new() { - viewDesc.ViewDimension = UavDimension.Texture2Darray; - viewDesc.Texture2DArray.MipSlice = Desc.FirstMipLevel; - viewDesc.Texture2DArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; - } - else + MipSlice = Desc.Range.BaseMipLevel, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; + break; + + case TextureType.Texture1DArray: + viewDesc.ViewDimension = UavDimension.Texture1Darray; + viewDesc.Texture1DArray = new() { - viewDesc.ViewDimension = UavDimension.Texture2Dmsarray; - viewDesc.Texture2DMSArray.FirstArraySlice = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerIndex; - viewDesc.Texture2DMSArray.ArraySize = ZenithHelper.FlattenArrayLayerRange(Desc).FlattenArrayLayerCount; - } + MipSlice = Desc.Range.BaseMipLevel, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; break; - case TextureType.Texture3D: + case TextureType.Texture2DArray: + case TextureType.TextureCubeArray: + viewDesc.ViewDimension = UavDimension.Texture2Darray; + viewDesc.Texture2DArray = new() { - viewDesc.ViewDimension = UavDimension.Texture3D; - viewDesc.Texture3D.MipSlice = Desc.FirstMipLevel; - viewDesc.Texture3D.WSize = Desc.Texture.Desc.Depth; - } + MipSlice = Desc.Range.BaseMipLevel, + FirstArraySlice = Desc.Range.BaseArrayLayer, + ArraySize = Desc.Range.LayerCount + }; break; } - context.Device.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, (ID3D12Resource*)null, &viewDesc, token.Handle); + context.Device.CreateUnorderedAccessView(Desc.Texture.DirectX12().Resource, default(ID3D12Resource*), &viewDesc, token.CpuHandle); 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/DXTimeline.cs b/sources/Zenith.NET.DirectX12/DXTimeline.cs new file mode 100644 index 00000000..07f6e77c --- /dev/null +++ b/sources/Zenith.NET.DirectX12/DXTimeline.cs @@ -0,0 +1,55 @@ +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, 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 nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override ulong GetCompletedValue() + { + return Fence.GetCompletedValue(); + } + + protected override void SignalImpl(ulong value) + { + Queue.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/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index 1c21312e..b32c76b7 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -7,147 +7,148 @@ internal unsafe class DXTopLevelAccelerationStructure : TopLevelAccelerationStru { public DXDescriptorToken Token; - public DXTopLevelAccelerationStructure(DXGraphicsContext context, TopLevelAccelerationStructureDesc desc, DXCommandBuffer commandBuffer) : base(context, desc) + public DXTopLevelAccelerationStructure(DXGraphicsContext context, DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { - using ZenithMarshal.Scope scope = new(); - - InstanceBuffer = new(context, new() + Instance = new(context, new() { SizeInBytes = (uint)(sizeof(RaytracingInstanceDesc) * desc.Instances.Length), - StrideInBytes = (uint)sizeof(RaytracingInstanceDesc), - Flags = BufferUsageFlags.MapWrite + Residency = MemoryResidency.CpuWriteOnly }); - FillInstanceBuffer(desc, out BuildRaytracingAccelerationStructureInputs inputs); + BuildRaytracingAccelerationStructureInputs inputs = Inputs(desc); RaytracingAccelerationStructurePrebuildInfo prebuildInfo = new(); + context.Device.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - context.Device5?.GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo); - - AccelerationStructureBuffer = new(context, new() + AccelerationStructure = new(context, new() { SizeInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, - StrideInBytes = (uint)prebuildInfo.ResultDataMaxSizeInBytes, - Flags = BufferUsageFlags.AccelerationStructure - }); + Residency = MemoryResidency.GpuOnly + }, ResourceFlags.RaytracingAccelerationStructure); - ScratchBuffer = new(context, new() + Scratch = new(context, new() { SizeInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, - StrideInBytes = (uint)prebuildInfo.ScratchDataSizeInBytes, - Flags = BufferUsageFlags.UnorderedAccess + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly }); BuildRaytracingAccelerationStructureDesc buildDesc = new() { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, Inputs = inputs, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress - }; - - commandBuffer.GraphicsCommandList4.BuildRaytracingAccelerationStructure(&buildDesc, 0, (RaytracingAccelerationStructurePostbuildInfoDesc*)null); - - ResourceBarrier barrier = new() - { - Type = ResourceBarrierType.Uav, - UAV = new() - { - PResource = AccelerationStructureBuffer.Resource - } + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); + BuildSyncBarrier(commandBuffer, BarrierSync.BuildRaytracingAccelerationStructure); + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + BuildSyncBarrier(commandBuffer, BarrierSync.AllShading); ShaderResourceViewDesc viewDesc = new() { ViewDimension = SrvDimension.RaytracingAccelerationStructure, Shader4ComponentMapping = DXGraphicsContext.Shader4ComponentMapping, - RaytracingAccelerationStructure = new() - { - Location = AccelerationStructureBuffer.GPUVirtualAddress - } + RaytracingAccelerationStructure = new() { Location = AccelerationStructure.GPUVirtualAddress } }; - context.Device.CreateShaderResourceView((ID3D12Resource*)null, &viewDesc, (Token = context.CbvSrvUavAllocator.Allocate(1)).Handle); + context.Device.CreateShaderResourceView(default(ID3D12Resource*), &viewDesc, (Token = context.CbvSrvUavHeap.Allocate()).CpuHandle); } - public new DXGraphicsContext Context => (DXGraphicsContext)base.Context; + public DXBuffer Instance { get; } - public DXBuffer InstanceBuffer { get; } + public DXBuffer AccelerationStructure { get; } - public DXBuffer AccelerationStructureBuffer { get; } + public DXBuffer Scratch { get; } - public DXBuffer ScratchBuffer { get; } + public override ResourceHandle Handle => Token.ResourceHandle; public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) { - FillInstanceBuffer(newDesc, out BuildRaytracingAccelerationStructureInputs inputs); + BuildRaytracingAccelerationStructureInputs inputs = Inputs(newDesc); + inputs.Flags |= RaytracingAccelerationStructureBuildFlags.PerformUpdate; BuildRaytracingAccelerationStructureDesc buildDesc = new() { - DestAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, + DestAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, Inputs = inputs, - SourceAccelerationStructureData = AccelerationStructureBuffer.GPUVirtualAddress, - ScratchAccelerationStructureData = ScratchBuffer.GPUVirtualAddress + SourceAccelerationStructureData = AccelerationStructure.GPUVirtualAddress, + ScratchAccelerationStructureData = Scratch.GPUVirtualAddress }; - commandBuffer.GraphicsCommandList4.BuildRaytracingAccelerationStructure(&buildDesc, 0, (RaytracingAccelerationStructurePostbuildInfoDesc*)null); - - ResourceBarrier barrier = new() - { - Type = ResourceBarrierType.Uav, - UAV = new() - { - PResource = AccelerationStructureBuffer.Resource - } - }; + BuildSyncBarrier(commandBuffer, BarrierSync.BuildRaytracingAccelerationStructure); + commandBuffer.CommandList.BuildRaytracingAccelerationStructure(&buildDesc, 0, default(RaytracingAccelerationStructurePostbuildInfoDesc*)); + BuildSyncBarrier(commandBuffer, BarrierSync.AllShading); + } - commandBuffer.GraphicsCommandList4.ResourceBarrier(1, &barrier); + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) { + AccelerationStructure.Name = name; } protected override void Destroy() { Token.Dispose(); - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - InstanceBuffer.Dispose(); + Scratch.Dispose(); + AccelerationStructure.Dispose(); + Instance.Dispose(); } - private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc, out BuildRaytracingAccelerationStructureInputs inputs) + private BuildRaytracingAccelerationStructureInputs Inputs(TopLevelAccelerationStructureDesc desc) { uint instanceCount = (uint)desc.Instances.Length; - MappedMemory mappedMemory = InstanceBuffer.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]; instances[i] = new() { - InstanceID = instance.ID, - InstanceMask = instance.Mask, + 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() + return new() { Type = RaytracingAccelerationStructureType.TopLevel, - Flags = DXFormats.DirectX12(desc.Flags), + Flags = DXFormats.DirectX12(desc.BuildFlags), NumDescs = instanceCount, - InstanceDescs = InstanceBuffer.GPUVirtualAddress + 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); } } diff --git a/sources/Zenith.NET.DirectX12/DXValidationLayer.cs b/sources/Zenith.NET.DirectX12/DXValidationLayer.cs index cd9b536b..5bb14255 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,31 +8,41 @@ internal unsafe class DXValidationLayer : ValidationLayer private readonly PfnMessageFunc callback; private readonly uint callbackCookie; + public ComPtr InfoQueue; + public DXValidationLayer(DXGraphicsContext context) : base(context) { - context.InfoQueue1?.RegisterMessageCallback(callback = new(Callback), MessageCallbackFlags.FlagNone, null, ref callbackCookie).Success(); + context.Device.QueryInterface(SilkMarshal.GuidPtrOf(), (void**)InfoQueue.GetAddressOf()).Success(); + + InfoQueue.RegisterMessageCallback(callback = new(Callback), MessageCallbackFlags.FlagNone, default, 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(); + InfoQueue.UnregisterMessageCallback(callbackCookie).Success(); + InfoQueue.Dispose(); 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(MessageSource.GraphicsAPI, severity switch + Report(severity switch { DxMessageSeverity.Error => MessageSeverity.Error, DxMessageSeverity.Warning => MessageSeverity.Warning, - _ => MessageSeverity.Message - }, ZenithMarshal.StringFromPointer((nint)pDescription, StringEncoding.UTF8)); + _ => MessageSeverity.Info + }, ZenithMarshal.StringFromPointer((nint)description, StringEncoding.UTF8)); } } diff --git a/sources/Zenith.NET.DirectX12/Extensions.cs b/sources/Zenith.NET.DirectX12/Extensions.cs index a9598823..0f15535d 100644 --- a/sources/Zenith.NET.DirectX12/Extensions.cs +++ b/sources/Zenith.NET.DirectX12/Extensions.cs @@ -28,139 +28,139 @@ internal bool IsSuccess() } } - extension(CommandBuffer commandBuffer) + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { - internal DXCommandBuffer DirectX12() + internal DXBottomLevelAccelerationStructure DirectX12() { - return (DXCommandBuffer)commandBuffer; + return (DXBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; } } - extension(SwapChain swapChain) + extension(Buffer buffer) { - internal DXSwapChain DirectX12() + internal DXBuffer DirectX12() { - return (DXSwapChain)swapChain; + return (DXBuffer)buffer; } } - extension(FrameBuffer frameBuffer) + extension(BufferView bufferView) { - internal DXFrameBuffer DirectX12() + internal DXBufferView DirectX12() { - return (DXFrameBuffer)frameBuffer; + return (DXBufferView)bufferView; } } - extension(Shader shader) + extension(CommandBuffer commandBuffer) { - internal DXShader DirectX12() + internal DXCommandBuffer DirectX12() { - return (DXShader)shader; + 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(Timeline timeline) { - internal DXBufferView DirectX12() + internal DXTimeline DirectX12() { - return (DXBufferView)bufferView; + return (DXTimeline)timeline; } } - extension(Texture texture) + extension(ComputePipeline computePipeline) { - internal DXTexture DirectX12() + internal DXComputePipeline DirectX12() { - return (DXTexture)texture; + return (DXComputePipeline)computePipeline; } } - extension(TextureView textureView) + extension(GraphicsPipeline graphicsPipeline) { - internal DXTextureView DirectX12() + internal DXGraphicsPipeline DirectX12() { - return (DXTextureView)textureView; + return (DXGraphicsPipeline)graphicsPipeline; } } - extension(Sampler sampler) + extension(Heap heap) { - internal DXSampler DirectX12() + internal DXHeap DirectX12() { - return (DXSampler)sampler; + return (DXHeap)heap; } } - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + extension(MeshShadingPipeline meshShadingPipeline) { - internal DXBottomLevelAccelerationStructure DirectX12() + internal DXMeshShadingPipeline DirectX12() { - return (DXBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + return (DXMeshShadingPipeline)meshShadingPipeline; } } - extension(TopLevelAccelerationStructure topLevelAccelerationStructure) + extension(QueryHeap queryHeap) { - internal DXTopLevelAccelerationStructure DirectX12() + internal DXQueryHeap DirectX12() { - return (DXTopLevelAccelerationStructure)topLevelAccelerationStructure; + return (DXQueryHeap)queryHeap; } } - extension(ResourceLayout resourceLayout) + extension(Sampler sampler) { - internal DXResourceLayout DirectX12() + internal DXSampler DirectX12() { - return (DXResourceLayout)resourceLayout; + return (DXSampler)sampler; } } - extension(ResourceTable resourceTable) + extension(Shader shader) { - internal DXResourceTable DirectX12() + internal DXShader DirectX12() { - return (DXResourceTable)resourceTable; + return (DXShader)shader; } } - extension(GraphicsPipeline graphicsPipeline) + extension(SwapChain swapChain) { - internal DXGraphicsPipeline DirectX12() + internal DXSwapChain DirectX12() { - return (DXGraphicsPipeline)graphicsPipeline; + return (DXSwapChain)swapChain; } } - extension(ComputePipeline computePipeline) + extension(Texture texture) { - internal DXComputePipeline DirectX12() + internal DXTexture DirectX12() { - return (DXComputePipeline)computePipeline; + return (DXTexture)texture; } } - extension(MeshShadingPipeline meshShadingPipeline) + extension(TextureView textureView) { - internal DXMeshShadingPipeline DirectX12() + internal DXTextureView DirectX12() { - return (DXMeshShadingPipeline)meshShadingPipeline; + return (DXTextureView)textureView; } } - extension(QueryHeap queryHeap) + extension(TopLevelAccelerationStructure topLevelAccelerationStructure) { - internal DXQueryHeap DirectX12() + internal DXTopLevelAccelerationStructure DirectX12() { - return (DXQueryHeap)queryHeap; + return (DXTopLevelAccelerationStructure)topLevelAccelerationStructure; } } } diff --git a/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs b/sources/Zenith.NET.DirectX12/PipelineStateStream2.cs index 3689bc95..fcd570d6 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 _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(); - 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 PRootSignature => ref _pRootSignature.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 diff --git a/sources/Zenith.NET.DirectX12/Usings.cs b/sources/Zenith.NET.DirectX12/Usings.cs index 5371b106..2e2db785 100644 --- a/sources/Zenith.NET.DirectX12/Usings.cs +++ b/sources/Zenith.NET.DirectX12/Usings.cs @@ -1,14 +1,14 @@ -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 DxBlendOp = Silk.NET.Direct3D12.BlendOp; global using DxCullMode = Silk.NET.Direct3D12.CullMode; global using DxFillMode = Silk.NET.Direct3D12.FillMode; -global using DxFilter = Silk.NET.Direct3D12.Filter; +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 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 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.Metal/Extensions.cs b/sources/Zenith.NET.Metal/Extensions.cs index 33bbf9b5..9c8a34ae 100644 --- a/sources/Zenith.NET.Metal/Extensions.cs +++ b/sources/Zenith.NET.Metal/Extensions.cs @@ -26,139 +26,147 @@ internal void Success() } } - extension(CommandBuffer commandBuffer) + extension(ulong value) { - internal MTLCommandBuffer Metal() + internal ResourceHandle ToHandle() { - return (MTLCommandBuffer)commandBuffer; + return new((uint)value, (uint)(value >> 32)); } } - extension(SwapChain swapChain) + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { - internal MTLSwapChain Metal() + internal MTLBottomLevelAccelerationStructure Metal() { - return (MTLSwapChain)swapChain; + return (MTLBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; } } - extension(FrameBuffer frameBuffer) + extension(Buffer buffer) { - internal MTLFrameBuffer Metal() + internal MTLBuffer Metal() { - return (MTLFrameBuffer)frameBuffer; + return (MTLBuffer)buffer; } } - extension(Shader shader) + extension(BufferView bufferView) { - internal MTLShader Metal() + internal MTLBufferView Metal() { - return (MTLShader)shader; + return (MTLBufferView)bufferView; } } - extension(Buffer buffer) + extension(CommandBuffer commandBuffer) { - internal MTLBuffer Metal() + internal MTLCommandBuffer Metal() { - return (MTLBuffer)buffer; + return (MTLCommandBuffer)commandBuffer; } } - extension(BufferView bufferView) + extension(CommandQueue commandQueue) { - internal MTLBufferView Metal() + internal MTLCommandQueue Metal() { - return (MTLBufferView)bufferView; + return (MTLCommandQueue)commandQueue; } } - extension(Texture texture) + extension(Timeline timeline) { - internal MTLTexture Metal() + internal MTLTimeline Metal() { - return (MTLTexture)texture; + return (MTLTimeline)timeline; } } - extension(TextureView textureView) + extension(ComputePipeline computePipeline) { - internal MTLTextureView Metal() + internal MTLComputePipeline Metal() { - return (MTLTextureView)textureView; + return (MTLComputePipeline)computePipeline; } } - extension(Sampler sampler) + extension(GraphicsPipeline graphicsPipeline) { - internal MTLSampler Metal() + internal MTLGraphicsPipeline Metal() { - return (MTLSampler)sampler; + return (MTLGraphicsPipeline)graphicsPipeline; } } - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + extension(Heap heap) { - internal MTLBottomLevelAccelerationStructure Metal() + internal MTLHeap Metal() { - return (MTLBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + return (MTLHeap)heap; } } - extension(TopLevelAccelerationStructure topLevelAccelerationStructure) + extension(MeshShadingPipeline meshShadingPipeline) { - internal MTLTopLevelAccelerationStructure Metal() + internal MTLMeshShadingPipeline Metal() { - return (MTLTopLevelAccelerationStructure)topLevelAccelerationStructure; + return (MTLMeshShadingPipeline)meshShadingPipeline; } } - extension(ResourceLayout resourceLayout) + extension(QueryHeap queryHeap) { - internal MTLResourceLayout Metal() + internal MTLQueryHeap Metal() { - return (MTLResourceLayout)resourceLayout; + return (MTLQueryHeap)queryHeap; } } - extension(ResourceTable resourceTable) + extension(Sampler sampler) { - internal MTLResourceTable Metal() + internal MTLSampler Metal() { - return (MTLResourceTable)resourceTable; + return (MTLSampler)sampler; } } - extension(GraphicsPipeline graphicsPipeline) + extension(Shader shader) { - internal MTLGraphicsPipeline Metal() + internal MTLShader Metal() { - return (MTLGraphicsPipeline)graphicsPipeline; + return (MTLShader)shader; } } - extension(ComputePipeline computePipeline) + extension(SwapChain swapChain) { - internal MTLComputePipeline Metal() + internal MTLSwapChain Metal() { - return (MTLComputePipeline)computePipeline; + return (MTLSwapChain)swapChain; } } - extension(MeshShadingPipeline meshShadingPipeline) + extension(Texture texture) { - internal MTLMeshShadingPipeline Metal() + internal MTLTexture Metal() { - return (MTLMeshShadingPipeline)meshShadingPipeline; + return (MTLTexture)texture; } } - extension(QueryHeap queryHeap) + extension(TextureView textureView) { - internal MTLQueryHeap Metal() + internal MTLTextureView Metal() { - return (MTLQueryHeap)queryHeap; + return (MTLTextureView)textureView; + } + } + + 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 index 03322d0f..942f85e1 100644 --- a/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLBottomLevelAccelerationStructure.cs @@ -2,117 +2,132 @@ namespace Zenith.NET.Metal; -internal class MTLBottomLevelAccelerationStructure : BottomLevelAccelerationStructure +internal unsafe class MTLBottomLevelAccelerationStructure : BottomLevelAccelerationStructure { public MTLAccelerationStructure AccelerationStructure; - public unsafe MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, BottomLevelAccelerationStructureDesc desc, MTLCommandBuffer commandBuffer) : base(context, desc) + public MTLBottomLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) { - uint geometryCount = (uint)desc.Geometries.Length; + Transform = new(context, new() + { + SizeInBytes = (uint)(sizeof(MTLPackedFloat4x3) * desc.Geometries.Length), + Residency = MemoryResidency.CpuWriteOnly + }); + + MTL4PrimitiveAccelerationStructureDescriptor descriptor = Descriptor(desc); - TransformBuffer = new(context, new() + MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); + + context.Register(AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize)); + + Scratch = new(context, new() { - SizeInBytes = (uint)(sizeof(MTLPackedFloat4x3) * geometryCount), - StrideInBytes = (uint)sizeof(MTLPackedFloat4x3), - Flags = BufferUsageFlags.AccelerationStructure | BufferUsageFlags.MapWrite + SizeInBytes = (uint)sizes.BuildScratchBufferSize, + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly }); - MappedMemory mappedMemory = TransformBuffer.Map(); + commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + } + + 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)); + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override void SetResourceName(string name) + { + AccelerationStructure.Label = name; + } + + protected override void Destroy() + { + Context.Unregister(AccelerationStructure); + + Scratch.Dispose(); + Transform.Dispose(); + AccelerationStructure.Dispose(); + } - desc.Geometries.Select(static item => MTLFormats.Metal(item.Triangles.Transform)).ToArray().CopyTo(new Span((MTLPackedFloat4x3*)mappedMemory.Pointer, (int)geometryCount)); + private MTL4PrimitiveAccelerationStructureDescriptor Descriptor(BottomLevelAccelerationStructureDesc desc) + { + uint geometryCount = (uint)desc.Geometries.Length; - TransformBuffer.Unmap(); + nint pointer = Transform.Map(); - MTL4AccelerationStructureGeometryDescriptor[] geometryDescriptors = new MTL4AccelerationStructureGeometryDescriptor[geometryCount]; + MTLPackedFloat4x3* transforms = (MTLPackedFloat4x3*)pointer; + MTL4AccelerationStructureGeometryDescriptor[] geometries = 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.Triangles: - geometryDescriptors[i] = new MTL4AccelerationStructureTriangleGeometryDescriptor() + case RayTracingGeometryType.Triangle: + geometries[i] = new MTL4AccelerationStructureTriangleGeometryDescriptor() { VertexBuffer = new() { - BufferAddress = geometry.Triangles.VertexBuffer.Metal().Buffer.GpuAddress + geometry.Triangles.VertexOffsetInBytes, - Length = geometry.Triangles.VertexStrideInBytes * geometry.Triangles.VertexCount + BufferAddress = geometry.TriangleGeometry.VertexBuffer.Metal().Buffer.GpuAddress + geometry.TriangleGeometry.VertexOffsetInBytes, + Length = geometry.TriangleGeometry.VertexStrideInBytes * geometry.TriangleGeometry.VertexCount }, - VertexFormat = MTLFormats.Metal(geometry.Triangles.VertexFormat).AttributeFormat, - VertexStride = geometry.Triangles.VertexStrideInBytes, - IndexBuffer = geometry.Triangles.IndexBuffer is not null ? new() + VertexFormat = MTLFormats.Metal(geometry.TriangleGeometry.VertexFormat).AttributeFormat, + VertexStride = geometry.TriangleGeometry.VertexStrideInBytes, + IndexBuffer = geometry.TriangleGeometry.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 + 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.Triangles.IndexFormat), - TriangleCount = geometry.Triangles.IndexBuffer is not null ? geometry.Triangles.IndexCount / 3 : geometry.Triangles.VertexCount / 3, + IndexType = MTLFormats.Metal(geometry.TriangleGeometry.IndexFormat), + TriangleCount = geometry.TriangleGeometry.IndexBuffer is not null ? geometry.TriangleGeometry.IndexCount / 3 : geometry.TriangleGeometry.VertexCount / 3, TransformationMatrixBuffer = new() { - BufferAddress = TransformBuffer.GpuAddress + (uint)(sizeof(MTLPackedFloat4x3) * i), + BufferAddress = Transform.Buffer.GpuAddress + (uint)(sizeof(MTLPackedFloat4x3) * i), Length = (uint)sizeof(MTLPackedFloat4x3) }, - TransformationMatrixLayout = MTLMatrixLayout.RowMajor, - Opaque = geometry.Flags.HasFlag(RayTracingGeometryFlags.Opaque) + TransformationMatrixLayout = MTLMatrixLayout.ColumnMajor }; break; - case RayTracingGeometryType.AABBs: - geometryDescriptors[i] = new MTL4AccelerationStructureBoundingBoxGeometryDescriptor() + case RayTracingGeometryType.Aabb: + geometries[i] = new MTL4AccelerationStructureBoundingBoxGeometryDescriptor() { BoundingBoxBuffer = new() { - BufferAddress = geometry.AABBs.Buffer.Metal().Buffer.GpuAddress + geometry.AABBs.OffsetInBytes, - Length = geometry.AABBs.StrideInBytes * geometry.AABBs.Count + BufferAddress = geometry.AabbGeometry.Buffer.Metal().Buffer.GpuAddress + geometry.AabbGeometry.OffsetInBytes, + Length = geometry.AabbGeometry.StrideInBytes * geometry.AabbGeometry.Count }, - BoundingBoxStride = geometry.AABBs.StrideInBytes, - BoundingBoxCount = geometry.AABBs.Count, - Opaque = geometry.Flags.HasFlag(RayTracingGeometryFlags.Opaque) + BoundingBoxStride = geometry.AabbGeometry.StrideInBytes, + BoundingBoxCount = geometry.AabbGeometry.Count }; break; } - } - - MTL4PrimitiveAccelerationStructureDescriptor descriptor = new() - { - GeometryDescriptors = geometryDescriptors, - Usage = MTLFormats.Metal(desc.Flags) - }; - MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); + geometries[i].Opaque = geometry.IsOpaque; + } - AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize); - context.AddAllocation(AccelerationStructure); + Transform.Unmap(); - ScratchBuffer = new(context, new() + return 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(); + GeometryDescriptors = geometries, + Usage = MTLFormats.Metal(desc.BuildFlags) + }; } } diff --git a/sources/Zenith.NET.Metal/MTLBuffer.cs b/sources/Zenith.NET.Metal/MTLBuffer.cs index 2dae0e98..6a7ac36d 100644 --- a/sources/Zenith.NET.Metal/MTLBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLBuffer.cs @@ -4,20 +4,48 @@ 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); + 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 MTLHeap Heap { get; } + public MTLBuffer(MTLGraphicsContext context, BufferDesc desc, MtlBuffer buffer) : base(context, desc) + { + context.Register(Buffer = 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 MappedMemory Map() + public override ResourceHandle StorageReadOnlyHandle => View.StorageReadOnlyHandle; + + public override ResourceHandle StorageReadWriteHandle => View.StorageReadWriteHandle; + + public override nint GetNativeObject(NativeObjectType type) { - return new() { Pointer = Buffer.Contents(), SizeInBytes = Desc.SizeInBytes }; + return 0; + } + + public override nint Map() + { + return Buffer.Contents(); } public override void Unmap() @@ -31,8 +59,9 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Buffer.Dispose(); + Context.Unregister(Buffer); - Heap.Dispose(); + View.Dispose(); + Buffer.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLBufferView.cs b/sources/Zenith.NET.Metal/MTLBufferView.cs index c32ee139..4342fdc7 100644 --- a/sources/Zenith.NET.Metal/MTLBufferView.cs +++ b/sources/Zenith.NET.Metal/MTLBufferView.cs @@ -1,8 +1,24 @@ namespace Zenith.NET.Metal; -internal class MTLBufferView(MTLGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) +internal class MTLBufferView : BufferView { - public nuint GpuAddress = desc.Buffer.Metal().GpuAddress + desc.OffsetInBytes; + 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 { 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) { diff --git a/sources/Zenith.NET.Metal/MTLCapabilities.cs b/sources/Zenith.NET.Metal/MTLCapabilities.cs index f46a5354..89bd8281 100644 --- a/sources/Zenith.NET.Metal/MTLCapabilities.cs +++ b/sources/Zenith.NET.Metal/MTLCapabilities.cs @@ -8,5 +8,5 @@ internal class MTLCapabilities(MTLGraphicsContext context) : Capabilities public override bool RayTracingSupported { get; } = context.Device.SupportsRaytracingFromRender; - public override bool MeshShadingSupported { get; } = context.Device.SupportsFamily(MTLGPUFamily.Apple7) || context.Device.SupportsFamily(MTLGPUFamily.Mac2); + public override bool MeshShadingSupported { get; } = context.Device.SupportsFamily(MTLGPUFamily.Apple7); } diff --git a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs index 8f8e8601..6596081f 100644 --- a/sources/Zenith.NET.Metal/MTLCommandBuffer.cs +++ b/sources/Zenith.NET.Metal/MTLCommandBuffer.cs @@ -9,119 +9,137 @@ internal unsafe class MTLCommandBuffer : CommandBuffer public MTL4CommandBuffer CommandBuffer; - public MTLCommandBuffer(MTLGraphicsContext context, CommandQueue queue) : base(context, queue) - { - CommandAllocator = context.Device.MakeCommandAllocator(); - CommandBuffer = NSAutorelease.Own(context.Device.MakeCommandBuffer); + public MTL4ArgumentTable ArgumentTable; - CommandEncoder = new(context, CommandBuffer); - } + public MTL4RenderCommandEncoder? Render; - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + public MTL4ComputeCommandEncoder? Compute; - public MTLCommandEncoder CommandEncoder { get; } + private readonly Dictionary activeVisibilityIndices = []; + private readonly List beginVisibilityBindings = []; + private readonly List endVisibilityBindings = []; + private readonly List resolveTimestamps = []; - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) - { - MTLBuffer mtlSrc = src.Metal(); - MTLBuffer mtlDest = dest.Metal(); + private uint visibilityIndex; + private IndexBinding indexBinding; - CommandEncoder.Compute?.Copy(mtlSrc.Buffer, srcOffsetInBytes, mtlDest.Buffer, destOffsetInBytes, sizeInBytes); + private GraphicsPipeline? todoGraphicsPipeline; + private ComputePipeline? todoComputePipeline; + private MeshShadingPipeline? todoMeshShadingPipeline; + private Viewport[]? todoViewports; + private Scissor[]? todoScissors; + private Vector4? todoBlendConstant; + private uint? todoStencilReference; - 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) + public MTLCommandBuffer(MTLGraphicsContext context, MTLCommandQueue queue) : base(context, queue) { - MTLBuffer mtlSrc = src.Metal(); - MTLTexture mtlDest = dest.Metal(); - - (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(mtlDest.Desc.Format, destExtent.Width, destExtent.Height); + CommandAllocator = context.Device.MakeCommandAllocator(); + CommandBuffer = NSAutorelease.Own(context.Device.MakeCommandBuffer); - uint formatSizeInBytes = ZenithHelper.SizeInBytes(mtlDest.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); + MTL4ArgumentTableDescriptor descriptor = new() + { + MaxBufferBindCount = 16, + SupportAttributeStrides = true + }; - CommandEncoder.Compute?.Copy(mtlSrc.Buffer, - srcOffsetInBytes, - sliceRowPitchInBytes, - sliceDepthPitchInBytes, - new(destExtent.Width, destExtent.Height, destExtent.Depth), - mtlDest.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlDest.Desc, destSlice), - destSlice.MipLevel, - new(destOffset.X, destOffset.Y, destOffset.Z)); + ArgumentTable = context.Device.MakeArgumentTable(descriptor, out NSError error); + error.Success(); - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); + Visibility = new(context, new() + { + SizeInBytes = sizeof(ulong) * 1024, + Residency = MemoryResidency.CpuReadOnly + }); } - protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent) - { - MTLTexture mtlSrc = src.Metal(); - MTLTexture mtlDest = dest.Metal(); + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlSrc.Desc, srcSlice), - srcSlice.MipLevel, - new(srcOffset.X, srcOffset.Y, srcOffset.Z), - new(extent.Width, extent.Height, extent.Depth), - mtlDest.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlDest.Desc, destSlice), - destSlice.MipLevel, - new(destOffset.X, destOffset.Y, destOffset.Z)); + public MTLBuffer Visibility { get; } - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } - protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes) + protected override void BarrierImpl(BarrierStages before, BarrierStages after) { - MTLTexture mtlSrc = src.Metal(); - MTLBuffer mtlDest = dest.Metal(); - - (_, _, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(mtlSrc.Desc.Format, srcExtent.Width, srcExtent.Height); + Render?.BarrierAfterEncoderStages(MTLFormats.Metal(before), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); + Compute?.BarrierAfterEncoderStages(MTLFormats.Metal(before), MTLFormats.Metal(after), MTL4VisibilityOptions.Device); + } - uint formatSizeInBytes = ZenithHelper.SizeInBytes(mtlSrc.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); + protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout before, TextureLayout after) + { + } - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlSrc.Desc, srcSlice), - srcSlice.MipLevel, - new(srcOffset.X, srcOffset.Y, srcOffset.Z), - new(srcExtent.Width, srcExtent.Height, srcExtent.Depth), - mtlDest.Buffer, - destOffsetInBytes, - sliceRowPitchInBytes, - sliceDepthPitchInBytes); + 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); + } - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); + 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, + 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 ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + protected override void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - MTLTexture mtlSrc = src.Metal(); - MTLTexture mtlDest = dest.Metal(); + 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)); + } - CommandEncoder.Compute?.Copy(mtlSrc.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlSrc.Desc, srcSlice), - srcSlice.MipLevel, - mtlDest.Texture, - ZenithHelper.FlattenArrayLayerIndex(mtlDest.Desc, destSlice), - destSlice.MipLevel, - 1, - 1); + 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, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, + new(srcOffset.X, srcOffset.Y, srcOffset.Z), + new(srcExtent.Width, srcExtent.Height, srcExtent.Depth), + dst.Metal().Buffer, + dstOffsetInBytes, + dstRowStrideInBytes, + dstSliceStrideInBytes); + } - CommandEncoder.Compute?.BarrierAfterEncoderStages(MTLStages.Blit, MTLStages.Blit | MTLStages.Dispatch, MTL4VisibilityOptions.Device); + protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) + { + Compute?.Copy(src.Metal().Texture, + srcSubresource.ArrayLayer, + srcSubresource.MipLevel, + dst.Metal().Texture, + dstSubresource.ArrayLayer, + dstSubresource.MipLevel, + 1, + 1); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) { - return new MTLBottomLevelAccelerationStructure(Context, desc, this); + return new MTLBottomLevelAccelerationStructure(Context, this, desc); } protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) { - return new MTLTopLevelAccelerationStructure(Context, desc, this); + return new MTLTopLevelAccelerationStructure(Context, this, desc); + } + + protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) + { + accelerationStructure.Metal().Update(this, newDesc); } protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) @@ -129,254 +147,387 @@ 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(); + EndComputeEncoding(); - bool clearColor = clearValue.Flags.HasFlag(ClearFlags.Color); - bool clearDepth = clearValue.Flags.HasFlag(ClearFlags.Depth); - bool clearStencil = clearValue.Flags.HasFlag(ClearFlags.Stencil); + MTL4RenderPassDescriptor descriptor = new() { VisibilityResultBuffer = Visibility.Buffer }; - for (uint i = 0; i < mtlFrameBuffer.ColorAttachmentCount; i++) + for (int i = 0; i < colorAttachments.Length; i++) { - MTLRenderPassColorAttachmentDescriptor colorAttachment = mtlFrameBuffer.Descriptor.ColorAttachments[i]; + ColorAttachment attachment = colorAttachments[i]; - colorAttachment.LoadAction = MTLLoadAction.Load; + MTLTexture texture = attachment.Texture.Metal(); - if (clearColor) + descriptor.ColorAttachments[(uint)i] = new() { - colorAttachment.LoadAction = MTLLoadAction.Clear; - - Vector4 color = clearValue.ColorValues[i]; - - colorAttachment.ClearColor = new() - { - Red = color.X, - Green = color.Y, - Blue = color.Z, - Alpha = color.W - }; - } + 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) + }; } - if (mtlFrameBuffer.HasDepthStencilAttachment) + if (depthStencilAttachment.HasValue) { - MTLRenderPassDepthAttachmentDescriptor depthAttachment = mtlFrameBuffer.Descriptor.DepthAttachment; + DepthStencilAttachment attachment = depthStencilAttachment.Value; - if (!depthAttachment.Texture.IsNull) - { - depthAttachment.LoadAction = MTLLoadAction.Load; + MTLTexture texture = attachment.Texture.Metal(); - if (clearDepth) + if (ZenithHelper.HasDepth(texture.Desc.Format)) + { + descriptor.DepthAttachment = new() { - depthAttachment.LoadAction = MTLLoadAction.Clear; - depthAttachment.ClearDepth = clearValue.Depth; - } + Texture = texture.Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = MTLFormats.Metal(attachment.DepthLoadOp), + StoreAction = MTLFormats.Metal(attachment.DepthStoreOp), + ClearDepth = attachment.ClearDepth + }; } - MTLRenderPassStencilAttachmentDescriptor stencilAttachment = mtlFrameBuffer.Descriptor.StencilAttachment; - - if (!stencilAttachment.Texture.IsNull) + if (ZenithHelper.HasStencil(texture.Desc.Format)) { - stencilAttachment.LoadAction = MTLLoadAction.Load; - - if (clearStencil) + descriptor.StencilAttachment = new() { - stencilAttachment.LoadAction = MTLLoadAction.Clear; - stencilAttachment.ClearStencil = clearValue.Stencil; - } + Texture = texture.Texture, + Level = attachment.Subresource.MipLevel, + Slice = attachment.Subresource.ArrayLayer, + LoadAction = MTLFormats.Metal(attachment.StencilLoadOp), + StoreAction = MTLFormats.Metal(attachment.StencilStoreOp), + ClearStencil = attachment.ClearStencil + }; } } - CommandEncoder.BeginRenderPass(mtlFrameBuffer.Descriptor); + BeginRenderEncoding(descriptor); + } + + protected override void EndRenderPassImpl() + { + EndRenderEncoding(); + BeginComputeEncoding(); + } + + protected override void SetPipelineImpl(GraphicsPipeline pipeline) + { + if (Render is null) + { + todoGraphicsPipeline = pipeline; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + } + 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); + } } - protected override void EndRenderPassImpl(FrameBuffer frameBuffer) + protected override void SetPipelineImpl(ComputePipeline pipeline) { - CommandEncoder.EndRenderPass(); + if (Compute is null) + { + todoGraphicsPipeline = null; + todoComputePipeline = pipeline; + todoMeshShadingPipeline = null; + } + else + { + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + + Compute.SetComputePipelineState(pipeline.Metal().ComputePipelineState); + } } - protected override void SetScissorsImpl(Scissor[] scissors) + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) { - CommandEncoder.SetScissors(scissors); + if (Render is null) + { + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = 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); + } } - protected override void SetViewportsImpl(Viewport[] viewports) + protected override void SetViewportsImpl(ReadOnlySpan viewports) { - CommandEncoder.SetViewports(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) + protected override void SetScissorsImpl(ReadOnlySpan scissors) { - CommandEncoder.SetPipeline(pipeline); + 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 SetPipelineImpl(ComputePipeline pipeline) + protected override void SetBlendConstantImpl(Vector4 blendConstant) { - CommandEncoder.SetPipeline(pipeline); + if (Render is null) + { + todoBlendConstant = blendConstant; + } + else + { + Render.SetBlendColor(blendConstant.X, blendConstant.Y, blendConstant.Z, blendConstant.W); + } } - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + protected override void SetStencilReferenceImpl(uint stencilReference) { - CommandEncoder.SetPipeline(pipeline); + if (Render is null) + { + todoStencilReference = stencilReference; + } + else + { + Render.SetStencilReferenceValue(stencilReference); + } } - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { - CommandEncoder.SetVertexBuffer(buffer, offsetInBytes, index); + 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 format) + protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - CommandEncoder.SetIndexBuffer(buffer, offsetInBytes, format); + indexBinding = new(MTLFormats.Metal(indexFormat), + buffer.Metal().Buffer.GpuAddress + offsetInBytes, + indexFormat is IndexFormat.UInt16 ? 2u : 4u, + buffer.Desc.SizeInBytes - offsetInBytes); } - protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) + protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) { - CommandEncoder.SetResourceTable(resourceTable); + ArgumentTable.SetAddress(buffer.Metal().Buffer.GpuAddress + offsetInBytes, 0); + + Render?.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Object | MTLRenderStages.Mesh); + Compute?.SetArgumentTable(ArgumentTable); } 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); + Render?.DrawPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, firstVertex, vertexCount, instanceCount, firstInstance); } protected override void DrawIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - CommandEncoder.Bind(); - - nuint indirectGpuAddress = indirectBuffer.Metal().GpuAddress + offsetInBytes; + nuint address = indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes; for (uint i = 0; i < drawCount; i++) { - CommandEncoder.Render?.DrawPrimitives(CommandEncoder.PrimitiveType, indirectGpuAddress + ((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) { - CommandEncoder.Bind(); - - CommandEncoder.Render?.DrawIndexedPrimitives(CommandEncoder.PrimitiveType, - indexCount, - CommandEncoder.IndexType, - CommandEncoder.IndexBuffer + (CommandEncoder.IndexStrideInBytes * firstIndex), - CommandEncoder.IndexSizeInBytes - (CommandEncoder.IndexStrideInBytes * firstIndex), - instanceCount, - vertexOffset, - firstInstance); + Render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, + indexCount, + indexBinding.Type, + indexBinding.Address + (indexBinding.SizeInBytes * firstIndex), + indexBinding.LengthInBytes, + instanceCount, + vertexOffset, + firstInstance); } protected override void DrawIndexedIndirectImpl(GraphicsPipeline pipeline, Buffer indirectBuffer, uint offsetInBytes, uint drawCount) { - CommandEncoder.Bind(); - - nuint indirectGpuAddress = indirectBuffer.Metal().GpuAddress + offsetInBytes; + nuint address = indirectBuffer.Metal().Buffer.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)); + Render?.DrawIndexedPrimitives(MTLFormats.Metal(pipeline.Desc.PrimitiveTopology).Type, + indexBinding.Type, + indexBinding.Address, + indexBinding.LengthInBytes, + address + (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); + Compute?.DispatchThreadgroups(new MTLSize(groupCountX, groupCountY, groupCountZ), pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); } 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); + Compute?.DispatchThreadgroups(indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes, pipeline.Metal().ComputePipelineState.RequiredThreadsPerThreadgroup); } 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); + 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) { - CommandEncoder.Bind(); - - nuint indirectGpuAddress = indirectBuffer.Metal().GpuAddress + offsetInBytes; + nuint address = indirectBuffer.Metal().Buffer.GpuAddress + offsetInBytes; for (uint i = 0; i < dispatchCount; i++) { - CommandEncoder.Render?.DrawMeshThreadgroups(indirectGpuAddress + ((uint)sizeof(IndirectDispatchMeshArgs) * i), - CommandEncoder.AmplificationThreadGroupSize, - CommandEncoder.MeshThreadGroupSize); + Render?.DrawMeshThreadgroups(address + (uint)(sizeof(IndirectDispatchMeshArgs) * i), + pipeline.Metal().RenderPipelineState.RequiredThreadsPerObjectThreadgroup, + pipeline.Metal().RenderPipelineState.RequiredThreadsPerMeshThreadgroup); } } protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { - CommandEncoder.BeginQuery(queryHeap, index); + MTLQueryHeap mtlQueryHeap = queryHeap.Metal(); + + uint scratchIndex = visibilityIndex++; + + activeVisibilityIndices.Add(new(mtlQueryHeap, index), scratchIndex); + + if (Render is null) + { + beginVisibilityBindings.Add(new(mtlQueryHeap, index, scratchIndex)); + } + else + { + Render.SetVisibilityResultMode(MTLFormats.Metal(mtlQueryHeap.Desc.Type), sizeof(ulong) * scratchIndex); + } } protected override void EndQueryImpl(QueryHeap queryHeap, uint index) { - CommandEncoder.EndQuery(queryHeap, 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) { 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); + 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) { - CommandEncoder.BeginDebugEvent(label); + Render?.PushDebugGroup(label); + Compute?.PushDebugGroup(label); } protected override void EndDebugEventImpl() { - CommandEncoder.EndDebugEvent(); + Render?.PopDebugGroup(); + Compute?.PopDebugGroup(); } protected override void InsertDebugMarkerImpl(string label) { - CommandEncoder.InsertDebugMarker(label); + Render?.InsertDebugSignpost(label); + Compute?.InsertDebugSignpost(label); } protected override void BeginImpl() { CommandBuffer.BeginCommandBuffer(CommandAllocator); - CommandBuffer.UseResidencySet(Context.ResidencySet); - - CommandEncoder.Begin(); + BeginComputeEncoding(); } protected override void EndImpl() { - CommandEncoder.End(); + EndRenderEncoding(); + EndComputeEncoding(); CommandBuffer.EndCommandBuffer(); } protected override void ResetImpl() { + activeVisibilityIndices.Clear(); + beginVisibilityBindings.Clear(); + endVisibilityBindings.Clear(); + resolveTimestamps.Clear(); + + visibilityIndex = 0; + indexBinding = default; + + todoScissors = null; + todoViewports = null; + todoGraphicsPipeline = null; + todoComputePipeline = null; + todoMeshShadingPipeline = null; + todoStencilReference = null; + todoBlendConstant = null; + CommandAllocator.Reset(); } @@ -389,9 +540,159 @@ protected override void Destroy() { base.Destroy(); - CommandEncoder.Dispose(); - + Visibility.Dispose(); + ArgumentTable.Dispose(); CommandBuffer.Dispose(); CommandAllocator.Dispose(); } -} + + private void BeginRenderEncoding(MTL4RenderPassDescriptor descriptor) + { + Render = NSAutorelease.Own(CommandBuffer.MakeRenderCommandEncoder, descriptor); + Render.SetArgumentTable(ArgumentTable, MTLRenderStages.Vertex | MTLRenderStages.Fragment | MTLRenderStages.Object | MTLRenderStages.Mesh); + + if (todoGraphicsPipeline is not null) + { + SetPipeline(todoGraphicsPipeline); + + todoGraphicsPipeline = null; + } + + if (todoMeshShadingPipeline is not null) + { + SetPipeline(todoMeshShadingPipeline); + + todoMeshShadingPipeline = null; + } + + if (todoViewports is not null) + { + SetViewports(todoViewports); + + todoViewports = null; + } + + if (todoScissors is not null) + { + SetScissors(todoScissors); + + todoScissors = null; + } + + if (todoBlendConstant is not null) + { + SetBlendConstant(todoBlendConstant.Value); + + todoBlendConstant = null; + } + + if (todoStencilReference is not null) + { + SetStencilReference(todoStencilReference.Value); + + todoStencilReference = null; + } + + foreach (VisibilityBinding visibilityBinding in beginVisibilityBindings) + { + Render.SetVisibilityResultMode(MTLFormats.Metal(visibilityBinding.QueryHeap.Desc.Type), sizeof(ulong) * visibilityBinding.ScratchIndex); + } + beginVisibilityBindings.Clear(); + } + + private void EndRenderEncoding() + { + if (Render is null) + { + return; + } + + Render.BarrierAfterStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); + Render.EndEncoding(); + Render.Dispose(); + Render = null; + + ResolveTimestamps(); + } + + private void BeginComputeEncoding() + { + Compute = NSAutorelease.Own(CommandBuffer.MakeComputeCommandEncoder); + Compute.SetArgumentTable(ArgumentTable); + + if (todoComputePipeline is not null) + { + SetPipeline(todoComputePipeline); + + 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() + { + if (Compute is null) + { + return; + } + + Compute.BarrierAfterStages(MTLStages.All, MTLStages.All, MTL4VisibilityOptions.Device); + Compute.EndEncoding(); + Compute.Dispose(); + Compute = null; + + ResolveTimestamps(); + } + + 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; + + public nuint Address = address; + + public uint SizeInBytes = sizeInBytes; + + public uint LengthInBytes = lengthInBytes; + } + + private struct VisibilityKey(MTLQueryHeap queryHeap, uint index) + { + public MTLQueryHeap QueryHeap = queryHeap; + + public uint Index = 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) + { + public MTLQueryHeap QueryHeap = queryHeap; + + public uint Index = index; + } +} \ No newline at end of file diff --git a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs b/sources/Zenith.NET.Metal/MTLCommandEncoder.cs deleted file mode 100644 index e0cc86ee..00000000 --- a/sources/Zenith.NET.Metal/MTLCommandEncoder.cs +++ /dev/null @@ -1,363 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLCommandEncoder : GraphicsResource -{ - 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 = 16, - MaxTextureBindCount = 16, - MaxSamplerStateBindCount = 16, - 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(Scissor[] scissors) - { - if (Render is null) - { - todoScissors = [.. scissors]; - } - else - { - MTLScissorRect[] mtlScissors = [.. scissors.Select(static item => new MTLScissorRect((uint)item.X, (uint)item.Y, item.Width, item.Height))]; - - Render.SetScissorRects(mtlScissors); - } - } - - public void SetViewports(Viewport[] viewports) - { - if (Render is null) - { - todoViewports = [.. viewports]; - } - else - { - MTLViewport[] mtlViewports = [.. viewports.Select(static item => new MTLViewport(item.X, item.Y, item.Width, item.Height, item.MinDepth, item.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 SetResourceTable(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); - } - - 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)); - - 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.Metal/MTLCommandQueue.cs b/sources/Zenith.NET.Metal/MTLCommandQueue.cs index b7b7f853..ece05fe9 100644 --- a/sources/Zenith.NET.Metal/MTLCommandQueue.cs +++ b/sources/Zenith.NET.Metal/MTLCommandQueue.cs @@ -2,23 +2,47 @@ namespace Zenith.NET.Metal; -internal class MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type, MTL4CommandQueue queue) : CommandQueue(context, type) +internal class MTLCommandQueue : CommandQueue { - private readonly MTLFence fence = new(context); + public MTL4CommandQueue CommandQueue; + + public MTLCommandQueue(MTLGraphicsContext context, CommandQueueType type) : base(context, type) + { + CommandQueue = context.Device.MakeMTL4CommandQueue(); + 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 CommandBuffer CreateCommandBuffer() { - return new MTLCommandBuffer(context, this); + return new MTLCommandBuffer(Context, this); } - protected override void WaitIdleImpl() + protected override double GetTimestampPeriod(out uint validBits) { - fence.Wait(queue); + validBits = 64; + + return 1_000_000_000.0 / Context.Device.QueryTimestampFrequency(); } - protected override void SubmitImpl(CommandBuffer commandBuffer) + protected override void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer) { - queue.Commit([commandBuffer.Metal().CommandBuffer]); + foreach (TimelineValue wait in waits) + { + CommandQueue.WaitForEvent(wait.Timeline.Metal().Event, wait.Value); + } + + CommandQueue.Commit([commandBuffer.Metal().CommandBuffer]); } protected override void SetResourceName(string name) @@ -29,6 +53,7 @@ protected override void Destroy() { base.Destroy(); - fence.Dispose(); + CommandQueue.RemoveResidencySet(Context.ResidencySet); + CommandQueue.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLComputePipeline.cs b/sources/Zenith.NET.Metal/MTLComputePipeline.cs index 516d2627..2af36f6e 100644 --- a/sources/Zenith.NET.Metal/MTLComputePipeline.cs +++ b/sources/Zenith.NET.Metal/MTLComputePipeline.cs @@ -10,14 +10,19 @@ public MTLComputePipeline(MTLGraphicsContext context, ComputePipelineDesc desc) { MTL4ComputePipelineDescriptor descriptor = new() { - ComputeFunctionDescriptor = desc.Compute.Metal().Descriptor, - RequiredThreadsPerThreadgroup = new(desc.ThreadGroupSizeX, desc.ThreadGroupSizeY, desc.ThreadGroupSizeZ) + 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) { } 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 index 56a7f965..359b1283 100644 --- a/sources/Zenith.NET.Metal/MTLFormats.cs +++ b/sources/Zenith.NET.Metal/MTLFormats.cs @@ -3,208 +3,35 @@ namespace Zenith.NET.Metal; -internal static unsafe class MTLFormats +internal static class MTLFormats { - public static MTLResourceOptions Metal(BufferUsageFlags bufferUsageFlags) + public static MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - MTLResourceOptions result = MTLResourceOptions.HazardTrackingModeUntracked; - - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead) || bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - result |= MTLResourceOptions.StorageModeShared; + MTLAccelerationStructureUsage result = default; - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) - { - result |= MTLResourceOptions.CPUCacheModeWriteCombined; - } - } - else + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - result |= MTLResourceOptions.StorageModePrivate; + result |= MTLAccelerationStructureUsage.Refit; } - 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)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) { - result |= MTLTextureUsage.RenderTarget; + result |= MTLAccelerationStructureUsage.PreferFastIntersection; } - if (textureUsageFlags.HasFlag(TextureUsageFlags.ShaderResource)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) { - result |= MTLTextureUsage.ShaderRead; + result |= MTLAccelerationStructureUsage.PreferFastBuild; } - if (textureUsageFlags.HasFlag(TextureUsageFlags.UnorderedAccess)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) { - result |= MTLTextureUsage.ShaderWrite; + result |= MTLAccelerationStructureUsage.MinimizeMemory; } 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 @@ -213,98 +40,59 @@ public static MTLSamplerAddressMode Metal(AddressMode addressMode) AddressMode.Mirror => MTLSamplerAddressMode.MirrorRepeat, AddressMode.Clamp => MTLSamplerAddressMode.ClampToEdge, AddressMode.Border => MTLSamplerAddressMode.ClampToBorderColor, - _ => MTLSamplerAddressMode.ClampToEdge + _ => default }; } - public static MTLCompareFunction Metal(ComparisonFunc comparisonFunc) + public static MTLStages Metal(BarrierStages barrierStages) { - 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 - }; - } + MTLStages result = default; - public static MTLSamplerBorderColor Metal(BorderColor borderColor) - { - return borderColor switch + if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - 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, + result |= MTLStages.Vertex | MTLStages.Object | MTLStages.Mesh; + } - PrimitiveTopology.LineList or - PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) + { + result |= MTLStages.Fragment; + } - PrimitiveTopology.TriangleList or - PrimitiveTopology.TriangleStrip => MTLPrimitiveTopologyClass.Triangle, + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) + { + result |= MTLStages.Dispatch; + } - _ => 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 - } - ); - } + if (barrierStages.HasFlag(BarrierStages.Copy) || barrierStages.HasFlag(BarrierStages.Resolve)) + { + result |= MTLStages.Blit; + } - public static MTLStencilOperation Metal(StencilOp stencilOp) - { - return stencilOp switch + if (barrierStages.HasFlag(BarrierStages.All)) { - 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 - }; + result = MTLStages.All; + } + + return result; } - public static MTLBlendFactor Metal(Blend blend) + public static MTLBlendFactor Metal(BlendFactor blendFactor) { - 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 + 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 }; } @@ -317,30 +105,41 @@ public static MTLBlendOperation Metal(BlendOp blendOp) BlendOp.ReverseSubtract => MTLBlendOperation.ReverseSubtract, BlendOp.Min => MTLBlendOperation.Min, BlendOp.Max => MTLBlendOperation.Max, - _ => MTLBlendOperation.Add + _ => 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(ColorComponentFlags colorComponentFlags) + public static MTLColorWriteMask Metal(ColorWrites colorWrites) { - MTLColorWriteMask result = MTLColorWriteMask.None; + MTLColorWriteMask result = default; - if (colorComponentFlags.HasFlag(ColorComponentFlags.Red)) + if (colorWrites.HasFlag(ColorWrites.Red)) { result |= MTLColorWriteMask.Red; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Green)) + if (colorWrites.HasFlag(ColorWrites.Green)) { result |= MTLColorWriteMask.Green; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Blue)) + if (colorWrites.HasFlag(ColorWrites.Blue)) { result |= MTLColorWriteMask.Blue; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Alpha)) + if (colorWrites.HasFlag(ColorWrites.Alpha)) { result |= MTLColorWriteMask.Alpha; } @@ -348,6 +147,33 @@ public static MTLColorWriteMask Metal(ColorComponentFlags colorComponentFlags) 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 @@ -355,30 +181,34 @@ public static MTLVertexFormat Metal(ElementFormat elementFormat) 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.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.UShort1Normalized => MTLVertexFormat.UShortNormalized, - ElementFormat.UShort2Normalized => MTLVertexFormat.UShort2Normalized, - ElementFormat.UShort4Normalized => MTLVertexFormat.UShort4Normalized, - ElementFormat.Short1Normalized => MTLVertexFormat.ShortNormalized, - ElementFormat.Short2Normalized => MTLVertexFormat.Short2Normalized, - ElementFormat.Short4Normalized => MTLVertexFormat.Short4Normalized, + 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, @@ -393,33 +223,33 @@ public static MTLVertexFormat Metal(ElementFormat elementFormat) 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 + _ => default }; } - public static MTLCullMode Metal(CullMode cullMode) + public static MTLTriangleFillMode Metal(FillMode fillMode) { - return cullMode switch + return fillMode switch { - CullMode.None => MTLCullMode.None, - CullMode.Front => MTLCullMode.Front, - CullMode.Back => MTLCullMode.Back, - _ => MTLCullMode.None + FillMode.Solid => MTLTriangleFillMode.Fill, + FillMode.Wireframe => MTLTriangleFillMode.Lines, + _ => default }; } - public static MTLTriangleFillMode Metal(FillMode fillMode) + public static (MTLSamplerMinMagFilter MinMagFilter, MTLSamplerMipFilter MipFilter) Metal(FilterMode filterMode) { - return fillMode switch + return filterMode switch { - FillMode.Solid => MTLTriangleFillMode.Fill, - FillMode.Wireframe => MTLTriangleFillMode.Lines, - _ => MTLTriangleFillMode.Fill + FilterMode.Point => (MTLSamplerMinMagFilter.Nearest, MTLSamplerMipFilter.Nearest), + FilterMode.Linear => (MTLSamplerMinMagFilter.Linear, MTLSamplerMipFilter.Linear), + _ => default }; } @@ -429,7 +259,7 @@ public static MTLWinding Metal(FrontFace frontFace) { FrontFace.CounterClockwise => MTLWinding.CounterClockwise, FrontFace.Clockwise => MTLWinding.Clockwise, - _ => MTLWinding.Clockwise + _ => default }; } @@ -439,93 +269,340 @@ public static MTLIndexType Metal(IndexFormat indexFormat) { IndexFormat.UInt16 => MTLIndexType.UInt16, IndexFormat.UInt32 => MTLIndexType.UInt32, - _ => MTLIndexType.UInt16 + _ => default }; } - public static MTLVisibilityResultMode Metal(QueryType queryType) + public static MTLLoadAction Metal(LoadOp loadOp) { - return queryType switch + return loadOp switch { - QueryType.Occlusion => MTLVisibilityResultMode.Counting, - QueryType.BinaryOcclusion => MTLVisibilityResultMode.Boolean, - _ => MTLVisibilityResultMode.Disabled + LoadOp.Load => MTLLoadAction.Load, + LoadOp.Clear => MTLLoadAction.Clear, + LoadOp.DontCare => MTLLoadAction.DontCare, + _ => default }; } public static MTLPackedFloat4x3 Metal(Matrix4x4 matrix4x4) { - MTLPackedFloat4x3 result; + 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)); + } - float* pResult = (float*)&result; + 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 + }; + } + + 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 + }, + 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 + } + ); + } - pResult[0] = matrix4x4.M11; - pResult[1] = matrix4x4.M21; - pResult[2] = matrix4x4.M31; - pResult[3] = matrix4x4.M41; + public static (MTLPrimitiveTopologyClass TopologyClass, MTLPrimitiveType Type) Metal(PrimitiveTopology primitiveTopology) + { + return + ( + primitiveTopology switch + { + PrimitiveTopology.PointList => MTLPrimitiveTopologyClass.Point, - pResult[4] = matrix4x4.M12; - pResult[5] = matrix4x4.M22; - pResult[6] = matrix4x4.M32; - pResult[7] = matrix4x4.M42; + PrimitiveTopology.LineList or + PrimitiveTopology.LineStrip => MTLPrimitiveTopologyClass.Line, - pResult[8] = matrix4x4.M13; - pResult[9] = matrix4x4.M23; - pResult[10] = matrix4x4.M33; - pResult[11] = matrix4x4.M43; + 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 MTLAccelerationStructureUsage Metal(AccelerationStructureBuildFlags accelerationStructureBuildFlags) + public static MTLVisibilityResultMode Metal(QueryType queryType) + { + return queryType switch + { + QueryType.Occlusion => MTLVisibilityResultMode.Counting, + QueryType.BinaryOcclusion => MTLVisibilityResultMode.Boolean, + _ => default + }; + } + + public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) { - MTLAccelerationStructureUsage result = MTLAccelerationStructureUsage.None; + MTLAccelerationStructureInstanceOptions result = default; - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate) || accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PerformUpdate)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { - result |= MTLAccelerationStructureUsage.Refit; + result |= MTLAccelerationStructureInstanceOptions.TriangleFrontFacingWindingCounterClockwise; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) { - result |= MTLAccelerationStructureUsage.PreferFastIntersection; + result |= MTLAccelerationStructureInstanceOptions.DisableTriangleCulling; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) { - result |= MTLAccelerationStructureUsage.PreferFastBuild; + result |= MTLAccelerationStructureInstanceOptions.Opaque; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) { - result |= MTLAccelerationStructureUsage.MinimizeMemory; + result |= MTLAccelerationStructureInstanceOptions.NonOpaque; } return result; } - public static MTLAccelerationStructureInstanceOptions Metal(RayTracingInstanceFlags rayTracingInstanceFlags) + public static nuint Metal(SampleCount sampleCount) { - MTLAccelerationStructureInstanceOptions result = MTLAccelerationStructureInstanceOptions.None; + return sampleCount switch + { + SampleCount.Count1 => 1, + SampleCount.Count2 => 2, + SampleCount.Count4 => 4, + SampleCount.Count8 => 8, + SampleCount.Count16 => 16, + SampleCount.Count32 => 32, + _ => default + }; + } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleCullDisable)) + public static MTLStencilOperation Metal(StencilOp stencilOp) + { + return stencilOp switch { - result |= MTLAccelerationStructureInstanceOptions.DisableTriangleCulling; - } + 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 + }; + } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleFrontCounterClockwise)) + public static MTLStoreAction Metal(StoreOp storeOp) + { + return storeOp switch { - result |= MTLAccelerationStructureInstanceOptions.TriangleFrontFacingWindingCounterClockwise; + StoreOp.Store => MTLStoreAction.Store, + StoreOp.DontCare => MTLStoreAction.DontCare, + _ => default + }; + } + + public static MTLTextureType Metal(TextureType textureType, SampleCount sampleCount) + { + 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 MTLTextureUsage Metal(TextureUsages textureUsages) + { + MTLTextureUsage result = default; + + if (textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= MTLTextureUsage.ShaderRead; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + if (textureUsages.HasFlag(TextureUsages.Storage)) { - result |= MTLAccelerationStructureInstanceOptions.Opaque; + result |= MTLTextureUsage.ShaderWrite; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNoOpaque)) + if (textureUsages.HasFlag(TextureUsages.ColorAttachment) || textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { - result |= MTLAccelerationStructureInstanceOptions.NonOpaque; + result |= MTLTextureUsage.RenderTarget; } return result; 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 bdd25114..f2ecdd4d 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -2,44 +2,47 @@ 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; + private readonly Lock @lock = new(); + + public MTLDevice Device = 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) + public void Register(MTLAllocation allocation) { + using Lock.Scope _ = @lock.EnterScope(); + ResidencySet.AddAllocation(allocation); ResidencySet.Commit(); } - public void RemoveAllocation(MTLAllocation allocation) + public void Unregister(MTLAllocation allocation) { + using Lock.Scope _ = @lock.EnterScope(); + ResidencySet.RemoveAllocation(allocation); ResidencySet.Commit(); } + public override nint GetNativeObject(NativeObjectType type) + { + 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 transferQueue, out ValidationLayer? validationLayer) { - Device = MTLDevice.CreateSystemDefaultDevice(); - if (!Device.SupportsFamily(MTLGPUFamily.Metal4)) { - throw new NotSupportedException("Metal 4 is not supported on system default device."); + throw new NotSupportedException("This device does not support Metal 4.0 or higher."); } Compiler = Device.MakeCompiler(new(), out NSError error); @@ -48,18 +51,10 @@ protected override void Initialize(bool useValidationLayer, ResidencySet = Device.MakeResidencySet(new(), out error); error.Success(); - GraphicsQueue = Device.MakeMTL4CommandQueue(); - ComputeQueue = Device.MakeMTL4CommandQueue(); - CopyQueue = Device.MakeMTL4CommandQueue(); - - GraphicsQueue.AddResidencySet(ResidencySet); - ComputeQueue.AddResidencySet(ResidencySet); - CopyQueue.AddResidencySet(ResidencySet); - 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); + graphicsQueue = new MTLCommandQueue(this, CommandQueueType.Graphics); + computeQueue = new MTLCommandQueue(this, CommandQueueType.Compute); + transferQueue = new MTLCommandQueue(this, CommandQueueType.Transfer); validationLayer = null; } @@ -68,14 +63,23 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) return new MTLSwapChain(this, desc); } - protected override FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc) + protected override Heap CreateHeapImpl(HeapDesc desc) { - return new MTLFrameBuffer(this, desc); + return new MTLHeap(this, desc); } - protected override Shader CreateShaderImpl(ShaderDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - return new MTLShader(this, desc); + MTLSizeAndAlign sizeAndAlign = Device.HeapBufferSizeAndAlign(desc.SizeInBytes, MTLFormats.Metal(desc.Residency)); + + return new(sizeAndAlign.Size, sizeAndAlign.Align); + } + + protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) + { + MTLSizeAndAlign sizeAndAlign = Device.HeapTextureSizeAndAlign(MTLTexture.Descriptor(desc)); + + return new(sizeAndAlign.Size, sizeAndAlign.Align); } protected override Buffer CreateBufferImpl(BufferDesc desc) @@ -93,6 +97,16 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new MTLTexture(this, desc); } + protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) + { + 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) { return new MTLTextureView(this, desc); @@ -103,14 +117,9 @@ 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) + protected override Shader CreateShaderImpl(ShaderDesc desc) { - return new MTLResourceTable(this, desc); + return new MTLShader(this, desc); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) @@ -137,17 +146,8 @@ 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 index 86fa35ca..6d345721 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsPipeline.cs @@ -4,111 +4,110 @@ namespace Zenith.NET.Metal; internal class MTLGraphicsPipeline : GraphicsPipeline { - public MTLRenderPipelineState RenderPipelineState; - public MTLDepthStencilState DepthStencilState; + public MTLRenderPipelineState RenderPipelineState; + public MTLGraphicsPipeline(MTLGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) { - VertexBufferStartIndex = desc.ResourceLayout is not null ? desc.ResourceLayout.Metal().BufferCount : 0; - MTL4RenderPipelineDescriptor descriptor = new() { - VertexFunctionDescriptor = desc.Vertex.Metal().Descriptor, - FragmentFunctionDescriptor = desc.Pixel.Metal().Descriptor, + VertexFunctionDescriptor = desc.VertexShader.Metal().Descriptor, + FragmentFunctionDescriptor = desc.FragmentShader.Metal().Descriptor, InputPrimitiveTopology = MTLFormats.Metal(desc.PrimitiveTopology).TopologyClass }; - // RenderStates - Output + // InputLayouts { - descriptor.AlphaToCoverageState = desc.RenderStates.BlendState.AlphaToCoverageEnable ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; + 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; - BlendStateRenderTarget[] blendStateRenderTargets = + 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.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 + 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 ]; - for (int i = 0; i < blendStateRenderTargets.Length; i++) + 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++) { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[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) + 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), + 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) }; } - 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() + 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.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 + 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.RenderStates.DepthStencilState.StencilEnable ? new() + BackFaceStencil = desc.RenderState.DepthStencil.IsStencilEnabled ? 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 + 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 }); } - // 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; } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } protected override void SetResourceName(string name) { @@ -116,7 +115,7 @@ protected override void SetResourceName(string name) protected override void Destroy() { - DepthStencilState.Dispose(); RenderPipelineState.Dispose(); + DepthStencilState.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLHeap.cs b/sources/Zenith.NET.Metal/MTLHeap.cs index fc3caa19..2580a20c 100644 --- a/sources/Zenith.NET.Metal/MTLHeap.cs +++ b/sources/Zenith.NET.Metal/MTLHeap.cs @@ -2,59 +2,44 @@ namespace Zenith.NET.Metal; -internal class MTLHeap : GraphicsResource +internal class MTLHeap : Heap { public MtlHeap Heap; - public MTLHeap(MTLGraphicsContext context, BufferDesc desc, out MtlBuffer buffer) : base(context) + public MTLHeap(MTLGraphicsContext context, HeapDesc desc) : base(context, desc) { - context.AddAllocation(Heap = context.Device.MakeHeap(new() + 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)); + Size = (nuint)desc.SizeInBytes, + ResourceOptions = MTLFormats.Metal(desc.Residency), + Type = MTLHeapType.Placement + }); } - public MTLHeap(MTLGraphicsContext context, TextureDesc desc, out MtlTexture texture) : base(context) + public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) { - 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 - })); + return 0; + } - texture = Heap.MakeTexture(descriptor); + protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) + { + return new MTLBuffer(Context, desc, Heap.MakeBuffer(desc.SizeInBytes, MTLFormats.Metal(Desc.Residency), (nuint)offsetInBytes)); } - public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; + 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.RemoveAllocation(Heap); - Heap.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs index cc84af40..065b5789 100644 --- a/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Metal/MTLMeshShadingPipeline.cs @@ -4,82 +4,81 @@ namespace Zenith.NET.Metal; internal class MTLMeshShadingPipeline : MeshShadingPipeline { - public MTLRenderPipelineState RenderPipelineState; - public MTLDepthStencilState DepthStencilState; + public MTLRenderPipelineState RenderPipelineState; + 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) + 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.Amplification is not null) + if (desc.TaskShader is not null) { - descriptor.ObjectFunctionDescriptor = desc.Amplification.Metal().Descriptor; - descriptor.RequiredThreadsPerObjectThreadgroup = new(desc.AmplificationThreadGroupSizeX, desc.AmplificationThreadGroupSizeY, desc.AmplificationThreadGroupSizeZ); + descriptor.ObjectFunctionDescriptor = desc.TaskShader.Metal().Descriptor; + descriptor.RequiredThreadsPerObjectThreadgroup = new(desc.TaskShader.Desc.ThreadGroupSize.X, desc.TaskShader.Desc.ThreadGroupSize.Y, desc.TaskShader.Desc.ThreadGroupSize.Z); } - // RenderStates - Output + // AttachmentFormats and RenderState { - descriptor.AlphaToCoverageState = desc.RenderStates.BlendState.AlphaToCoverageEnable ? MTL4AlphaToCoverageState.Enabled : MTL4AlphaToCoverageState.Disabled; - - BlendStateRenderTarget[] blendStateRenderTargets = + ColorAttachmentBlendState[] states = [ - 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 + 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 ]; - for (int i = 0; i < blendStateRenderTargets.Length; i++) + 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++) { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[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) + 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), + 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) }; } - 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() + 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.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 + 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.RenderStates.DepthStencilState.StencilEnable ? new() + BackFaceStencil = desc.RenderState.DepthStencil.IsStencilEnabled ? 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 + 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 }); } @@ -88,13 +87,18 @@ public MTLMeshShadingPipeline(MTLGraphicsContext context, MeshShadingPipelineDes error.Success(); } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + protected override void SetResourceName(string name) { } protected override void Destroy() { - DepthStencilState.Dispose(); RenderPipelineState.Dispose(); + DepthStencilState.Dispose(); } } diff --git a/sources/Zenith.NET.Metal/MTLQueryHeap.cs b/sources/Zenith.NET.Metal/MTLQueryHeap.cs index 99edbc27..c7e36e35 100644 --- a/sources/Zenith.NET.Metal/MTLQueryHeap.cs +++ b/sources/Zenith.NET.Metal/MTLQueryHeap.cs @@ -20,18 +20,22 @@ public MTLQueryHeap(MTLGraphicsContext context, QueryHeapDesc desc) : base(conte Buffer = new(context, new() { SizeInBytes = sizeof(ulong) * desc.Count, - StrideInBytes = sizeof(ulong), - Flags = BufferUsageFlags.MapRead + Residency = MemoryResidency.CpuReadOnly }); } public MTLBuffer Buffer { get; } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + 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(); } @@ -44,7 +48,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { Buffer.Dispose(); - CounterHeap.Dispose(); } } 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 deleted file mode 100644 index 50885767..00000000 --- a/sources/Zenith.NET.Metal/MTLResourceTable.cs +++ /dev/null @@ -1,155 +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; - private readonly Binding[] accelerationStructureBindings; - - public MTL4ArgumentTable ArgumentTable; - - public MTLResourceTable(MTLGraphicsContext context, ResourceTableDesc desc) : base(context, desc) - { - MTLResourceLayout layout = desc.Layout.Metal(); - - MTL4ArgumentTableDescriptor descriptor = new() - { - MaxBufferBindCount = layout.BufferCount, - MaxTextureBindCount = layout.TextureCount, - MaxSamplerStateBindCount = layout.SamplerCount - }; - - 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) - { - binding.Buffer(argumentTable); - } - - foreach (Binding binding in textureBindings) - { - binding.Texture(argumentTable); - } - - foreach (Binding binding in samplerBindings) - { - binding.Sampler(argumentTable); - } - - foreach (Binding binding in accelerationStructureBindings) - { - binding.AccelerationStructure(argumentTable); - } - } - - protected override void PreprocessImpl(CommandBuffer commandBuffer) - { - } - - 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) - { - argumentTable.SetAddress(gpuAddress, index); - } - - public void Texture(MTL4ArgumentTable argumentTable) - { - argumentTable.SetTexture(resourceID, index); - } - - public void Sampler(MTL4ArgumentTable argumentTable) - { - argumentTable.SetSamplerState(resourceID, index); - } - - public void AccelerationStructure(MTL4ArgumentTable argumentTable) - { - argumentTable.SetResource(resourceID, index); - } - } -} diff --git a/sources/Zenith.NET.Metal/MTLSampler.cs b/sources/Zenith.NET.Metal/MTLSampler.cs index 2acdba25..a738bca8 100644 --- a/sources/Zenith.NET.Metal/MTLSampler.cs +++ b/sources/Zenith.NET.Metal/MTLSampler.cs @@ -8,24 +8,32 @@ internal class MTLSampler : Sampler public MTLSampler(MTLGraphicsContext context, SamplerDesc desc) : base(context, desc) { - MTLSamplerDescriptor descriptor = new() + SamplerState = context.Device.MakeSamplerState(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), + 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.ComparisonFunc), + CompareFunction = MTLFormats.Metal(desc.CompareOp), SupportArgumentBuffers = true - }; + }); - SamplerState = context.Device.MakeSamplerState(descriptor); + Handle = SamplerState.GpuResourceID.Impl.ToHandle(); + } + + public override ResourceHandle Handle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Metal/MTLShader.cs b/sources/Zenith.NET.Metal/MTLShader.cs index f5a419a3..d617c4a5 100644 --- a/sources/Zenith.NET.Metal/MTLShader.cs +++ b/sources/Zenith.NET.Metal/MTLShader.cs @@ -10,18 +10,24 @@ internal class MTLShader : Shader public MTLShader(MTLGraphicsContext context, ShaderDesc desc) : base(context, desc) { - Library = context.Device.MakeLibrary(DispatchData.Create(desc.ShaderBytes), out NSError error); + Library = context.Device.MakeLibrary(DispatchData.Create(desc.CodeBytes), out NSError error); error.Success(); Descriptor = new() { - Name = desc.EntryPoint, + 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() diff --git a/sources/Zenith.NET.Metal/MTLSwapChain.cs b/sources/Zenith.NET.Metal/MTLSwapChain.cs index 34e067ba..2b2ff851 100644 --- a/sources/Zenith.NET.Metal/MTLSwapChain.cs +++ b/sources/Zenith.NET.Metal/MTLSwapChain.cs @@ -4,43 +4,77 @@ namespace Zenith.NET.Metal; internal class MTLSwapChain : SwapChain { - private readonly MTLSwapChainFrameBuffer swapChainFrameBuffer; + public CAMetalLayer MetalLayer = CAMetalLayer.Null; - public CAMetalLayer Layer = CAMetalLayer.Null; + public CAMetalDrawable MetalDrawable = CAMetalDrawable.Null; - public CAMetalDrawable Drawable = CAMetalDrawable.Null; + private MTLTexture? drawable; public MTLSwapChain(MTLGraphicsContext context, SwapChainDesc desc) : base(context, desc) { - swapChainFrameBuffer = new(context, this); - - CreateSwapChain(); + Initialize(); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - public override FrameBuffer FrameBuffer => swapChainFrameBuffer.Get(Desc.Surface.Width, Desc.Surface.Height, Drawable); + public override Texture Drawable + { + get + { + if (drawable is null || drawable.Texture.NativePtr != MetalDrawable.Texture.NativePtr) + { + 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 + }; + + drawable?.Dispose(); + drawable = new(Context, desc, MetalDrawable.Texture.Retain()); + } + + return drawable; + } + } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } - public override void Present() + protected override void PresentImpl() { - Drawable.Present(); - Drawable.Dispose(); + Context.GraphicsQueue.Metal().CommandQueue.SignalDrawable(MetalDrawable); - Drawable = NSAutorelease.Own(Layer.NextDrawable); + MetalDrawable.Present(); + MetalDrawable.Dispose(); + MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); } protected override void ResizeImpl() { - Drawable.Dispose(); + MetalLayer.DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height); - Layer.DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height); - - Drawable = NSAutorelease.Own(Layer.NextDrawable); + MetalDrawable.Dispose(); + MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); } protected override void RefreshImpl() { - CreateSwapChain(); + drawable?.Dispose(); + drawable = null; + + MetalDrawable.Dispose(); + MetalLayer.Dispose(); + + Initialize(); } protected override void SetResourceName(string name) @@ -49,29 +83,22 @@ protected override void SetResourceName(string name) protected override void Destroy() { - DestroySwapChain(); + drawable?.Dispose(); - swapChainFrameBuffer.Dispose(); + MetalDrawable.Dispose(); + MetalLayer.Dispose(); } - private void CreateSwapChain() + private void Initialize() { - DestroySwapChain(); - - Layer = new(Desc.Surface.Handles[0], NativeObjectOwnership.Borrowed) + MetalLayer = new(Desc.Surface.Handles[0], NativeObjectOwnership.Borrowed) { Device = Context.Device, - PixelFormat = MTLFormats.Metal(Desc.ColorTargetFormat).PixelFormat, + PixelFormat = MTLFormats.Metal(Desc.Format).PixelFormat, FramebufferOnly = false, DrawableSize = new(Desc.Surface.Width, Desc.Surface.Height) }; - Drawable = NSAutorelease.Own(Layer.NextDrawable); - } - - private void DestroySwapChain() - { - Drawable.Dispose(); - Layer.Dispose(); + MetalDrawable = NSAutorelease.Own(MetalLayer.NextDrawable); } } diff --git a/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs b/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs deleted file mode 100644 index 2a2cf920..00000000 --- a/sources/Zenith.NET.Metal/MTLSwapChainFrameBuffer.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; - -internal class MTLSwapChainFrameBuffer(MTLGraphicsContext context, MTLSwapChain swapChain) : GraphicsResource(context) -{ - private MTLTexture? colorTarget; - private MTLTexture? depthStencilTarget; - private MTLFrameBuffer? frameBuffer; - - public MTLFrameBuffer Get(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; - - frameBuffer ??= new(context, new() - { - ColorAttachments = [new() { Target = colorTarget }], - DepthStencilAttachment = depthStencilTarget is not null ? new() { Target = depthStencilTarget } : null - }); - - if (frameBuffer.Width != width || frameBuffer.Height != height) - { - DestroyFrameBuffer(); - - return Get(width, height, drawable); - } - - frameBuffer.Descriptor.ColorAttachments[0].Texture = colorTarget.Texture = drawable.Texture; - - return frameBuffer; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroyFrameBuffer(); - } - - private void DestroyFrameBuffer() - { - frameBuffer?.Dispose(); - frameBuffer = null; - - depthStencilTarget?.Dispose(); - depthStencilTarget = null; - - colorTarget?.Dispose(); - colorTarget = null; - } -} diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs index 34609036..88cd42ca 100644 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -1,4 +1,6 @@ -namespace Zenith.NET.Metal; +using Metal.NET; + +namespace Zenith.NET.Metal; internal class MTLTexture : Texture { @@ -6,15 +8,42 @@ internal class MTLTexture : Texture public MTLTexture(MTLGraphicsContext context, TextureDesc desc) : base(context, desc) { - Heap = new(context, desc, out Texture); + 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) { - Texture = texture; + context.Register(Texture = texture); + + View = new(context, new() + { + Texture = this, + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) + }); } - public MTLHeap? Heap { get; } + 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) { @@ -23,11 +52,27 @@ protected override void SetResourceName(string name) protected override void Destroy() { - if (Heap is not null) - { - Texture.Dispose(); + Context.Unregister(Texture); - Heap.Dispose(); - } + View.Dispose(); + Texture.Dispose(); + } + + public static MTLTextureDescriptor Descriptor(TextureDesc desc) + { + return new() + { + TextureType = MTLFormats.Metal(desc.Type, desc.SampleCount), + 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.CPUCacheModeDefaultCache | MTLResourceOptions.StorageModePrivate | MTLResourceOptions.HazardTrackingModeUntracked, + Usage = MTLFormats.Metal(desc.Usages), + AllowGPUOptimizedContents = true + }; } } diff --git a/sources/Zenith.NET.Metal/MTLTextureView.cs b/sources/Zenith.NET.Metal/MTLTextureView.cs index 8a6c578c..73d580e4 100644 --- a/sources/Zenith.NET.Metal/MTLTextureView.cs +++ b/sources/Zenith.NET.Metal/MTLTextureView.cs @@ -1,6 +1,4 @@ -using Metal.NET; - -namespace Zenith.NET.Metal; +namespace Zenith.NET.Metal; internal class MTLTextureView : TextureView { @@ -8,15 +6,22 @@ internal class MTLTextureView : TextureView public MTLTextureView(MTLGraphicsContext context, TextureViewDesc desc) : base(context, desc) { - 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) - }; - - Texture = desc.Texture.Metal().Texture.MakeTextureView(descriptor); + 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)); + + SampledHandle = Texture.GpuResourceID.Impl.ToHandle(); + StorageHandle = Texture.GpuResourceID.Impl.ToHandle(); + } + + public override ResourceHandle SampledHandle { get; } + + public override ResourceHandle StorageHandle { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -28,15 +33,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.Metal/MTLTimeline.cs b/sources/Zenith.NET.Metal/MTLTimeline.cs new file mode 100644 index 00000000..ca93362f --- /dev/null +++ b/sources/Zenith.NET.Metal/MTLTimeline.cs @@ -0,0 +1,38 @@ +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 nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override ulong GetCompletedValue() + { + return Event.SignaledValue; + } + + 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.Metal/MTLTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs index 04d703dd..8d0d5576 100644 --- a/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Metal/MTLTopLevelAccelerationStructure.cs @@ -6,65 +6,55 @@ internal unsafe class MTLTopLevelAccelerationStructure : TopLevelAccelerationStr { public MTLAccelerationStructure AccelerationStructure; - public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, TopLevelAccelerationStructureDesc desc, MTLCommandBuffer commandBuffer) : base(context, desc) + public MTLTopLevelAccelerationStructure(MTLGraphicsContext context, MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { - uint instanceCount = (uint)desc.Instances.Length; - - InstanceBuffer = new(context, new() + Instance = new(context, new() { - SizeInBytes = (uint)(sizeof(MTLIndirectAccelerationStructureInstanceDescriptor) * instanceCount), - StrideInBytes = (uint)sizeof(MTLIndirectAccelerationStructureInstanceDescriptor), - Flags = BufferUsageFlags.MapWrite + SizeInBytes = (uint)(sizeof(MTLIndirectAccelerationStructureInstanceDescriptor) * desc.Instances.Length), + Residency = MemoryResidency.CpuWriteOnly }); - 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) - }; + MTL4InstanceAccelerationStructureDescriptor descriptor = Descriptor(desc); MTLAccelerationStructureSizes sizes = context.Device.AccelerationStructureSizes(descriptor); - AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize); - context.AddAllocation(AccelerationStructure); + context.Register(AccelerationStructure = context.Device.MakeAccelerationStructure(sizes.AccelerationStructureSize)); - ScratchBuffer = new(context, new() + Scratch = new(context, new() { SizeInBytes = (uint)sizes.BuildScratchBufferSize, - StrideInBytes = (uint)sizes.BuildScratchBufferSize, - Flags = BufferUsageFlags.ShaderResource + Usages = BufferUsages.StorageReadWrite, + Residency = MemoryResidency.GpuOnly }); - commandBuffer.CommandEncoder.Compute?.Build(AccelerationStructure, descriptor, new(ScratchBuffer.Buffer.GpuAddress, ScratchBuffer.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); + commandBuffer.Compute?.Build(AccelerationStructure, descriptor, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); + + Handle = AccelerationStructure.GpuResourceID.Impl.ToHandle(); } public new MTLGraphicsContext Context => (MTLGraphicsContext)base.Context; - public MTLBuffer InstanceBuffer { get; } + public MTLBuffer Instance { get; } - public MTLBuffer ScratchBuffer { get; } + public MTLBuffer Scratch { get; } + + public override ResourceHandle Handle { get; } public void Update(MTLCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc newDesc) { - FillInstanceBuffer(newDesc); + MTL4InstanceAccelerationStructureDescriptor descriptor = Descriptor(newDesc); + descriptor.Usage |= MTLAccelerationStructureUsage.Refit; - 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.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.AccelerationStructure, MTL4VisibilityOptions.Device); + commandBuffer.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(Scratch.Buffer.GpuAddress, Scratch.Desc.SizeInBytes)); + commandBuffer.Compute?.BarrierAfterEncoderStages(MTLStages.AccelerationStructure, MTLStages.Dispatch, MTL4VisibilityOptions.Device); + } - commandBuffer.CommandEncoder.Compute?.Refit(AccelerationStructure, descriptor, AccelerationStructure, new(ScratchBuffer.Buffer.GpuAddress, ScratchBuffer.Desc.SizeInBytes)); + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -74,21 +64,20 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.RemoveAllocation(AccelerationStructure); + Context.Unregister(AccelerationStructure); + Scratch.Dispose(); + Instance.Dispose(); AccelerationStructure.Dispose(); - - ScratchBuffer.Dispose(); - InstanceBuffer.Dispose(); } - private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc) + private MTL4InstanceAccelerationStructureDescriptor Descriptor(TopLevelAccelerationStructureDesc desc) { uint instanceCount = (uint)desc.Instances.Length; - MappedMemory mappedMemory = InstanceBuffer.Map(); + nint pointer = Instance.Map(); - MTLIndirectAccelerationStructureInstanceDescriptor* instances = (MTLIndirectAccelerationStructureInstanceDescriptor*)mappedMemory.Pointer; + MTLIndirectAccelerationStructureInstanceDescriptor* instances = (MTLIndirectAccelerationStructureInstanceDescriptor*)pointer; for (uint i = 0; i < instanceCount; i++) { RayTracingInstance instance = desc.Instances[i]; @@ -97,12 +86,26 @@ private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc) { TransformationMatrix = MTLFormats.Metal(instance.Transform), Options = MTLFormats.Metal(instance.Flags), - Mask = instance.Mask, - UserID = instance.ID, + Mask = instance.VisibilityMask, + UserID = instance.InstanceId, AccelerationStructureID = instance.AccelerationStructure.Metal().AccelerationStructure.GpuResourceID }; } - InstanceBuffer.Unmap(); + 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.ColumnMajor, + Usage = MTLFormats.Metal(desc.BuildFlags) + }; } } diff --git a/sources/Zenith.NET.Metal/NSAutorelease.cs b/sources/Zenith.NET.Metal/NSAutorelease.cs index 8033430c..5d11406d 100644 --- a/sources/Zenith.NET.Metal/NSAutorelease.cs +++ b/sources/Zenith.NET.Metal/NSAutorelease.cs @@ -31,4 +31,4 @@ public static T Own(Func func, T1 arg1, T2 arg2, T 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 index 6bc1dc32..fc76a42c 100644 --- a/sources/Zenith.NET.Metal/Usings.cs +++ b/sources/Zenith.NET.Metal/Usings.cs @@ -1,4 +1,3 @@ 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/ExtDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs new file mode 100644 index 00000000..af13bef4 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/ExtDescriptorHeap.cs @@ -0,0 +1,2723 @@ +// +#pragma warning disable + +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 +{ + 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; + } + } +} + +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 diff --git a/sources/Zenith.NET.Vulkan/Extensions.cs b/sources/Zenith.NET.Vulkan/Extensions.cs index efbf2e95..1b537e1e 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,152 +32,149 @@ 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(CommandBuffer commandBuffer) + extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) { - internal VKCommandBuffer Vulkan() + internal VKBottomLevelAccelerationStructure Vulkan() { - return (VKCommandBuffer)commandBuffer; + return (VKBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; } } - extension(SwapChain swapChain) + extension(Buffer buffer) { - internal VKSwapChain Vulkan() + internal VKBuffer Vulkan() { - return (VKSwapChain)swapChain; + return (VKBuffer)buffer; } } - extension(FrameBuffer frameBuffer) + extension(BufferView bufferView) { - internal VKFrameBuffer Vulkan() + internal VKBufferView Vulkan() { - return (VKFrameBuffer)frameBuffer; + return (VKBufferView)bufferView; } } - extension(Shader shader) + extension(CommandBuffer commandBuffer) { - internal VKShader Vulkan() + internal VKCommandBuffer Vulkan() { - return (VKShader)shader; + return (VKCommandBuffer)commandBuffer; } } - extension(Buffer buffer) + extension(CommandQueue commandQueue) { - internal VKBuffer Vulkan() + internal VKCommandQueue Vulkan() { - return (VKBuffer)buffer; + return (VKCommandQueue)commandQueue; } } - extension(BufferView bufferView) + extension(Timeline timeline) { - internal VKBufferView Vulkan() + internal VKTimeline Vulkan() { - return (VKBufferView)bufferView; + return (VKTimeline)timeline; } } - extension(Texture texture) + extension(ComputePipeline computePipeline) { - internal VKTexture Vulkan() + internal VKComputePipeline Vulkan() { - return (VKTexture)texture; + return (VKComputePipeline)computePipeline; } } - extension(TextureView textureView) + extension(GraphicsPipeline graphicsPipeline) { - internal VKTextureView Vulkan() + internal VKGraphicsPipeline Vulkan() { - return (VKTextureView)textureView; + return (VKGraphicsPipeline)graphicsPipeline; } } - extension(Sampler sampler) + extension(Heap heap) { - internal VKSampler Vulkan() + internal VKHeap Vulkan() { - return (VKSampler)sampler; + return (VKHeap)heap; } } - extension(BottomLevelAccelerationStructure bottomLevelAccelerationStructure) + extension(MeshShadingPipeline meshShadingPipeline) { - internal VKBottomLevelAccelerationStructure Vulkan() + internal VKMeshShadingPipeline Vulkan() { - return (VKBottomLevelAccelerationStructure)bottomLevelAccelerationStructure; + return (VKMeshShadingPipeline)meshShadingPipeline; } } - extension(TopLevelAccelerationStructure topLevelAccelerationStructure) + extension(QueryHeap queryHeap) { - internal VKTopLevelAccelerationStructure Vulkan() + internal VKQueryHeap Vulkan() { - return (VKTopLevelAccelerationStructure)topLevelAccelerationStructure; + return (VKQueryHeap)queryHeap; } } - extension(ResourceLayout resourceLayout) + extension(Sampler sampler) { - internal VKResourceLayout Vulkan() + internal VKSampler Vulkan() { - return (VKResourceLayout)resourceLayout; + return (VKSampler)sampler; } } - extension(ResourceTable resourceTable) + extension(Shader shader) { - internal VKResourceTable Vulkan() + internal VKShader Vulkan() { - return (VKResourceTable)resourceTable; + return (VKShader)shader; } } - extension(GraphicsPipeline graphicsPipeline) + extension(SwapChain swapChain) { - internal VKGraphicsPipeline Vulkan() + internal VKSwapChain Vulkan() { - return (VKGraphicsPipeline)graphicsPipeline; + return (VKSwapChain)swapChain; } } - extension(ComputePipeline computePipeline) + extension(Texture texture) { - internal VKComputePipeline Vulkan() + internal VKTexture Vulkan() { - return (VKComputePipeline)computePipeline; + return (VKTexture)texture; } } - extension(MeshShadingPipeline meshShadingPipeline) + extension(TextureView textureView) { - internal VKMeshShadingPipeline Vulkan() + internal VKTextureView Vulkan() { - return (VKMeshShadingPipeline)meshShadingPipeline; + return (VKTextureView)textureView; } } - extension(QueryHeap queryHeap) + extension(TopLevelAccelerationStructure topLevelAccelerationStructure) { - internal VKQueryHeap Vulkan() + internal VKTopLevelAccelerationStructure Vulkan() { - return (VKQueryHeap)queryHeap; + return (VKTopLevelAccelerationStructure)topLevelAccelerationStructure; } } } 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/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/Usings.cs b/sources/Zenith.NET.Vulkan/Usings.cs index b101a612..54005db9 100644 --- a/sources/Zenith.NET.Vulkan/Usings.cs +++ b/sources/Zenith.NET.Vulkan/Usings.cs @@ -1,15 +1,13 @@ -global using VkBlendOp = Silk.NET.Vulkan.BlendOp; +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 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 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 VkQueryType = Silk.NET.Vulkan.QueryType; -global using VkSampler = Silk.NET.Vulkan.Sampler; -global using VkShaderStageFlags = Silk.NET.Vulkan.ShaderStageFlags; +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/VKAllocation.cs b/sources/Zenith.NET.Vulkan/VKAllocation.cs new file mode 100644 index 00000000..fbdba0de --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKAllocation.cs @@ -0,0 +1,14 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +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 OwnsResource = ownsResource; + + public readonly bool OwnsMemory = ownsMemory; +} diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index b29a9464..b542a751 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -8,140 +8,83 @@ internal unsafe class VKBottomLevelAccelerationStructure : BottomLevelAccelerati public ulong DeviceAddress; - public VKBottomLevelAccelerationStructure(VKGraphicsContext context, BottomLevelAccelerationStructureDesc desc, VKCommandBuffer commandBuffer) : base(context, desc) + public VKBottomLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc desc) : base(context, desc) { using ZenithMarshal.Scope scope = new(); - uint geometryCount = (uint)desc.Geometries.Length; - - TransformBuffer = new(context, new() + Transform = new(context, new() { - SizeInBytes = (uint)(sizeof(TransformMatrixKHR) * geometryCount), - StrideInBytes = (uint)sizeof(TransformMatrixKHR), - Flags = BufferUsageFlags.AccelerationStructure | BufferUsageFlags.MapWrite - }); - - MappedMemory mappedMemory = TransformBuffer.Map(); - - desc.Geometries.Select(static item => VKFormats.Vulkan(item.Triangles.Transform)).ToArray().CopyTo(new Span((TransformMatrixKHR*)mappedMemory.Pointer, (int)geometryCount)); - - TransformBuffer.Unmap(); + SizeInBytes = (uint)(sizeof(TransformMatrixKHR) * desc.Geometries.Length), + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr); - 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 - }; + 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); - context.AccelerationStructure?.GetAccelerationStructureBuildSizes(context.Device, AccelerationStructureBuildTypeKHR.DeviceKhr, &buildInfo, maxPrimitiveCounts, &sizeInfo); - - BufferDesc accelerationStructureBufferDesc = new() + Storage = new(context, new() { SizeInBytes = (uint)sizeInfo.AccelerationStructureSize, - StrideInBytes = (uint)sizeInfo.AccelerationStructureSize - }; + Residency = MemoryResidency.GpuOnly + }, BufferUsageFlags.AccelerationStructureStorageBitKhr); - AccelerationStructureBuffer = new(context, accelerationStructureBufferDesc, VkBufferUsageFlags.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 = AccelerationStructureBuffer.Buffer, + Buffer = Storage.Buffer, Size = sizeInfo.AccelerationStructureSize, Type = AccelerationStructureTypeKHR.BottomLevelKhr }; - context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, null, out AccelerationStructure).Success(); + context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, default, out AccelerationStructure).Success(); - AccelerationStructureDeviceAddressInfoKHR addressInfo = new() + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; + + context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + + AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() { SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, AccelerationStructure = AccelerationStructure }; - DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &addressInfo) ?? 0; + DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0; + } - ScratchBuffer = new(context, new() - { - SizeInBytes = (uint)sizeInfo.BuildScratchSize, - StrideInBytes = (uint)sizeInfo.BuildScratchSize, - Flags = BufferUsageFlags.ShaderResource - }); + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - buildInfo.DstAccelerationStructure = AccelerationStructure; - buildInfo.ScratchData = new() { DeviceAddress = ScratchBuffer.DeviceAddress }; + public VKBuffer Transform { get; } - context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &buildInfo, &buildRangeInfos); + public VKBuffer Storage { get; } - MemoryBarrier barrier = new() - { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.AccelerationStructureWriteBitKhr, - DstAccessMask = AccessFlags.AccelerationStructureReadBitKhr - }; + public VKBuffer Scratch { get; } - 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 void Update(VKCommandBuffer commandBuffer, BottomLevelAccelerationStructureDesc newDesc) + { + using ZenithMarshal.Scope scope = new(); - public VKBuffer TransformBuffer { get; } + 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 }; - public VKBuffer AccelerationStructureBuffer { get; } + Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + } - public VKBuffer ScratchBuffer { get; } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } protected override void SetResourceName(string name) { @@ -160,10 +103,73 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, null); + Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, default); + + Scratch.Dispose(); + Storage.Dispose(); + Transform.Dispose(); + } + + private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope, BottomLevelAccelerationStructureDesc desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos) + { + 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 : geometry.TriangleGeometry.IndexCount) / 3u, + RayTracingGeometryType.Aabb => geometry.AabbGeometry.Count, + _ => default + }; + buildRangeInfos[i] = new() { PrimitiveCount = maxPrimitiveCounts[i] }; + } - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - TransformBuffer.Dispose(); + 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/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 0ba5915a..0370a18f 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -6,99 +6,166 @@ internal unsafe class VKBuffer : Buffer { public VkBuffer Buffer; + public VKAllocation Allocation; + public ulong DeviceAddress; public VKBuffer(VKGraphicsContext context, BufferDesc desc) : base(context, desc) { - using ZenithMarshal.Scope scope = new(); + BufferCreateInfo createInfo = CreateInfo(desc, context.Capabilities, context.QueueFamilies); - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); + context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); - BufferCreateInfo createInfo = new() + BufferMemoryRequirementsInfo2 requirementsInfo2 = new() { - SType = StructureType.BufferCreateInfo, - Size = desc.SizeInBytes, - Usage = VKFormats.Vulkan(desc.Flags).UsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices + SType = StructureType.BufferMemoryRequirementsInfo2, + Buffer = Buffer }; - context.Vk.CreateBuffer(context.Device, &createInfo, null, out Buffer).Success(); + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); + + context.Vk.GetBufferMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - DeviceMemory = new(context, this); + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, desc.Residency) + }; - BufferDeviceAddressInfo addressInfo = new() + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Buffer = Buffer; + } + + 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(); + + Allocation = new(deviceMemory, 0, true, true); + + BufferDeviceAddressInfo deviceAddressInfo = new() { SType = StructureType.BufferDeviceAddressInfo, Buffer = Buffer }; - DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &addressInfo); + DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); 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) + public VKBuffer(VKGraphicsContext context, BufferDesc desc, BufferUsageFlags usage) : base(context, desc) { - using ZenithMarshal.Scope scope = new(); + BufferCreateInfo createInfo = CreateInfo(desc, context.Capabilities, context.QueueFamilies); + createInfo.Usage |= usage; - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); + context.Vk.CreateBuffer(context.Device, &createInfo, default, out Buffer).Success(); - BufferCreateInfo createInfo = new() + BufferMemoryRequirementsInfo2 requirementsInfo2 = new() { - SType = StructureType.BufferCreateInfo, - Size = desc.SizeInBytes, - Usage = VKFormats.Vulkan(desc.Flags).UsageFlags | otherUsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices + 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) }; - context.Vk.CreateBuffer(context.Device, &createInfo, null, out Buffer).Success(); + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Buffer = Buffer; + } + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo flagsInfo); + flagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; - DeviceMemory = new(context, this); + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory deviceMemory).Success(); + context.Vk.BindBufferMemory(context.Device, Buffer, deviceMemory, 0).Success(); - BufferDeviceAddressInfo addressInfo = new() + Allocation = new(deviceMemory, 0, true, true); + + BufferDeviceAddressInfo deviceAddressInfo = new() { SType = StructureType.BufferDeviceAddressInfo, Buffer = Buffer }; - DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &addressInfo); + DeviceAddress = context.Vk.GetBufferDeviceAddress(context.Device, &deviceAddressInfo); View = new(context, new() { Buffer = this, - OffsetInBytes = 0, SizeInBytes = desc.SizeInBytes, StrideInBytes = desc.StrideInBytes }); } - public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, VKAllocation allocation) : base(context, desc) + { + Buffer = buffer; + Allocation = allocation; - public VKDeviceMemory DeviceMemory { get; } + BufferDeviceAddressInfo deviceAddressInfo = new() + { + SType = StructureType.BufferDeviceAddressInfo, + Buffer = Buffer + }; + + 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 VKBufferView View { get; } - public override MappedMemory Map() + 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 nint Map() { void* pointer; - Context.Vk.MapMemory(Context.Device, DeviceMemory.DeviceMemory, 0, Desc.SizeInBytes, 0, &pointer).Success(); + Context.Vk.MapMemory(Context.Device, Allocation.DeviceMemory, Allocation.OffsetInBytes, Desc.SizeInBytes, MemoryMapFlags.None, &pointer).Success(); - return new() { Pointer = (nint)pointer, SizeInBytes = Desc.SizeInBytes }; + return (nint)pointer; } public override void Unmap() { - Context.Vk.UnmapMemory(Context.Device, DeviceMemory.DeviceMemory); + Context.Vk.UnmapMemory(Context.Device, Allocation.DeviceMemory); } protected override void SetResourceName(string name) @@ -120,8 +187,27 @@ protected override void Destroy() { View.Dispose(); - Context.Vk.DestroyBuffer(Context.Device, Buffer, null); + if (Allocation.OwnsResource) + { + Context.Vk.DestroyBuffer(Context.Device, Buffer, default); + } + + if (Allocation.OwnsMemory) + { + Context.Vk.FreeMemory(Context.Device, Allocation.DeviceMemory, default); + } + } - DeviceMemory.Dispose(); + public static BufferCreateInfo CreateInfo(BufferDesc desc, Capabilities capabilities, QueueFamilies queueFamilies) + { + return new() + { + SType = StructureType.BufferCreateInfo, + Size = desc.SizeInBytes, + Usage = VKFormats.Vulkan(desc.Usages, capabilities.RayTracingSupported), + SharingMode = queueFamilies.SharingMode, + QueueFamilyIndexCount = queueFamilies.IndexCount, + PQueueFamilyIndices = queueFamilies.Indices + }; } } diff --git a/sources/Zenith.NET.Vulkan/VKBufferView.cs b/sources/Zenith.NET.Vulkan/VKBufferView.cs index 83d2e473..7d930eb3 100644 --- a/sources/Zenith.NET.Vulkan/VKBufferView.cs +++ b/sources/Zenith.NET.Vulkan/VKBufferView.cs @@ -2,25 +2,47 @@ namespace Zenith.NET.Vulkan; -internal class VKBufferView : BufferView +internal unsafe class VKBufferView(VKGraphicsContext context, BufferViewDesc desc) : BufferView(context, desc) { - public VKBufferView(VKGraphicsContext context, BufferViewDesc desc) : base(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) { - BufferInfo = new() - { - Buffer = desc.Buffer.Vulkan().Buffer, - Offset = desc.OffsetInBytes, - Range = desc.SizeInBytes - }; + return 0; } - public DescriptorBufferInfo BufferInfo { get; } - protected override void SetResourceName(string name) { } protected override void Destroy() { + storageReadWriteToken?.Dispose(); + storageReadOnlyToken?.Dispose(); + constantToken?.Dispose(); + } + + private VKDescriptorToken CreateToken(DescriptorType type) + { + 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 } + }); } } diff --git a/sources/Zenith.NET.Vulkan/VKCapabilities.cs b/sources/Zenith.NET.Vulkan/VKCapabilities.cs index b129ad59..48681f10 100644 --- a/sources/Zenith.NET.Vulkan/VKCapabilities.cs +++ b/sources/Zenith.NET.Vulkan/VKCapabilities.cs @@ -13,10 +13,10 @@ public VKCapabilities(VKGraphicsContext context) context.Vk.GetPhysicalDeviceProperties(context.PhysicalDevice, &properties); uint extensionCount = 0; - context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, (byte*)null, &extensionCount, (ExtensionProperties*)null).Success(); + context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, default(byte*), &extensionCount, default).Success(); ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); - context.Vk.EnumerateDeviceExtensionProperties(context.PhysicalDevice, (byte*)null, &extensionCount, extensions).Success(); + 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))]; diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 01001e66..96dc0631 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -18,13 +18,12 @@ public VKCommandBuffer(VKGraphicsContext context, VKCommandQueue queue) : base(c QueueFamilyIndex = queue.QueueFamilyIndex }; - context.Vk.CreateCommandPool(context.Device, &createInfo, null, out CommandPool).Success(); + context.Vk.CreateCommandPool(context.Device, &createInfo, default, out CommandPool).Success(); CommandBufferAllocateInfo allocateInfo = new() { SType = StructureType.CommandBufferAllocateInfo, CommandPool = CommandPool, - Level = CommandBufferLevel.Primary, CommandBufferCount = 1 }; @@ -33,94 +32,159 @@ public VKCommandBuffer(VKGraphicsContext context, VKCommandQueue queue) : base(c public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes) + public override nint GetNativeObject(NativeObjectType type) { - VKBuffer vkSrc = src.Vulkan(); - VKBuffer vkDest = dest.Vulkan(); + return 0; + } + + protected override void BarrierImpl(BarrierStages before, BarrierStages after) + { + (PipelineStageFlags2 srcStage, AccessFlags2 srcAccess) = VKFormats.Vulkan(before); + (PipelineStageFlags2 dstStage, AccessFlags2 dstAccess) = VKFormats.Vulkan(after); - BufferCopy copyRegion = new() + MemoryBarrier2 memoryBarrier = new() { - SrcOffset = srcOffsetInBytes, - DstOffset = destOffsetInBytes, - Size = sizeInBytes + SType = StructureType.MemoryBarrier2, + SrcStageMask = srcStage, + SrcAccessMask = srcAccess, + DstStageMask = dstStage, + DstAccessMask = dstAccess }; - Context.Vk.CmdCopyBuffer(CommandBuffer, vkSrc.Buffer, vkDest.Buffer, 1, ©Region); + DependencyInfo dependencyInfo = new() + { + SType = StructureType.DependencyInfo, + MemoryBarrierCount = 1, + PMemoryBarriers = &memoryBarrier + }; - MemoryBarrier barrier = new() + Context.Vk.CmdPipelineBarrier2(CommandBuffer, &dependencyInfo); + } + + protected override void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout before, TextureLayout after) + { + VKTexture vkTexture = texture.Vulkan(); + + (PipelineStageFlags2 srcStage, AccessFlags2 srcAccess, ImageLayout oldLayout) = VKFormats.Vulkan(before); + (PipelineStageFlags2 dstStage, AccessFlags2 dstAccess, ImageLayout newLayout) = VKFormats.Vulkan(after); + + ImageMemoryBarrier2 imageMemoryBarrier = new() { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.TransferWriteBit, - DstAccessMask = AccessFlags.TransferReadBit + 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.CmdPipelineBarrier(CommandBuffer, PipelineStageFlags.TransferBit, PipelineStageFlags.TransferBit, 0, 1, &barrier, 0, null, 0, null); + Context.Vk.CmdPipelineBarrier2(CommandBuffer, &dependencyInfo); } - protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + protected override void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes) { VKBuffer vkSrc = src.Vulkan(); - VKTexture vkDest = dest.Vulkan(); + VKBuffer vkDst = dst.Vulkan(); + + BufferCopy2 region = new() + { + SType = StructureType.BufferCopy2, + SrcOffset = srcOffsetInBytes, + DstOffset = dstOffsetInBytes, + Size = sizeInBytes + }; - ImageLayout destOldLayout = vkDest.Layouts[ZenithHelper.SubresourceIndex(vkDest.Desc, destSlice)]; + CopyBufferInfo2 copyBufferInfo = new() + { + SType = StructureType.CopyBufferInfo2, + SrcBuffer = vkSrc.Buffer, + DstBuffer = vkDst.Buffer, + RegionCount = 1, + PRegions = ®ion + }; - vkDest.TransitionLayout(this, destSlice, ImageLayout.TransferDstOptimal); + Context.Vk.CmdCopyBuffer2(CommandBuffer, ©BufferInfo); + } - (uint blockWidth, uint blockHeight, uint blocksWide, uint blocksHigh) = ZenithHelper.BlockLayout(vkDest.Desc.Format, destExtent.Width, destExtent.Height); + protected override void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) + { + VKBuffer vkSrc = src.Vulkan(); + VKTexture vkDst = dst.Vulkan(); - uint formatSizeInBytes = ZenithHelper.SizeInBytes(vkDest.Desc.Format); - uint sliceRowPitchInBytes = ZenithHelper.Align(formatSizeInBytes * blocksWide, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchInBytes = ZenithHelper.Align(sliceRowPitchInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); + (uint blockWidth, uint blockHeight, _, _) = ZenithHelper.BlockLayout(vkDst.Desc.Format, dstExtent.Width, dstExtent.Height); - BufferImageCopy bufferImageCopy = new() + BufferImageCopy2 region = new() { + SType = StructureType.BufferImageCopy2, BufferOffset = srcOffsetInBytes, - BufferRowLength = sliceRowPitchInBytes / formatSizeInBytes * blockWidth, - BufferImageHeight = sliceDepthPitchInBytes / sliceRowPitchInBytes * blockHeight, + BufferRowLength = srcRowStrideInBytes / ZenithHelper.SizeInBytes(vkDst.Desc.Format) * blockWidth, + BufferImageHeight = srcSliceStrideInBytes / srcRowStrideInBytes * blockHeight, ImageSubresource = new() { - AspectMask = VKFormats.Vulkan(vkDest.Desc.Format, vkDest.Desc.Flags).AspectFlags, - MipLevel = destSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkDest.Desc, destSlice), + AspectMask = VKFormats.Vulkan(vkDst.Desc.Format).AspectFlags, + MipLevel = dstSubresource.MipLevel, + BaseArrayLayer = dstSubresource.ArrayLayer, LayerCount = 1 }, ImageOffset = new() { - X = (int)destOffset.X, - Y = (int)destOffset.Y, - Z = (int)destOffset.Z + X = (int)dstOffset.X, + Y = (int)dstOffset.Y, + Z = (int)dstOffset.Z }, ImageExtent = new() { - Width = destExtent.Width, - Height = destExtent.Height, - Depth = destExtent.Depth + Width = dstExtent.Width, + Height = dstExtent.Height, + Depth = dstExtent.Depth } }; - Context.Vk.CmdCopyBufferToImage(CommandBuffer, vkSrc.Buffer, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &bufferImageCopy); + CopyBufferToImageInfo2 copyBufferToImageInfo = new() + { + SType = StructureType.CopyBufferToImageInfo2, + SrcBuffer = vkSrc.Buffer, + DstImage = vkDst.Image, + DstImageLayout = ImageLayout.TransferDstOptimal, + RegionCount = 1, + PRegions = ®ion + }; - vkDest.TransitionLayout(this, destSlice, destOldLayout); + Context.Vk.CmdCopyBufferToImage2(CommandBuffer, ©BufferToImageInfo); } - 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 dst, TextureSubresource dstSubresource, Offset3D dstOffset, 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); + VKTexture vkDst = dst.Vulkan(); - ImageCopy imageCopy = new() + ImageCopy2 region = new() { + SType = StructureType.ImageCopy2, SrcSubresource = new() { - AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkSrc.Desc, srcSlice), + AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format).AspectFlags, + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, LayerCount = 1 }, SrcOffset = new() @@ -131,16 +195,16 @@ 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), + AspectMask = VKFormats.Vulkan(vkDst.Desc.Format).AspectFlags, + MipLevel = dstSubresource.MipLevel, + BaseArrayLayer = dstSubresource.ArrayLayer, LayerCount = 1 }, DstOffset = new() { - X = (int)destOffset.X, - Y = (int)destOffset.Y, - Z = (int)destOffset.Z + X = (int)dstOffset.X, + Y = (int)dstOffset.Y, + Z = (int)dstOffset.Z }, Extent = new() { @@ -150,36 +214,38 @@ protected override void CopyTextureImpl(Texture src, TextureSlice srcSlice, Text } }; - Context.Vk.CmdCopyImage(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &imageCopy); + CopyImageInfo2 copyImageInfo = new() + { + SType = StructureType.CopyImageInfo2, + SrcImage = vkSrc.Image, + SrcImageLayout = ImageLayout.TransferSrcOptimal, + DstImage = vkDst.Image, + DstImageLayout = ImageLayout.TransferDstOptimal, + RegionCount = 1, + PRegions = ®ion + }; - vkSrc.TransitionLayout(this, srcSlice, srcOldLayout); - vkDest.TransitionLayout(this, destSlice, destOldLayout); + Context.Vk.CmdCopyImage2(CommandBuffer, ©ImageInfo); } - 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 dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { VKTexture vkSrc = src.Vulkan(); + VKBuffer vkDst = dst.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() + BufferImageCopy2 region = new() { - BufferOffset = destOffsetInBytes, - BufferRowLength = sliceRowPitchInBytes / formatSizeInBytes * blockWidth, - BufferImageHeight = sliceDepthPitchInBytes / sliceRowPitchInBytes * blockHeight, + 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, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkSrc.Desc, srcSlice), + AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format).AspectFlags, + MipLevel = srcSubresource.MipLevel, + BaseArrayLayer = srcSubresource.ArrayLayer, LayerCount = 1 }, ImageOffset = new() @@ -196,38 +262,41 @@ protected override void CopyTextureToBufferImpl(Texture src, TextureSlice srcSli } }; - Context.Vk.CmdCopyImageToBuffer(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, dest.Vulkan().Buffer, 1, &bufferImageCopy); + CopyImageToBufferInfo2 copyImageToBufferInfo = new() + { + SType = StructureType.CopyImageToBufferInfo2, + SrcImage = vkSrc.Image, + SrcImageLayout = ImageLayout.TransferSrcOptimal, + DstBuffer = vkDst.Buffer, + RegionCount = 1, + PRegions = ®ion + }; - vkSrc.TransitionLayout(this, srcSlice, srcOldLayout); + Context.Vk.CmdCopyImageToBuffer2(CommandBuffer, ©ImageToBufferInfo); } - protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + protected override void ResolveTextureImpl(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { 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)]; + VKTexture vkDst = dst.Vulkan(); - vkSrc.TransitionLayout(this, srcSlice, ImageLayout.TransferSrcOptimal); - vkDest.TransitionLayout(this, destSlice, ImageLayout.TransferDstOptimal); + ZenithHelper.MipDimensions(vkSrc.Desc.Width, vkSrc.Desc.Height, vkSrc.Desc.Depth, srcSubresource.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); - ZenithHelper.MipDimensions(vkDest.Desc.Width, vkDest.Desc.Height, vkDest.Desc.Depth, destSlice.MipLevel, out uint mipWidth, out uint mipHeight, out uint mipDepth); - - ImageResolve imageResolve = new() + ImageResolve2 region = new() { + SType = StructureType.ImageResolve2, SrcSubresource = new() { - AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format, vkSrc.Desc.Flags).AspectFlags, - MipLevel = srcSlice.MipLevel, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(vkSrc.Desc, srcSlice), + AspectMask = VKFormats.Vulkan(vkSrc.Desc.Format).AspectFlags, + 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), + AspectMask = VKFormats.Vulkan(vkDst.Desc.Format).AspectFlags, + MipLevel = dstSubresource.MipLevel, + BaseArrayLayer = dstSubresource.ArrayLayer, LayerCount = 1 }, Extent = new() @@ -238,151 +307,249 @@ protected override void ResolveTextureImpl(Texture src, TextureSlice srcSlice, T } }; - Context.Vk.CmdResolveImage(CommandBuffer, vkSrc.Image, ImageLayout.TransferSrcOptimal, vkDest.Image, ImageLayout.TransferDstOptimal, 1, &imageResolve); + ResolveImageInfo2 resolveImageInfo = new() + { + SType = StructureType.ResolveImageInfo2, + SrcImage = vkSrc.Image, + SrcImageLayout = ImageLayout.TransferSrcOptimal, + DstImage = vkDst.Image, + DstImageLayout = ImageLayout.TransferDstOptimal, + RegionCount = 1, + PRegions = ®ion + }; - vkSrc.TransitionLayout(this, srcSlice, srcOldLayout); - vkDest.TransitionLayout(this, destSlice, destOldLayout); + Context.Vk.CmdResolveImage2(CommandBuffer, &resolveImageInfo); } protected override BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc) { - return new VKBottomLevelAccelerationStructure(Context, desc, this); + return new VKBottomLevelAccelerationStructure(Context, this, desc); } protected override TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc) { - return new VKTopLevelAccelerationStructure(Context, desc, this); + return new VKTopLevelAccelerationStructure(Context, this, desc); } - protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) + protected override void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) { accelerationStructure.Vulkan().Update(this, newDesc); } - protected override void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue) + protected override void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { - VKFrameBuffer vkFrameBuffer = frameBuffer.Vulkan(); - - vkFrameBuffer.PrepareAttachments(this); + accelerationStructure.Vulkan().Update(this, newDesc); + } - bool clearColor = clearValue.Flags.HasFlag(ClearFlags.Color); - bool clearDepth = clearValue.Flags.HasFlag(ClearFlags.Depth); - bool clearStencil = clearValue.Flags.HasFlag(ClearFlags.Stencil); + protected override void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + uint width = 0; + uint height = 0; + bool hasDepth = false; + bool hasStencil = false; - for (int i = 0; i < vkFrameBuffer.ColorAttachmentCount; i++) + RenderingAttachmentInfo* pColorAttachments = stackalloc RenderingAttachmentInfo[colorAttachments.Length]; + for (int i = 0; i < colorAttachments.Length; i++) { - ref RenderingAttachmentInfo colorAttachment = ref vkFrameBuffer.ColorAttachments[i]; - - colorAttachment.LoadOp = AttachmentLoadOp.Load; + ColorAttachment attachment = colorAttachments[i]; - if (clearColor) - { - colorAttachment.LoadOp = AttachmentLoadOp.Clear; + VKTexture texture = attachment.Texture.Vulkan(); - Vector4 color = clearValue.ColorValues[i]; + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, attachment.Subresource.MipLevel, out width, out height, out _); - colorAttachment.ClearValue.Color = new() + 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() { - Float32_0 = color.X, - Float32_1 = color.Y, - Float32_2 = color.Z, - Float32_3 = color.W - }; - } + Color = new() + { + Float32_0 = attachment.ClearColor.X, + Float32_1 = attachment.ClearColor.Y, + Float32_2 = attachment.ClearColor.Z, + Float32_3 = attachment.ClearColor.W + } + } + }; } - if (vkFrameBuffer.HasDepthStencilAttachment) + RenderingAttachmentInfo* pDepthAttachment = stackalloc RenderingAttachmentInfo[depthStencilAttachment.HasValue ? 1 : 0]; + RenderingAttachmentInfo* pStencilAttachment = stackalloc RenderingAttachmentInfo[depthStencilAttachment.HasValue ? 1 : 0]; + if (depthStencilAttachment.HasValue) { - if (vkFrameBuffer.DepthAttachment is not null) - { - ref RenderingAttachmentInfo depthAttachment = ref vkFrameBuffer.DepthAttachment[0]; + DepthStencilAttachment attachment = depthStencilAttachment.Value; + + VKTexture texture = attachment.Texture.Vulkan(); - depthAttachment.LoadOp = AttachmentLoadOp.Load; + ZenithHelper.MipDimensions(texture.Desc.Width, texture.Desc.Height, texture.Desc.Depth, attachment.Subresource.MipLevel, out width, out height, out _); - if (clearDepth) + if (hasDepth = ZenithHelper.HasDepth(texture.Desc.Format)) + { + pDepthAttachment[0] = new() { - depthAttachment.LoadOp = AttachmentLoadOp.Clear; - depthAttachment.ClearValue.DepthStencil.Depth = clearValue.Depth; - } + 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 (vkFrameBuffer.StencilAttachment is not null) + if (hasStencil = ZenithHelper.HasStencil(texture.Desc.Format)) { - ref RenderingAttachmentInfo stencilAttachment = ref vkFrameBuffer.StencilAttachment[0]; - - stencilAttachment.LoadOp = AttachmentLoadOp.Load; - - if (clearStencil) + pStencilAttachment[0] = new() { - stencilAttachment.LoadOp = AttachmentLoadOp.Clear; - stencilAttachment.ClearValue.DepthStencil.Stencil = clearValue.Stencil; - } + 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 + } + } + }; } } - Context.Vk.CmdBeginRendering(CommandBuffer, ref vkFrameBuffer.RenderingInfo); + 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(FrameBuffer frameBuffer) + protected override void EndRenderPassImpl() { Context.Vk.CmdEndRendering(CommandBuffer); + } - frameBuffer.Vulkan().PresentColorAttachments(this); + protected override void SetPipelineImpl(GraphicsPipeline pipeline) + { + Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); } - protected override void SetScissorsImpl(Scissor[] scissors) + protected override void SetPipelineImpl(ComputePipeline pipeline) { - Rect2D[] vkScissors = [.. scissors.Select(static item => new Rect2D(new(item.X, item.Y), new(item.Width, item.Height)))]; + Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Compute, pipeline.Vulkan().Pipeline); + } - Context.Vk.CmdSetScissor(CommandBuffer, 0, (uint)vkScissors.Length, ref vkScissors[0]); + protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + { + Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); } - 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))]; + VkViewport* pViewports = stackalloc VkViewport[viewports.Length]; + for (int i = 0; i < viewports.Length; i++) + { + Viewport viewport = viewports[i]; - Context.Vk.CmdSetViewport(CommandBuffer, 0, (uint)vkViewports.Length, ref vkViewports[0]); + pViewports[i] = new() + { + X = viewport.X, + Y = viewport.Y + viewport.Height, + Width = viewport.Width, + Height = -viewport.Height, + MinDepth = viewport.MinDepth, + MaxDepth = viewport.MaxDepth + }; + } + + Context.Vk.CmdSetViewport(CommandBuffer, 0, (uint)viewports.Length, pViewports); } - protected override void SetPipelineImpl(GraphicsPipeline pipeline) + protected override void SetScissorsImpl(ReadOnlySpan scissors) { - Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); + 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 SetPipelineImpl(ComputePipeline pipeline) + protected override void SetBlendConstantImpl(Vector4 blendConstant) { - Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Compute, pipeline.Vulkan().Pipeline); + Context.Vk.CmdSetBlendConstants(CommandBuffer, &blendConstant.X); } - protected override void SetPipelineImpl(MeshShadingPipeline pipeline) + protected override void SetStencilReferenceImpl(uint stencilReference) { - Context.Vk.CmdBindPipeline(CommandBuffer, PipelineBindPoint.Graphics, pipeline.Vulkan().Pipeline); + Context.Vk.CmdSetStencilReference(CommandBuffer, StencilFaceFlags.FrontAndBack, stencilReference); } - protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint index) + protected override void SetVertexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, uint slot) { - VkBuffer vkBuffer = buffer.Vulkan().Buffer; - ulong vkOffset = offsetInBytes; - - Context.Vk.CmdBindVertexBuffers(CommandBuffer, index, 1, ref vkBuffer, ref vkOffset); + Context.Vk.CmdBindVertexBuffers(CommandBuffer, slot, 1, [buffer.Vulkan().Buffer], [offsetInBytes]); } - protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat format) + protected override void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - Context.Vk.CmdBindIndexBuffer(CommandBuffer, buffer.Vulkan().Buffer, offsetInBytes, VKFormats.Vulkan(format)); + Context.Vk.CmdBindIndexBuffer(CommandBuffer, buffer.Vulkan().Buffer, offsetInBytes, VKFormats.Vulkan(indexFormat)); } - protected override void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable) + protected override void SetConstantBufferImpl(Pipeline pipeline, Buffer buffer, uint offsetInBytes) { - (PipelineBindPoint pipelineBindPoint, PipelineLayout pipelineLayout) = pipeline switch + ulong address = buffer.Vulkan().DeviceAddress + offsetInBytes; + + PushDataInfoEXT pushDataInfo = new() { - GraphicsPipeline graphicsPipeline => (PipelineBindPoint.Graphics, graphicsPipeline.Vulkan().PipelineLayout), - ComputePipeline computePipeline => (PipelineBindPoint.Compute, computePipeline.Vulkan().PipelineLayout), - MeshShadingPipeline meshShadingPipeline => (PipelineBindPoint.Graphics, meshShadingPipeline.Vulkan().PipelineLayout), - _ => (PipelineBindPoint.Graphics, default) + SType = StructureType.PushDataInfoExt(), + Data = new() + { + Address = &address, + Size = sizeof(ulong) + } }; - Context.Vk.CmdBindDescriptorSets(CommandBuffer, pipelineBindPoint, pipelineLayout, 0, 1, ref resourceTable.Vulkan().DescriptorToken.Set, 0, null); + Context.DescriptorHeap?.CmdPushData(CommandBuffer, &pushDataInfo); } protected override void DrawImpl(GraphicsPipeline pipeline, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) @@ -427,9 +594,10 @@ protected override void DispatchMeshIndirectImpl(MeshShadingPipeline pipeline, B protected override void BeginQueryImpl(QueryHeap queryHeap, uint index) { - Context.Vk.CmdResetQueryPool(CommandBuffer, queryHeap.Vulkan().QueryPool, index, 1); + VKQueryHeap vkQueryHeap = queryHeap.Vulkan(); - Context.Vk.CmdBeginQuery(CommandBuffer, queryHeap.Vulkan().QueryPool, index, queryHeap.Desc.Type is QueryType.Occlusion ? QueryControlFlags.PreciseBit : QueryControlFlags.None); + 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) @@ -439,9 +607,10 @@ protected override void EndQueryImpl(QueryHeap queryHeap, uint index) protected override void WriteTimestampImpl(QueryHeap queryHeap, uint index) { - Context.Vk.CmdResetQueryPool(CommandBuffer, queryHeap.Vulkan().QueryPool, index, 1); + VKQueryHeap vkQueryHeap = queryHeap.Vulkan(); - Context.Vk.CmdWriteTimestamp(CommandBuffer, PipelineStageFlags.BottomOfPipeBit, queryHeap.Vulkan().QueryPool, index); + Context.Vk.CmdResetQueryPool(CommandBuffer, vkQueryHeap.QueryPool, index, 1); + Context.Vk.CmdWriteTimestamp2(CommandBuffer, PipelineStageFlags2.BottomOfPipeBit, vkQueryHeap.QueryPool, index); } protected override void BeginDebugEventImpl(string label) @@ -484,6 +653,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() @@ -515,6 +707,6 @@ protected override void Destroy() { base.Destroy(); - Context.Vk.DestroyCommandPool(Context.Device, CommandPool, null); + Context.Vk.DestroyCommandPool(Context.Device, CommandPool, default); } } diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 2b7a7c9a..97fd5a42 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -2,37 +2,82 @@ 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; + + public uint QueueFamilyIndex; + + public VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue queue, uint queueFamilyIndex) : base(context, type) + { + Queue = queue; + QueueFamilyIndex = queueFamilyIndex; + + Timeline = new VKTimeline(context, this); + } + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public uint QueueFamilyIndex => queueFamilyIndex; + public override Timeline Timeline { get; } + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } protected override CommandBuffer CreateCommandBuffer() { return new VKCommandBuffer(Context, this); } - protected override void WaitIdleImpl() + protected override double GetTimestampPeriod(out uint validBits) { - Context.Vk.QueueWaitIdle(queue).Success(); + PhysicalDeviceProperties properties; + Context.Vk.GetPhysicalDeviceProperties(Context.PhysicalDevice, &properties); + + uint queueFamilyCount = 0; + Context.Vk.GetPhysicalDeviceQueueFamilyProperties(Context.PhysicalDevice, &queueFamilyCount, default); + + QueueFamilyProperties* queueFamilies = stackalloc QueueFamilyProperties[(int)queueFamilyCount]; + Context.Vk.GetPhysicalDeviceQueueFamilyProperties(Context.PhysicalDevice, &queueFamilyCount, queueFamilies); + + validBits = queueFamilies[(int)QueueFamilyIndex].TimestampValidBits; + + return properties.Limits.TimestampPeriod; } - protected override void SubmitImpl(CommandBuffer commandBuffer) + protected override void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer) { - VKCommandBuffer vkCommandBuffer = commandBuffer.Vulkan(); - - fixed (VkCommandBuffer* pCommandBuffers = &vkCommandBuffer.CommandBuffer) + SemaphoreSubmitInfo* waitSemaphoreInfos = stackalloc SemaphoreSubmitInfo[waits.Length]; + for (int i = 0; i < waits.Length; i++) { - SubmitInfo submitInfo = new() + TimelineValue wait = waits[i]; + + waitSemaphoreInfos[i] = new() { - SType = StructureType.SubmitInfo, - CommandBufferCount = 1, - PCommandBuffers = pCommandBuffers + SType = StructureType.SemaphoreSubmitInfo, + Semaphore = wait.Timeline.Vulkan().Semaphore, + Value = wait.Value, + StageMask = PipelineStageFlags2.AllCommandsBit }; - - Context.Vk.QueueSubmit(queue, 1, &submitInfo, default).Success(); } + + CommandBufferSubmitInfo commandBufferInfo = new() + { + SType = StructureType.CommandBufferSubmitInfo, + CommandBuffer = commandBuffer.Vulkan().CommandBuffer + }; + + SubmitInfo2 submitInfo = new() + { + SType = StructureType.SubmitInfo2, + WaitSemaphoreInfoCount = (uint)waits.Length, + PWaitSemaphoreInfos = waitSemaphoreInfos, + CommandBufferInfoCount = 1, + PCommandBufferInfos = &commandBufferInfo + }; + + Context.Vk.QueueSubmit2(Queue, 1, &submitInfo, default).Success(); } protected override void SetResourceName(string name) @@ -43,7 +88,7 @@ protected override void SetResourceName(string name) { SType = StructureType.DebugUtilsObjectNameInfoExt, ObjectType = ObjectType.Queue, - ObjectHandle = (ulong)queue.Handle, + ObjectHandle = (ulong)Queue.Handle, PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) }; diff --git a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs index add6f6a1..5df5043c 100644 --- a/sources/Zenith.NET.Vulkan/VKComputePipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKComputePipeline.cs @@ -4,8 +4,6 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKComputePipeline : ComputePipeline { - public PipelineLayout PipelineLayout; - public VkPipeline Pipeline; public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : base(context, desc) @@ -15,28 +13,22 @@ public VKComputePipeline(VKGraphicsContext context, ComputePipelineDesc desc) : ComputePipelineCreateInfo createInfo = new() { SType = StructureType.ComputePipelineCreateInfo, - Stage = desc.Compute.Vulkan().GetPipelineShaderStageCreateInfo(scope) + Stage = desc.ComputeShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.ComputeBit) }; - // ResourceLayout - { - 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]) - }; - - context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - - createInfo.Layout = PipelineLayout; - } + createInfo.AddNext(out PipelineCreateFlags2CreateInfo flags2CreateInfo); + flags2CreateInfo.Flags = PipelineCreateFlags2.Vk2DescriptorHeapBitExt(); - context.Vk.CreateComputePipelines(context.Device, default, 1, &createInfo, null, out Pipeline).Success(); + context.Vk.CreateComputePipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } 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(); @@ -54,7 +46,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); - Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); + Context.Vk.DestroyPipeline(Context.Device, Pipeline, default); } } diff --git a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs b/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs deleted file mode 100644 index 33a3ccb1..00000000 --- a/sources/Zenith.NET.Vulkan/VKDescriptorAllocator.cs +++ /dev/null @@ -1,54 +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(VKResourceLayout resourceLayout) - { - using Lock.Scope _ = @lock.EnterScope(); - - if (available.FirstOrDefault(item => item.CanAllocate(resourceLayout.Counts)) is not VKDescriptorPool pool) - { - available.Add(pool = new(context)); - } - - fixed (DescriptorSetLayout* pSetLayouts = &resourceLayout.DescriptorSetLayout) - { - 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 }; - } - } - - public void Free(VKDescriptorToken token) - { - DescriptorSet set = token.Set; - context.Vk.FreeDescriptorSets(context.Device, token.Pool.Pool, 1, &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/VKDescriptorHeap.cs b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs new file mode 100644 index 00000000..af46c090 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKDescriptorHeap.cs @@ -0,0 +1,118 @@ +using Silk.NET.Vulkan; + +namespace Zenith.NET.Vulkan; + +internal unsafe class VKDescriptorHeap(VKGraphicsContext context, + VKBuffer buffer, + ulong reservedBytes, + VKDescriptorRegion? bufferRegion, + VKDescriptorRegion? imageRegion, + VKDescriptorRegion? samplerRegion) : DisposableObject +{ + private readonly nint pointer = buffer.Map(); + + public DeviceAddressRangeEXT Range => new(buffer.DeviceAddress, buffer.Desc.SizeInBytes); + + public ulong ReservedBytes => reservedBytes; + + public VKDescriptorToken Allocate(ResourceDescriptorInfoEXT info) + { + VKDescriptorRegion? region = info.Type switch + { + DescriptorType.UniformBuffer or + DescriptorType.StorageBuffer or + DescriptorType.AccelerationStructureKhr => bufferRegion, + + DescriptorType.SampledImage or + DescriptorType.StorageImage => imageRegion, + + _ => null + }; + + if (region is null) + { + return default; + } + + VKDescriptorToken token = region.Allocate(pointer, 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(pointer, 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 descriptorHeapProperties); + + context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); + + ulong reservedBytes = descriptorHeapProperties.MinResourceHeapReservedRange; + + ulong bufferStride = descriptorHeapProperties.BufferDescriptorSize; + ulong bufferOffset = ZenithHelper.Align(reservedBytes, bufferStride); + + ulong imageStride = descriptorHeapProperties.ImageDescriptorSize; + ulong imageOffset = ZenithHelper.Align(bufferOffset + (bufferCapacity * bufferStride), imageStride); + + VKBuffer buffer = new(context, new() + { + SizeInBytes = (uint)ZenithHelper.Align(imageOffset + (imageCapacity * imageStride), descriptorHeapProperties.ResourceHeapAlignment), + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.DescriptorHeapBitExt()); + + return new(context, + buffer, + reservedBytes, + new((uint)(bufferOffset / bufferStride), (uint)bufferStride), + new((uint)(imageOffset / imageStride), (uint)imageStride), + null); + } + + public static VKDescriptorHeap CreateSamplerHeap(VKGraphicsContext context, uint samplerCapacity) + { + PhysicalDeviceProperties2 properties2 = new() { SType = StructureType.PhysicalDeviceProperties2 }; + properties2.AddNext(out PhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties); + + context.Vk.GetPhysicalDeviceProperties2(context.PhysicalDevice, &properties2); + + ulong reservedBytes = descriptorHeapProperties.MinSamplerHeapReservedRange; + + ulong samplerStride = descriptorHeapProperties.SamplerDescriptorSize; + ulong samplerOffset = ZenithHelper.Align(reservedBytes, samplerStride); + + VKBuffer buffer = new(context, new() + { + SizeInBytes = (uint)ZenithHelper.Align(samplerOffset + (samplerCapacity * samplerStride), descriptorHeapProperties.SamplerHeapAlignment), + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.DescriptorHeapBitExt()); + + return new(context, + buffer, + reservedBytes, + null, + null, + new((uint)(samplerOffset / samplerStride), (uint)samplerStride)); + } +} 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/VKDescriptorRegion.cs b/sources/Zenith.NET.Vulkan/VKDescriptorRegion.cs new file mode 100644 index 00000000..626ac59b --- /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, uint stride) +{ + private readonly Lock @lock = new(); + private readonly Stack recycled = []; + + private uint head; + + public VKDescriptorToken Allocate(nint pointer, out HostAddressRangeEXT target) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (!recycled.TryPop(out uint index)) + { + index = head++; + } + + index += baseIndex; + + target = new((void*)(pointer + (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 index 498f0fe8..f11b7aa8 100644 --- a/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs +++ b/sources/Zenith.NET.Vulkan/VKDescriptorToken.cs @@ -1,10 +1,13 @@ -using Silk.NET.Vulkan; +namespace Zenith.NET.Vulkan; -namespace Zenith.NET.Vulkan; - -internal record struct VKDescriptorToken +internal readonly struct VKDescriptorToken(VKDescriptorRegion region, uint index) : IDisposable { - public VKDescriptorPool Pool; + public readonly uint Index = index; + + public readonly ResourceHandle ResourceHandle = new(index, 0); - public DescriptorSet Set; + public void Dispose() + { + region.Free(this); + } } 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 index 0da4efd0..33eb87bd 100644 --- a/sources/Zenith.NET.Vulkan/VKFence.cs +++ b/sources/Zenith.NET.Vulkan/VKFence.cs @@ -2,38 +2,29 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKFence : GraphicsResource +internal unsafe class VKFence : DisposableObject { public Fence Fence; - public VKFence(VKGraphicsContext context) : base(context) + public VKFence(VKGraphicsContext context) { - FenceCreateInfo createInfo = new() - { - SType = StructureType.FenceCreateInfo, - Flags = FenceCreateFlags.SignaledBit - }; + FenceCreateInfo createInfo = new() { SType = StructureType.FenceCreateInfo }; - context.Vk.CreateFence(context.Device, &createInfo, null, out Fence).Success(); + context.Vk.CreateFence(context.Device, &createInfo, default, out Fence).Success(); - context.Vk.ResetFences(context.Device, 1, ref Fence).Success(); + Context = context; } - public new VKGraphicsContext Context => (VKGraphicsContext)base.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 SetResourceName(string name) - { - } - protected override void Destroy() { - Context.Vk.DestroyFence(Context.Device, Fence, null); + Context.Vk.DestroyFence(Context.Device, Fence, default); } } diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index fc83082d..948165f0 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -3,129 +3,410 @@ namespace Zenith.NET.Vulkan; -internal static unsafe class VKFormats +internal static class VKFormats { - public static VkShaderStageFlags Vulkan(ShaderStageFlags shaderStageFlags) + public static BuildAccelerationStructureFlagsKHR Vulkan(AccelerationStructureBuildFlags accelerationStructureBuildFlags) { - VkShaderStageFlags result = VkShaderStageFlags.None; + BuildAccelerationStructureFlagsKHR result = default; - if (shaderStageFlags.HasFlag(ShaderStageFlags.Vertex)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowUpdate)) { - result |= VkShaderStageFlags.VertexBit; + result |= BuildAccelerationStructureFlagsKHR.AllowUpdateBitKhr; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Pixel)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.AllowCompaction)) { - result |= VkShaderStageFlags.FragmentBit; + result |= BuildAccelerationStructureFlagsKHR.AllowCompactionBitKhr; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Compute)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastTrace)) { - result |= VkShaderStageFlags.ComputeBit; + result |= BuildAccelerationStructureFlagsKHR.PreferFastTraceBitKhr; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Amplification)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) { - result |= VkShaderStageFlags.TaskBitExt; + result |= BuildAccelerationStructureFlagsKHR.PreferFastBuildBitKhr; } - if (shaderStageFlags.HasFlag(ShaderStageFlags.Mesh)) + if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) { - result |= VkShaderStageFlags.MeshBitExt; + result |= BuildAccelerationStructureFlagsKHR.LowMemoryBitKhr; } return result; } - public static (VkBufferUsageFlags UsageFlags, MemoryPropertyFlags PropertyFlags) Vulkan(BufferUsageFlags bufferUsageFlags) + public static SamplerAddressMode Vulkan(AddressMode addressMode) { - VkBufferUsageFlags usageFlags = VkBufferUsageFlags.TransferSrcBit | VkBufferUsageFlags.TransferDstBit | VkBufferUsageFlags.ShaderDeviceAddressBit; + return addressMode switch + { + AddressMode.Wrap => SamplerAddressMode.Repeat, + AddressMode.Mirror => SamplerAddressMode.MirroredRepeat, + AddressMode.Clamp => SamplerAddressMode.ClampToEdge, + AddressMode.Border => SamplerAddressMode.ClampToBorder, + _ => 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 (bufferUsageFlags.HasFlag(BufferUsageFlags.Vertex)) + if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - usageFlags |= VkBufferUsageFlags.VertexBufferBit; + 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 (bufferUsageFlags.HasFlag(BufferUsageFlags.Index)) + if (barrierStages.HasFlag(BarrierStages.FragmentShading)) { - usageFlags |= VkBufferUsageFlags.IndexBufferBit; + 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 (bufferUsageFlags.HasFlag(BufferUsageFlags.Indirect)) + if (barrierStages.HasFlag(BarrierStages.ComputeShading)) { - usageFlags |= VkBufferUsageFlags.IndirectBufferBit; + stage |= PipelineStageFlags2.ComputeShaderBit | PipelineStageFlags2.DrawIndirectBit; + access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.IndirectCommandReadBit | AccessFlags2.AccelerationStructureReadBitKhr; } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.AccelerationStructure)) + if (barrierStages.HasFlag(BarrierStages.Copy)) { - usageFlags |= VkBufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; + stage |= PipelineStageFlags2.CopyBit; + access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.Constant)) + if (barrierStages.HasFlag(BarrierStages.Resolve)) { - usageFlags |= VkBufferUsageFlags.UniformBufferBit; + stage |= PipelineStageFlags2.ResolveBit; + access |= AccessFlags2.TransferReadBit | AccessFlags2.TransferWriteBit; } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.ShaderResource) || bufferUsageFlags.HasFlag(BufferUsageFlags.UnorderedAccess)) + if (barrierStages.HasFlag(BarrierStages.All)) { - usageFlags |= VkBufferUsageFlags.StorageBufferBit; + stage = PipelineStageFlags2.AllCommandsBit; + access = AccessFlags2.MemoryReadBit | AccessFlags2.MemoryWriteBit; } - MemoryPropertyFlags propertyFlags = MemoryPropertyFlags.DeviceLocalBit; + return (stage, access); + } + + 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 VkBorderColor Vulkan(BorderColor borderColor) + { + return borderColor switch + { + BorderColor.TransparentBlack => VkBorderColor.FloatTransparentBlack, + BorderColor.OpaqueBlack => VkBorderColor.FloatOpaqueBlack, + BorderColor.OpaqueWhite => VkBorderColor.FloatOpaqueWhite, + _ => default + }; + } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead) || bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) + public static BufferUsageFlags Vulkan(BufferUsages bufferUsages, bool rayTracingSupported) + { + BufferUsageFlags result = BufferUsageFlags.ShaderDeviceAddressBit; + + if (bufferUsages.HasFlag(BufferUsages.Vertex)) { - propertyFlags = MemoryPropertyFlags.HostVisibleBit; + result |= BufferUsageFlags.VertexBufferBit; - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapRead)) + if (rayTracingSupported) { - propertyFlags |= MemoryPropertyFlags.HostCachedBit; + result |= BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; } + } - if (bufferUsageFlags.HasFlag(BufferUsageFlags.MapWrite)) + if (bufferUsages.HasFlag(BufferUsages.Index)) + { + result |= BufferUsageFlags.IndexBufferBit; + + if (rayTracingSupported) { - propertyFlags |= MemoryPropertyFlags.HostCoherentBit; + result |= BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; } } - return (usageFlags, propertyFlags); + 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 (rayTracingSupported) + { + result |= BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr; + } + } + + if (bufferUsages.HasFlag(BufferUsages.TransferSrc)) + { + result |= BufferUsageFlags.TransferSrcBit; + } + + if (bufferUsages.HasFlag(BufferUsages.TransferDst)) + { + result |= BufferUsageFlags.TransferDstBit; + } + + return result; } - public static (ImageType Type, ImageViewType ViewType) Vulkan(TextureType textureType) + public static ColorComponentFlags Vulkan(ColorWrites colorWrites) { - return - ( - textureType switch - { - TextureType.Texture1D or - TextureType.Texture1DArray => ImageType.Type1D, + ColorComponentFlags result = default; - TextureType.Texture2D or - TextureType.Texture2DArray or - TextureType.TextureCube or - TextureType.TextureCubeArray => ImageType.Type2D, + if (colorWrites.HasFlag(ColorWrites.Red)) + { + result |= ColorComponentFlags.RBit; + } - TextureType.Texture3D => ImageType.Type3D, + 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 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 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) + { + 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 PolygonMode Vulkan(FillMode fillMode) + { + return fillMode switch + { + FillMode.Solid => PolygonMode.Fill, + FillMode.Wireframe => PolygonMode.Line, + _ => default + }; + } - _ => ImageType.Type1D + public static (Filter Filter, SamplerMipmapMode MipmapMode) Vulkan(FilterMode filterMode) + { + return + ( + filterMode switch + { + FilterMode.Point => Filter.Nearest, + FilterMode.Linear => Filter.Linear, + _ => default }, - textureType switch + filterMode 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 + FilterMode.Point => SamplerMipmapMode.Nearest, + FilterMode.Linear => SamplerMipmapMode.Linear, + _ => default } ); } - public static Format Vulkan(PixelFormat pixelFormat) + public static VkFrontFace Vulkan(FrontFace frontFace) { - return pixelFormat switch + 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 = 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; + } + + 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 { PixelFormat.R8UNorm => Format.R8Unorm, PixelFormat.R8SNorm => Format.R8SNorm, @@ -187,533 +468,249 @@ public static Format Vulkan(PixelFormat pixelFormat) 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 + _ => default }; - } - public static (ImageUsageFlags UsageFlags, ImageAspectFlags AspectFlags) Vulkan(PixelFormat pixelFormat, TextureUsageFlags textureUsageFlags) - { - ImageUsageFlags usageFlags = ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit; + ImageAspectFlags aspectFlags = default; - if (textureUsageFlags.HasFlag(TextureUsageFlags.RenderTarget)) + if (ZenithHelper.HasDepth(pixelFormat)) { - usageFlags |= ImageUsageFlags.ColorAttachmentBit; + aspectFlags |= ImageAspectFlags.DepthBit; } - if (textureUsageFlags.HasFlag(TextureUsageFlags.DepthStencil)) + if (ZenithHelper.HasStencil(pixelFormat)) { - usageFlags |= ImageUsageFlags.DepthStencilAttachmentBit; + aspectFlags |= ImageAspectFlags.StencilBit; } - 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 + if (aspectFlags is 0) { - aspectFlags |= ImageAspectFlags.ColorBit; + 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 - }; + return (format, aspectFlags); } - 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) + public static VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) { - return cullMode switch + return primitiveTopology switch { - CullMode.None => CullModeFlags.None, - CullMode.Front => CullModeFlags.FrontBit, - CullMode.Back => CullModeFlags.BackBit, - _ => CullModeFlags.None + PrimitiveTopology.PointList => VkPrimitiveTopology.PointList, + PrimitiveTopology.LineList => VkPrimitiveTopology.LineList, + PrimitiveTopology.LineStrip => VkPrimitiveTopology.LineStrip, + PrimitiveTopology.TriangleList => VkPrimitiveTopology.TriangleList, + PrimitiveTopology.TriangleStrip => VkPrimitiveTopology.TriangleStrip, + _ => default }; } - public static VkFrontFace Vulkan(FrontFace frontFace) + public static VkQueryType Vulkan(QueryType queryType) { - return frontFace switch + return queryType switch { - FrontFace.CounterClockwise => VkFrontFace.CounterClockwise, - FrontFace.Clockwise => VkFrontFace.Clockwise, - _ => VkFrontFace.CounterClockwise - }; - } + QueryType.Occlusion or + QueryType.BinaryOcclusion => VkQueryType.Occlusion, - 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 - }; - } + QueryType.Timestamp => VkQueryType.Timestamp, - 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 + _ => 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, - _ => VkBlendOp.Add + RayTracingGeometryType.Triangle => GeometryTypeKHR.TrianglesKhr, + RayTracingGeometryType.Aabb => GeometryTypeKHR.AabbsKhr, + _ => default }; } - public static VkColorComponentFlags Vulkan(ColorComponentFlags colorComponentFlags) + public static GeometryInstanceFlagsKHR Vulkan(RayTracingInstanceFlags rayTracingInstanceFlags) { - VkColorComponentFlags result = VkColorComponentFlags.None; + GeometryInstanceFlagsKHR result = default; - if (colorComponentFlags.HasFlag(ColorComponentFlags.Red)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.FrontCounterClockwise)) { - result |= VkColorComponentFlags.RBit; + result |= GeometryInstanceFlagsKHR.TriangleFrontCounterclockwiseBitKhr; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Green)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.DisableCull)) { - result |= VkColorComponentFlags.GBit; + result |= GeometryInstanceFlagsKHR.TriangleFacingCullDisableBitKhr; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Blue)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) { - result |= VkColorComponentFlags.BBit; + result |= GeometryInstanceFlagsKHR.ForceOpaqueBitKhr; } - if (colorComponentFlags.HasFlag(ColorComponentFlags.Alpha)) + if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNonOpaque)) { - result |= VkColorComponentFlags.ABit; + result |= GeometryInstanceFlagsKHR.ForceNoOpaqueBitKhr; } return result; } - public static Format Vulkan(ElementFormat elementFormat) + public static SampleCountFlags Vulkan(SampleCount sampleCount) { - return elementFormat switch + return sampleCount 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 + 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 VkPrimitiveTopology Vulkan(PrimitiveTopology primitiveTopology) + public static VkStencilOp Vulkan(StencilOp stencilOp) { - return primitiveTopology switch + return stencilOp switch { - PrimitiveTopology.PointList => VkPrimitiveTopology.PointList, - PrimitiveTopology.LineList => VkPrimitiveTopology.LineList, - PrimitiveTopology.LineStrip => VkPrimitiveTopology.LineStrip, - PrimitiveTopology.TriangleList => VkPrimitiveTopology.TriangleList, - PrimitiveTopology.TriangleStrip => VkPrimitiveTopology.TriangleStrip, - _ => VkPrimitiveTopology.PointList + 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 }; } - public static VkQueryType Vulkan(QueryType queryType) + public static AttachmentStoreOp Vulkan(StoreOp storeOp) { - return queryType switch + return storeOp switch { - QueryType.Occlusion or QueryType.BinaryOcclusion => VkQueryType.Occlusion, - QueryType.Timestamp => VkQueryType.Timestamp, - _ => VkQueryType.Occlusion + StoreOp.Store => AttachmentStoreOp.Store, + StoreOp.DontCare => AttachmentStoreOp.DontCare, + _ => default }; } - public static IndexType Vulkan(IndexFormat indexFormat) + public static (PipelineStageFlags2 Stage, AccessFlags2 Access, ImageLayout Layout) Vulkan(TextureLayout textureLayout) { - return indexFormat switch - { - IndexFormat.UInt16 => IndexType.Uint16, - IndexFormat.UInt32 => IndexType.Uint32, - _ => IndexType.Uint16 + return textureLayout switch + { + TextureLayout.Undefined => (PipelineStageFlags2.None, AccessFlags2.None, ImageLayout.Undefined), + TextureLayout.Common => (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), + _ => (default, default, default) }; } - 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) + public static (ImageType Type, ImageViewType ViewType) Vulkan(TextureType textureType) { - return rayTracingGeometryType switch - { - RayTracingGeometryType.Triangles => GeometryTypeKHR.TrianglesKhr, - RayTracingGeometryType.AABBs => GeometryTypeKHR.AabbsKhr, - _ => GeometryTypeKHR.TrianglesKhr - }; - } + return + ( + textureType switch + { + TextureType.Texture1D or + TextureType.Texture1DArray => ImageType.Type1D, - public static GeometryFlagsKHR Vulkan(RayTracingGeometryFlags rayTracingGeometryFlags) - { - GeometryFlagsKHR result = GeometryFlagsKHR.None; + TextureType.Texture2D or + TextureType.Texture2DArray or + TextureType.TextureCube or + TextureType.TextureCubeArray => ImageType.Type2D, - if (rayTracingGeometryFlags.HasFlag(RayTracingGeometryFlags.Opaque)) - { - result |= GeometryFlagsKHR.OpaqueBitKhr; - } + TextureType.Texture3D => ImageType.Type3D, - return result; + _ => 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 BuildAccelerationStructureFlagsKHR Vulkan(AccelerationStructureBuildFlags accelerationStructureBuildFlags) + public static ImageUsageFlags Vulkan(TextureUsages textureUsages) { - 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; - } + ImageUsageFlags result = default; - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.PreferFastBuild)) + if (textureUsages.HasFlag(TextureUsages.Sampled)) { - result |= BuildAccelerationStructureFlagsKHR.PreferFastBuildBitKhr; + result |= ImageUsageFlags.SampledBit; } - if (accelerationStructureBuildFlags.HasFlag(AccelerationStructureBuildFlags.MinimizeMemory)) + if (textureUsages.HasFlag(TextureUsages.Storage)) { - result |= BuildAccelerationStructureFlagsKHR.LowMemoryBitKhr; + result |= ImageUsageFlags.StorageBit; } - return result; - } - - public static GeometryInstanceFlagsKHR Vulkan(RayTracingInstanceFlags rayTracingInstanceFlags) - { - GeometryInstanceFlagsKHR result = GeometryInstanceFlagsKHR.None; - - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleCullDisable)) + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) { - result |= GeometryInstanceFlagsKHR.TriangleFacingCullDisableBitKhr; + result |= ImageUsageFlags.ColorAttachmentBit; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.TriangleFrontCounterClockwise)) + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { - result |= GeometryInstanceFlagsKHR.TriangleFrontCounterclockwiseBitKhr; + result |= ImageUsageFlags.DepthStencilAttachmentBit; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceOpaque)) + if (textureUsages.HasFlag(TextureUsages.TransferSrc)) { - result |= GeometryInstanceFlagsKHR.ForceOpaqueBitKhr; + result |= ImageUsageFlags.TransferSrcBit; } - if (rayTracingInstanceFlags.HasFlag(RayTracingInstanceFlags.ForceNoOpaque)) + if (textureUsages.HasFlag(TextureUsages.TransferDst)) { - result |= GeometryInstanceFlagsKHR.ForceNoOpaqueBitKhr; + result |= ImageUsageFlags.TransferDstBit; } return result; 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 5fd94b41..1b200450 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -1,12 +1,13 @@ 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 unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(Backend.Vulkan, useValidationLayer) +internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsContext(GraphicsApi.Vulkan, useValidationLayer) { private static readonly string[] InstanceLayers = [ @@ -16,22 +17,28 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte private static readonly string[] InstanceExtensions = [ ExtDebugUtils.ExtensionName, + ExtMetalSurface.ExtensionName, + KhrAndroidSurface.ExtensionName, KhrSurface.ExtensionName, - KhrWin32Surface.ExtensionName, KhrWaylandSurface.ExtensionName, - KhrXlibSurface.ExtensionName, - KhrAndroidSurface.ExtensionName, - ExtMetalSurface.ExtensionName + KhrWin32Surface.ExtensionName, + KhrXlibSurface.ExtensionName ]; private static readonly string[] DeviceExtensions = [ - KhrSwapchain.ExtensionName, - KhrExternalMemoryWin32.ExtensionName, - KhrRayQuery.ExtensionName, + AndroidExternalMemoryAndroidHardwareBuffer.ExtensionName, + ExtDescriptorHeap.ExtensionName, + ExtMeshShader.ExtensionName, + ExtMetalObjects.ExtensionName, KhrAccelerationStructure.ExtensionName, KhrDeferredHostOperations.ExtensionName, - ExtMeshShader.ExtensionName + KhrExternalMemoryFd.ExtensionName, + KhrExternalMemoryWin32.ExtensionName, + KhrFragmentShadingRate.ExtensionName, + KhrRayQuery.ExtensionName, + KhrShaderUntypedPointers.ExtensionName, + KhrSwapchain.ExtensionName ]; public Instance Instance; @@ -40,78 +47,96 @@ internal unsafe class VKGraphicsContext(bool useValidationLayer) : GraphicsConte public Device Device; - public Queue GraphicsQueue; - - public Queue ComputeQueue; - - public Queue CopyQueue; - public Vk Vk { get; } = Vk.GetApi(); - public VKDescriptorAllocator DescriptorAllocator => field ??= new(this); - public ExtDebugUtils? DebugUtils { get; private set; } - public KhrSurface? Surface { get; private set; } + public ExtMetalSurface? MetalSurface { get; private set; } - public KhrWin32Surface? Win32Surface { get; private set; } + public KhrAndroidSurface? AndroidSurface { get; private set; } + + public KhrSurface? Surface { get; private set; } public KhrWaylandSurface? WaylandSurface { get; private set; } - public KhrXlibSurface? XlibSurface { get; private set; } + public KhrWin32Surface? Win32Surface { get; private set; } - public KhrAndroidSurface? AndroidSurface { get; private set; } + public KhrXlibSurface? XlibSurface { get; private set; } - public ExtMetalSurface? MetalSurface { get; private set; } + public AndroidExternalMemoryAndroidHardwareBuffer? ExternalMemoryAndroidHardwareBuffer { get; private set; } - public uint[] QueueFamilyIndices { get; private set; } = []; + public ExtDescriptorHeap? DescriptorHeap { get; private set; } - public KhrSwapchain? Swapchain { get; private set; } + public ExtMeshShader? MeshShader { get; private set; } - public KhrExternalMemoryWin32? ExternalMemoryWin32 { get; private set; } + public ExtMetalObjects? MetalObjects { get; private set; } public KhrAccelerationStructure? AccelerationStructure { get; private set; } public KhrDeferredHostOperations? DeferredHostOperations { get; private set; } - public ExtMeshShader? MeshShader { get; private set; } + public KhrExternalMemoryFd? ExternalMemoryFd { get; private set; } - public (SharingMode SharingMode, uint QueueFamilyIndexCount, nint PQueueFamilyIndices) GetSharingModeInfo(ZenithMarshal.Scope scope) - { - if (QueueFamilyIndices.Length is 1) - { - return (SharingMode.Exclusive, 0, 0); - } - else - { - return (SharingMode.Concurrent, (uint)QueueFamilyIndices.Length, ZenithMarshal.AllocateAndFill(scope, QueueFamilyIndices)); - } - } + public KhrExternalMemoryWin32? ExternalMemoryWin32 { get; private set; } + + public KhrFragmentShadingRate? FragmentShadingRate { get; private set; } + + public KhrSwapchain? Swapchain { get; private set; } + + public QueueFamilies QueueFamilies { get; private set; } - public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryPropertyFlags flags) + 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; Vk.GetPhysicalDeviceMemoryProperties(PhysicalDevice, &properties); - uint index = 0; - foreach (MemoryType memoryType in properties.MemoryTypes.AsSpan()) + 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) { - if ((memoryTypeBits & (1 << (int)index)) is not 0 && memoryType.PropertyFlags.HasFlag(flags)) + for (uint index = 0; index < properties.MemoryTypeCount; index++) { - break; + if ((memoryTypeBits & (1u << (int)index)) is not 0 && (properties.MemoryTypes[(int)index].PropertyFlags & propertyFlags) == propertyFlags) + { + return index; + } } - - index++; } - return index; + return 0; + } + + public override nint GetNativeObject(NativeObjectType type) + { + 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 transferQueue, out ValidationLayer? validationLayer) { Version32 apiVersion = new(1, 4, 0); @@ -121,10 +146,10 @@ protected override void Initialize(bool useValidationLayer, // Create instance { uint extensionCount = 0; - Vk.EnumerateInstanceExtensionProperties((byte*)null, &extensionCount, (ExtensionProperties*)null).Success(); + Vk.EnumerateInstanceExtensionProperties(default(byte*), &extensionCount, default).Success(); ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); - Vk.EnumerateInstanceExtensionProperties((byte*)null, &extensionCount, extensions).Success(); + 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)]; @@ -133,9 +158,7 @@ protected override void Initialize(bool useValidationLayer, { 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 }; @@ -150,7 +173,7 @@ protected override void Initialize(bool useValidationLayer, if (useValidationLayer) { uint layerCount = 0; - Vk.EnumerateInstanceLayerProperties(&layerCount, (LayerProperties*)null).Success(); + Vk.EnumerateInstanceLayerProperties(&layerCount, default).Success(); LayerProperties* layers = (LayerProperties*)ZenithMarshal.Allocate(scope, layerCount); Vk.EnumerateInstanceLayerProperties(&layerCount, layers).Success(); @@ -162,23 +185,25 @@ protected override void Initialize(bool useValidationLayer, createInfo.PpEnabledLayerNames = (byte**)ZenithMarshal.StringArrayToPointer(scope, enabledLayers, StringEncoding.UTF8); } - Vk.CreateInstance(&createInfo, null, out Instance).Success(); + 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; - Win32Surface = enabledExtensions.Contains(KhrWin32Surface.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; - AndroidSurface = enabledExtensions.Contains(KhrAndroidSurface.ExtensionName) ? new(context) : null; - MetalSurface = enabledExtensions.Contains(ExtMetalSurface.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; - Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, (PhysicalDevice*)null).Success(); + Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, default).Success(); PhysicalDevice* physicalDevices = (PhysicalDevice*)ZenithMarshal.Allocate(scope, physicalDeviceCount); Vk.EnumeratePhysicalDevices(Instance, &physicalDeviceCount, physicalDevices).Success(); @@ -197,31 +222,58 @@ protected override void Initialize(bool useValidationLayer, continue; } - ulong score = 0; + 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 (properties.DeviceType == PhysicalDeviceType.DiscreteGpu) + if (features.SamplerAnisotropy) { - score += 100000; + score += 2000; } - else if (properties.DeviceType == PhysicalDeviceType.IntegratedGpu) + + if (features.MultiDrawIndirect) { - score += 10000; + score += 1000; } - else if (properties.DeviceType == PhysicalDeviceType.VirtualGpu) + + if (features.DrawIndirectFirstInstance) { 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 (features.IndependentBlend) + { + score += 500; + } + + if (features.FillModeNonSolid) + { + score += 250; + } + + if (features.TextureCompressionBC) + { + score += 500; + } + + if (features.ShaderInt64) + { + score += 250; + } if (score > bestScore) { @@ -233,7 +285,7 @@ protected override void Initialize(bool useValidationLayer, if (PhysicalDevice.Handle is 0) { - throw new NotSupportedException("No suitable Vulkan physical device found."); + throw new NotSupportedException("This device does not support Vulkan 1.4 or higher."); } uint graphicsQueueFamilyIndex = 0; @@ -242,11 +294,11 @@ protected override void Initialize(bool useValidationLayer, uint computeQueueFamilyIndex = 0; uint computeQueueFamilyCount = 0; - uint copyQueueFamilyIndex = 0; - uint copyQueueFamilyCount = 0; + uint transferQueueFamilyIndex = 0; + uint transferQueueFamilyCount = 0; uint queueFamilyCount = 0; - Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, (QueueFamilyProperties*)null); + Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, default); QueueFamilyProperties* queueFamilies = (QueueFamilyProperties*)ZenithMarshal.Allocate(scope, queueFamilyCount); Vk.GetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &queueFamilyCount, queueFamilies); @@ -264,20 +316,20 @@ protected override void Initialize(bool useValidationLayer, computeQueueFamilyIndex = index; computeQueueFamilyCount = queueFamilyProperties.QueueCount; } - else if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.TransferBit) && queueFamilyProperties.QueueCount > copyQueueFamilyCount) + else if (queueFamilyProperties.QueueFlags.HasFlag(QueueFlags.TransferBit) && queueFamilyProperties.QueueCount > transferQueueFamilyCount) { - copyQueueFamilyIndex = index; - copyQueueFamilyCount = queueFamilyProperties.QueueCount; + transferQueueFamilyIndex = index; + transferQueueFamilyCount = queueFamilyProperties.QueueCount; } index++; } - HashSet queueFamilyIndices = [graphicsQueueFamilyIndex, computeQueueFamilyIndex, copyQueueFamilyIndex]; + HashSet queueFamilyIndices = [graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex]; uint queueCreateInfoCount; DeviceQueueCreateInfo* queueCreateInfos; - Func<(Queue GraphicsQueue, Queue ComputeQueue, Queue CopyQueue, uint[] QueueFamilyIndices)> getQueues; + Action loadQueues; if (queueFamilyIndices.Count is 3) { queueCreateInfoCount = 3; @@ -306,12 +358,12 @@ protected override void Initialize(bool useValidationLayer, queueCreateInfos[2] = new() { SType = StructureType.DeviceQueueCreateInfo, - QueueFamilyIndex = copyQueueFamilyIndex, + QueueFamilyIndex = transferQueueFamilyIndex, QueueCount = 1, PQueuePriorities = queuePriorities }; - getQueues = () => + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); @@ -319,10 +371,10 @@ protected override void Initialize(bool useValidationLayer, Queue computeQueue = default; Vk.GetDeviceQueue(Device, computeQueueFamilyIndex, 0, &computeQueue); - Queue copyQueue = default; - Vk.GetDeviceQueue(Device, copyQueueFamilyIndex, 0, ©Queue); + Queue transferQueue = default; + Vk.GetDeviceQueue(Device, transferQueueFamilyIndex, 0, &transferQueue); - return (graphicsQueue, computeQueue, copyQueue, [graphicsQueueFamilyIndex, computeQueueFamilyIndex, copyQueueFamilyIndex]); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, computeQueueFamilyIndex, transferQueue, transferQueueFamilyIndex); }; } else if (graphicsQueueFamilyCount >= 3) @@ -343,7 +395,7 @@ protected override void Initialize(bool useValidationLayer, PQueuePriorities = queuePriorities }; - getQueues = () => + loadQueues = () => { Queue graphicsQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 0, &graphicsQueue); @@ -351,10 +403,10 @@ protected override void Initialize(bool useValidationLayer, Queue computeQueue = default; Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 1, &computeQueue); - Queue copyQueue = default; - Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, ©Queue); + Queue transferQueue = default; + Vk.GetDeviceQueue(Device, graphicsQueueFamilyIndex, 2, &transferQueue); - return (graphicsQueue, computeQueue, copyQueue, [graphicsQueueFamilyIndex]); + queues = (graphicsQueue, graphicsQueueFamilyIndex, computeQueue, graphicsQueueFamilyIndex, transferQueue, graphicsQueueFamilyIndex); }; } else @@ -373,20 +425,20 @@ 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); }; } uint extensionCount = 0; - Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, (byte*)null, &extensionCount, (ExtensionProperties*)null).Success(); + Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, default(byte*), &extensionCount, default).Success(); ExtensionProperties* extensions = (ExtensionProperties*)ZenithMarshal.Allocate(scope, extensionCount); - Vk.EnumerateDeviceExtensionProperties(PhysicalDevice, (byte*)null, &extensionCount, extensions).Success(); + 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)]; @@ -401,42 +453,67 @@ protected override void Initialize(bool useValidationLayer, }; createInfo.AddNext(out PhysicalDeviceFeatures2 features2); - createInfo.AddNext(out PhysicalDeviceVulkan14Features _); - createInfo.AddNext(out PhysicalDeviceVulkan13Features _); - createInfo.AddNext(out PhysicalDeviceVulkan12Features _); createInfo.AddNext(out PhysicalDeviceVulkan11Features _); + createInfo.AddNext(out PhysicalDeviceVulkan12Features _); + createInfo.AddNext(out PhysicalDeviceVulkan13Features _); + createInfo.AddNext(out PhysicalDeviceVulkan14Features _); - if (enabledExtensions.Contains(KhrRayQuery.ExtensionName)) + if (enabledExtensions.Contains(ExtDescriptorHeap.ExtensionName)) { - createInfo.AddNext(out PhysicalDeviceRayQueryFeaturesKHR _); - createInfo.AddNext(out PhysicalDeviceAccelerationStructureFeaturesKHR _); + 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 _); } - Vk.GetPhysicalDeviceFeatures2(PhysicalDevice, &features2); + if (enabledExtensions.Contains(KhrRayQuery.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceRayQueryFeaturesKHR _); + } - Vk.CreateDevice(PhysicalDevice, &createInfo, null, out Device).Success(); + if (enabledExtensions.Contains(KhrShaderUntypedPointers.ExtensionName)) + { + createInfo.AddNext(out PhysicalDeviceShaderUntypedPointersFeaturesKHR _); + } - (GraphicsQueue, ComputeQueue, CopyQueue, QueueFamilyIndices) = getQueues(); + 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))); - Swapchain = enabledExtensions.Contains(KhrSwapchain.ExtensionName) ? new(context) : null; - ExternalMemoryWin32 = enabledExtensions.Contains(KhrExternalMemoryWin32.ExtensionName) ? new(context) : null; + 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; - MeshShader = enabledExtensions.Contains(ExtMeshShader.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; + + loadQueues(); + + QueueFamilies = new([.. new HashSet() { queues.GraphicsQueueFamilyIndex, queues.ComputeQueueFamilyIndex, queues.TransferQueueFamilyIndex }]); } 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]); + 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); validationLayer = useValidationLayer ? new VKValidationLayer(this) : null; } @@ -445,14 +522,46 @@ protected override SwapChain CreateSwapChainImpl(SwapChainDesc desc) return new VKSwapChain(this, desc); } - protected override FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc) + protected override Heap CreateHeapImpl(HeapDesc desc) { - return new VKFrameBuffer(this, desc); + return new VKHeap(this, desc); } - protected override Shader CreateShaderImpl(ShaderDesc desc) + protected override SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc) { - return new VKShader(this, desc); + BufferCreateInfo createInfo = VKBuffer.CreateInfo(desc, Capabilities, QueueFamilies); + + DeviceBufferMemoryRequirements requirements = new() + { + SType = StructureType.DeviceBufferMemoryRequirements, + PCreateInfo = &createInfo + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + + Vk.GetDeviceBufferMemoryRequirements(Device, &requirements, &requirements2); + + return new(requirements2.MemoryRequirements.Size, requirements2.MemoryRequirements.Alignment); + } + + protected override SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc) + { + ImageCreateInfo createInfo = VKTexture.CreateInfo(desc, QueueFamilies); + + DeviceImageMemoryRequirements requirements = new() + { + SType = StructureType.DeviceImageMemoryRequirements, + PCreateInfo = &createInfo + }; + + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + + Vk.GetDeviceImageMemoryRequirements(Device, &requirements, &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) @@ -470,6 +579,101 @@ protected override Texture CreateTextureImpl(TextureDesc desc) return new VKTexture(this, desc); } + protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) + { + 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) { return new VKTextureView(this, desc); @@ -480,14 +684,9 @@ 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) + protected override Shader CreateShaderImpl(ShaderDesc desc) { - return new VKResourceTable(this, desc); + return new VKShader(this, desc); } protected override GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc) @@ -512,15 +711,15 @@ protected override QueryHeap CreateQueryHeapImpl(QueryHeapDesc desc) protected override void Destroy() { - Vk.DeviceWaitIdle(Device).Success(); - base.Destroy(); - DescriptorAllocator.Dispose(); + SamplerHeap.Dispose(); + ResourceHeap.Dispose(); - Vk.DestroyDevice(Device, null); - Vk.DestroyInstance(Instance, null); + QueueFamilies.Dispose(); + Vk.DestroyDevice(Device, default); + Vk.DestroyInstance(Instance, default); Vk.Dispose(); } -} +} \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs index 536f9d1a..93fce2b9 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsPipeline.cs @@ -4,8 +4,6 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKGraphicsPipeline : GraphicsPipeline { - public PipelineLayout PipelineLayout; - public VkPipeline Pipeline; public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) : base(context, desc) @@ -18,150 +16,11 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) StageCount = 2, PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, [ - desc.Vertex.Vulkan().GetPipelineShaderStageCreateInfo(scope), - desc.Pixel.Vulkan().GetPipelineShaderStageCreateInfo(scope) + desc.VertexShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.VertexBit), + desc.FragmentShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.FragmentBit) ]) }; - // 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 = desc.RenderStates.StencilReference - }, - 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 = desc.RenderStates.StencilReference - }, - 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 - }; - - 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; - createInfo.PViewportState = &viewportState; - createInfo.PMultisampleState = &multisampleState; - createInfo.PNext = &rendering; - } - - // ResourceLayout - { - 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]) - }; - - context.Vk.CreatePipelineLayout(context.Device, &pipelineLayoutCreateInfo, null, out PipelineLayout).Success(); - - createInfo.Layout = PipelineLayout; - } - // InputLayouts { uint vertexBindingDescriptionCount = (uint)desc.InputLayouts.Length; @@ -219,20 +78,151 @@ public VKGraphicsPipeline(VKGraphicsContext context, GraphicsPipelineDesc desc) 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).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() { 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.BlendConstants, DynamicState.StencilReference]) }; createInfo.PDynamicState = &dynamicState; - context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, null, out Pipeline).Success(); + createInfo.AddNext(out PipelineCreateFlags2CreateInfo flags2CreateInfo); + flags2CreateInfo.Flags = PipelineCreateFlags2.Vk2DescriptorHeapBitExt(); + + context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } 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(); @@ -250,7 +240,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); - Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); + Context.Vk.DestroyPipeline(Context.Device, Pipeline, default); } } diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs new file mode 100644 index 00000000..8971ba17 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -0,0 +1,70 @@ +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) + { + MemoryAllocateInfo allocateInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = desc.SizeInBytes, + MemoryTypeIndex = context.FindMemoryTypeIndex(uint.MaxValue, desc.Residency) + }; + + allocateInfo.AddNext(out MemoryAllocateFlagsInfo flagsInfo); + flagsInfo.Flags = MemoryAllocateFlags.DeviceAddressBit; + + context.Vk.AllocateMemory(context.Device, &allocateInfo, default, out DeviceMemory).Success(); + } + + public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; + + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) + { + 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(); + + return new VKBuffer(Context, desc, buffer, new(DeviceMemory, offsetInBytes, true, false)); + } + + protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc 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(); + + return new VKTexture(Context, desc, image, new(DeviceMemory, offsetInBytes, true, false)); + } + + 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); + } +} diff --git a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs index 02b06649..8fcfe963 100644 --- a/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs +++ b/sources/Zenith.NET.Vulkan/VKMeshShadingPipeline.cs @@ -4,8 +4,6 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKMeshShadingPipeline : MeshShadingPipeline { - public PipelineLayout PipelineLayout; - public VkPipeline Pipeline; public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc desc) : base(context, desc) @@ -14,196 +12,182 @@ public VKMeshShadingPipeline(VKGraphicsContext context, MeshShadingPipelineDesc GraphicsPipelineCreateInfo createInfo = new() { - SType = StructureType.GraphicsPipelineCreateInfo + SType = StructureType.GraphicsPipelineCreateInfo, + StageCount = 2, + PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, + [ + desc.MeshShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.MeshBitExt), + desc.FragmentShader.Vulkan().GetPipelineShaderStageCreateInfo(scope, ShaderStageFlags.FragmentBit) + ]) }; - // RenderStates - Output + if (desc.TaskShader is not null) { - BlendStateRenderTarget[] blendStateRenderTargets = + createInfo.StageCount = 3; + createInfo.PStages = (PipelineShaderStageCreateInfo*)ZenithMarshal.AllocateAndFill(scope, [ - 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 - ]; + 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).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; + } - uint colorAttachmentCount = (uint)desc.Output.ColorAttachments.Length; + 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 + ]; - PipelineColorBlendAttachmentState* attachments = (PipelineColorBlendAttachmentState*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - Format* colorAttachmentFormats = (Format*)ZenithMarshal.Allocate(scope, colorAttachmentCount); - for (uint i = 0; i < colorAttachmentCount; i++) + uint attachmentCount = (uint)desc.AttachmentFormats.ColorFormats.Length; + PipelineColorBlendAttachmentState* attachments = (PipelineColorBlendAttachmentState*)ZenithMarshal.Allocate(scope, attachmentCount); + for (uint i = 0; i < attachmentCount; i++) { - BlendStateRenderTarget target = desc.RenderStates.BlendState.IndependentBlendEnable ? blendStateRenderTargets[i] : blendStateRenderTargets[0]; + ColorAttachmentBlendState state = desc.RenderState.Blend.IsIndependentBlendEnabled ? states[i] : states[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) + 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) }; - - 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, + 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.RenderStates.DepthStencilState.DepthEnable, - DepthWriteEnable = desc.RenderStates.DepthStencilState.DepthWriteEnable, - DepthCompareOp = VKFormats.Vulkan(desc.RenderStates.DepthStencilState.DepthFunc), - StencilTestEnable = desc.RenderStates.DepthStencilState.StencilEnable, + 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.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 = desc.RenderStates.StencilReference + 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.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 = desc.RenderStates.StencilReference + 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 = colorAttachmentCount, + AttachmentCount = attachmentCount, PAttachments = attachments }; + PipelineViewportStateCreateInfo viewportState = new() { SType = StructureType.PipelineViewportStateCreateInfo, - ViewportCount = Math.Max(colorAttachmentCount, 1), - ScissorCount = Math.Max(colorAttachmentCount, 1) + ViewportCount = attachmentCount, + ScissorCount = attachmentCount }; + 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 + RasterizationSamples = VKFormats.Vulkan(desc.AttachmentFormats.SampleCount), + AlphaToCoverageEnable = desc.RenderState.Blend.IsAlphaToCoverageEnabled }; - 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; 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; - } - - // ResourceLayout - { - 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]) - }; - - 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 = 2, - PDynamicStates = (DynamicState*)ZenithMarshal.AllocateAndFill(scope, [DynamicState.Viewport, DynamicState.Scissor]) + 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, null, out Pipeline).Success(); + createInfo.AddNext(out PipelineCreateFlags2CreateInfo flags2CreateInfo); + flags2CreateInfo.Flags = PipelineCreateFlags2.Vk2DescriptorHeapBitExt(); + + context.Vk.CreateGraphicsPipelines(context.Device, default, 1, &createInfo, default, out Pipeline).Success(); } 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(); @@ -221,7 +205,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.Vk.DestroyPipeline(Context.Device, Pipeline, null); - Context.Vk.DestroyPipelineLayout(Context.Device, PipelineLayout, null); + Context.Vk.DestroyPipeline(Context.Device, Pipeline, default); } } diff --git a/sources/Zenith.NET.Vulkan/VKQueryHeap.cs b/sources/Zenith.NET.Vulkan/VKQueryHeap.cs index 792b5fc7..4625e288 100644 --- a/sources/Zenith.NET.Vulkan/VKQueryHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKQueryHeap.cs @@ -15,13 +15,18 @@ public VKQueryHeap(VKGraphicsContext context, QueryHeapDesc desc) : base(context QueryCount = desc.Count }; - context.Vk.CreateQueryPool(context.Device, &createInfo, null, out QueryPool).Success(); + 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) @@ -30,7 +35,7 @@ protected override void GetResultsImpl(Span results, uint startIndex) QueryPool, startIndex, (uint)results.Length, - (uint)(sizeof(ulong) * results.Length), + (nuint)(sizeof(ulong) * results.Length), pResults, sizeof(ulong), QueryResultFlags.Result64Bit).Success(); @@ -54,6 +59,6 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.Vk.DestroyQueryPool(Context.Device, QueryPool, null); + Context.Vk.DestroyQueryPool(Context.Device, QueryPool, default); } } 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 deleted file mode 100644 index 1c1cd3a3..00000000 --- a/sources/Zenith.NET.Vulkan/VKResourceTable.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKResourceTable : ResourceTable -{ - public VKDescriptorToken DescriptorToken; - - public VKResourceTable(VKGraphicsContext context, ResourceTableDesc desc) : base(context, desc) - { - using ZenithMarshal.Scope scope = new(); - - DescriptorToken = context.DescriptorAllocator.Allocate(desc.Layout.Vulkan()); - - WriteDescriptorSet* descriptorWrites = (WriteDescriptorSet*)ZenithMarshal.Allocate(scope, (uint)desc.Layout.Desc.Bindings.Length); - WriteDescriptorSetAccelerationStructureKHR* accelerationStructureWrites = (WriteDescriptorSetAccelerationStructureKHR*)ZenithMarshal.Allocate(scope, (uint)desc.Layout.Desc.Bindings.Length); - - uint resourceStartIndex = 0; - List srvTextureViews = []; - List uavTextureViews = []; - - for (int i = 0; i < desc.Layout.Desc.Bindings.Length; i++) - { - 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); - - 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 - }; - - 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) - { - bufferInfos[j] = buffer.Vulkan().View.BufferInfo; - } - else if (resource is BufferView bufferView) - { - bufferInfos[j] = bufferView.Vulkan().BufferInfo; - } - break; - - 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()); - } - } - else if (resource is Texture texture) - { - imageInfos[j] = texture.Vulkan().View.UavImageInfo; - - uavTextureViews.Add(texture.Vulkan().View); - } - else if (resource is TextureView textureView) - { - imageInfos[j] = textureView.Vulkan().UavImageInfo; - - uavTextureViews.Add(textureView.Vulkan()); - } - break; - - case ResourceType.Sampler: - if (resource is Sampler sampler) - { - imageInfos[j] = new() { Sampler = sampler.Vulkan().Sampler }; - } - break; - - case ResourceType.AccelerationStructure: - if (resource is TopLevelAccelerationStructure topLevelAccelerationStructure) - { - accelerationStructures[j] = topLevelAccelerationStructure.Vulkan().AccelerationStructure; - } - break; - } - } - - resourceStartIndex += binding.Count; - } - - context.Vk.UpdateDescriptorSets(context.Device, (uint)desc.Layout.Desc.Bindings.Length, descriptorWrites, 0, (CopyDescriptorSet*)null); - - SrvTextureViews = [.. srvTextureViews]; - UavTextureViews = [.. uavTextureViews]; - } - - 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) - { - textureView.TransitionLayout(vkCommandBuffer, ImageLayout.ShaderReadOnlyOptimal); - } - - foreach (VKTextureView textureView in UavTextureViews) - { - textureView.TransitionLayout(vkCommandBuffer, ImageLayout.General); - } - } - - 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); - } -} \ No newline at end of file diff --git a/sources/Zenith.NET.Vulkan/VKSampler.cs b/sources/Zenith.NET.Vulkan/VKSampler.cs index 1d4fd186..068adbb5 100644 --- a/sources/Zenith.NET.Vulkan/VKSampler.cs +++ b/sources/Zenith.NET.Vulkan/VKSampler.cs @@ -2,53 +2,47 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKSampler : Sampler +internal class VKSampler : Sampler { - public VkSampler Sampler; + public VKDescriptorToken Token; public VKSampler(VKGraphicsContext context, SamplerDesc desc) : base(context, desc) { - SamplerCreateInfo createInfo = new() + Token = context.SamplerHeap.Allocate(new SamplerCreateInfo() { 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), + 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.Filter is Filter.Anisotropic, + AnisotropyEnable = desc.MaxAnisotropy > 1, MaxAnisotropy = desc.MaxAnisotropy, - CompareEnable = desc.ComparisonFunc is not ComparisonFunc.Never, - CompareOp = VKFormats.Vulkan(desc.ComparisonFunc), + CompareEnable = desc.CompareOp is not CompareOp.Never, + CompareOp = VKFormats.Vulkan(desc.CompareOp), 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(); + public override ResourceHandle Handle => Token.ResourceHandle; - DebugUtilsObjectNameInfoEXT nameInfo = new() - { - SType = StructureType.DebugUtilsObjectNameInfoExt, - ObjectType = ObjectType.Sampler, - ObjectHandle = Sampler.Handle, - PObjectName = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8) - }; + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } - Context.DebugUtils?.SetDebugUtilsObjectName(Context.Device, &nameInfo).Success(); + protected override void SetResourceName(string name) + { } protected override void Destroy() { - Context.Vk.DestroySampler(Context.Device, Sampler, null); + Token.Dispose(); } } diff --git a/sources/Zenith.NET.Vulkan/VKShader.cs b/sources/Zenith.NET.Vulkan/VKShader.cs index 22eda796..b8fadef9 100644 --- a/sources/Zenith.NET.Vulkan/VKShader.cs +++ b/sources/Zenith.NET.Vulkan/VKShader.cs @@ -4,25 +4,45 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKShader(VKGraphicsContext context, ShaderDesc desc) : Shader(context, desc) { - public PipelineShaderStageCreateInfo GetPipelineShaderStageCreateInfo(ZenithMarshal.Scope scope) + public PipelineShaderStageCreateInfo GetPipelineShaderStageCreateInfo(ZenithMarshal.Scope scope, ShaderStageFlags stage) { + 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]) + }; + + 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, - 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) - } - ]) + PNext = (ShaderModuleCreateInfo*)ZenithMarshal.AllocateAndFill(scope, [createInfo]), + Stage = stage, + PName = (byte*)ZenithMarshal.StringToPointer(scope, Desc.Name, StringEncoding.UTF8) }; } + public override nint GetNativeObject(NativeObjectType type) + { + return 0; + } + protected override void SetResourceName(string name) { } diff --git a/sources/Zenith.NET.Vulkan/VKSwapChain.cs b/sources/Zenith.NET.Vulkan/VKSwapChain.cs index 02005114..c251c84f 100644 --- a/sources/Zenith.NET.Vulkan/VKSwapChain.cs +++ b/sources/Zenith.NET.Vulkan/VKSwapChain.cs @@ -6,296 +6,291 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKSwapChain : SwapChain { private readonly VKFence fence; - private readonly VKSwapChainFrameBuffer swapChainFrameBuffer; public SurfaceKHR Surface; public SwapchainKHR Swapchain; - public uint ImageIndex; + private VKTexture[] textures = []; + private uint index; public VKSwapChain(VKGraphicsContext context, SwapChainDesc desc) : base(context, desc) { fence = new(context); - swapChainFrameBuffer = new(context, this); - CreateSurface(); CreateSwapChain(); + CreateTextures(); + AcquireNextImage(); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public override FrameBuffer FrameBuffer => swapChainFrameBuffer[ImageIndex]; + public override Texture Drawable => textures[index]; - public override void Present() + public override nint GetNativeObject(NativeObjectType type) { - if (Swapchain.Handle is not 0) + return 0; + } + + protected override void PresentImpl() + { + fixed (SwapchainKHR* swapchains = &Swapchain) { - fixed (SwapchainKHR* pSwapchains = &Swapchain) + fixed (uint* imageIndices = &index) { - fixed (uint* pImageIndices = &ImageIndex) + PresentInfoKHR presentInfo = new() { - PresentInfoKHR presentInfo = new() - { - SType = StructureType.PresentInfoKhr, - SwapchainCount = 1, - PSwapchains = pSwapchains, - PImageIndices = pImageIndices - }; + SType = StructureType.PresentInfoKhr, + SwapchainCount = 1, + PSwapchains = swapchains, + PImageIndices = imageIndices + }; - (Context.Swapchain?.QueuePresent(Context.GraphicsQueue, &presentInfo) ?? Result.ErrorInitializationFailed).Success(); - - AcquireNextImage(); - } + Context.Swapchain?.QueuePresent(Context.GraphicsQueue.Vulkan().Queue, &presentInfo).Success(); } } + + AcquireNextImage(); } protected override void ResizeImpl() { + DestroyTextures(); + DestroySwapChain(); + CreateSwapChain(); + CreateTextures(); + + AcquireNextImage(); } protected override void RefreshImpl() { - CreateSurface(); + DestroyTextures(); + DestroySwapChain(); + CreateSwapChain(); + CreateTextures(); + + AcquireNextImage(); } 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() { + DestroyTextures(); DestroySwapChain(); - DestroySurface(); - swapChainFrameBuffer.Dispose(); fence.Dispose(); } - private void CreateSurface() + private void CreateSwapChain() { - DestroySurface(); + 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, null, 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, null, out Surface).Success(); + Context.WaylandSurface?.CreateWaylandSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); } break; case SurfaceType.Xlib: { - XlibSurfaceCreateInfoKHR createInfo = new() + XlibSurfaceCreateInfoKHR surfaceCreateInfo = 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(); + Context.XlibSurface?.CreateXlibSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); } break; case SurfaceType.Android: { - AndroidSurfaceCreateInfoKHR createInfo = new() + AndroidSurfaceCreateInfoKHR surfaceCreateInfo = new() { SType = StructureType.AndroidSurfaceCreateInfoKhr, Window = (nint*)Desc.Surface.Handles[0] }; - Context.AndroidSurface?.CreateAndroidSurface(Context.Instance, &createInfo, null, out Surface).Success(); + Context.AndroidSurface?.CreateAndroidSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); } break; case SurfaceType.Apple: { - MetalSurfaceCreateInfoEXT createInfo = new() + MetalSurfaceCreateInfoEXT surfaceCreateInfo = new() { SType = StructureType.MetalSurfaceCreateInfoExt, PLayer = (nint*)Desc.Surface.Handles[0] }; - Context.MetalSurface?.CreateMetalSurface(Context.Instance, &createInfo, null, out Surface).Success(); + Context.MetalSurface?.CreateMetalSurface(Context.Instance, &surfaceCreateInfo, default, out Surface).Success(); } break; } - } - private void DestroySurface() - { - if (Surface.Handle is not 0) - { - Context.Surface?.DestroySurface(Context.Instance, Surface, null); + SurfaceCapabilitiesKHR capabilities = default; + Context.Surface?.GetPhysicalDeviceSurfaceCapabilities(Context.PhysicalDevice, Surface, &capabilities).Success(); - Surface = default; - } - } + uint surfaceFormatCount = 0; + Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, default).Success(); - private void CreateSwapChain() - { - DestroySwapChain(); + SurfaceFormatKHR* surfaceFormats = (SurfaceFormatKHR*)ZenithMarshal.Allocate(scope, surfaceFormatCount); + Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, surfaceFormats).Success(); - if (Desc.Surface.Type is not SurfaceType.D3D11Interop) - { - using ZenithMarshal.Scope scope = new(); + uint presentModeCount = 0; + Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, null).Success(); - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = Context.GetSharingModeInfo(scope); + PresentModeKHR* presentModes = (PresentModeKHR*)ZenithMarshal.Allocate(scope, presentModeCount); + Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, presentModes).Success(); - SurfaceCapabilitiesKHR capabilities = default; - Context.Surface?.GetPhysicalDeviceSurfaceCapabilities(Context.PhysicalDevice, Surface, &capabilities).Success(); - - uint surfaceFormatCount = 0; - Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, null).Success(); + uint minImageCount = capabilities.MinImageCount + 1; + if (capabilities.MaxImageCount > 0 && minImageCount > capabilities.MaxImageCount) + { + minImageCount = capabilities.MaxImageCount; + } - SurfaceFormatKHR* surfaceFormats = (SurfaceFormatKHR*)ZenithMarshal.Allocate(scope, surfaceFormatCount); - Context.Surface?.GetPhysicalDeviceSurfaceFormats(Context.PhysicalDevice, Surface, &surfaceFormatCount, surfaceFormats).Success(); + SurfaceFormatKHR surfaceFormat = default; + foreach (SurfaceFormatKHR item in new ReadOnlySpan(surfaceFormats, (int)surfaceFormatCount)) + { + if (item.Format == VKFormats.Vulkan(Desc.Format).Format) + { + surfaceFormat = item; - uint presentModeCount = 0; - Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, null).Success(); + if (item.ColorSpace is ColorSpaceKHR.SpaceSrgbNonlinearKhr) + { + break; + } + } + } - PresentModeKHR* presentModes = (PresentModeKHR*)ZenithMarshal.Allocate(scope, presentModeCount); - Context.Surface?.GetPhysicalDeviceSurfacePresentModes(Context.PhysicalDevice, Surface, &presentModeCount, presentModes).Success(); + SurfaceTransformFlagsKHR preTransform = SurfaceTransformFlagsKHR.InheritBitKhr; + if (capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.IdentityBitKhr)) + { + preTransform = SurfaceTransformFlagsKHR.IdentityBitKhr; + } - uint minImageCount = capabilities.MinImageCount + 1; - if (capabilities.MaxImageCount > 0 && minImageCount > capabilities.MaxImageCount) - { - minImageCount = capabilities.MaxImageCount; - } + CompositeAlphaFlagsKHR compositeAlpha = CompositeAlphaFlagsKHR.InheritBitKhr; + if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr)) + { + compositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr; + } - SurfaceFormatKHR surfaceFormat = default; - foreach (SurfaceFormatKHR item in new ReadOnlySpan(surfaceFormats, (int)surfaceFormatCount)) + PresentModeKHR presentMode = PresentModeKHR.FifoKhr; + foreach (PresentModeKHR item in new ReadOnlySpan(presentModes, (int)presentModeCount)) + { + if (item is PresentModeKHR.MailboxKhr) { - if (item.Format == VKFormats.Vulkan(Desc.ColorTargetFormat)) - { - surfaceFormat = item; + presentMode = PresentModeKHR.MailboxKhr; - if (item.ColorSpace is ColorSpaceKHR.SpaceSrgbNonlinearKhr) - { - break; - } - } + break; } + } - Extent2D imageExtent = new() + 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(); + } - SurfaceTransformFlagsKHR preTransform = SurfaceTransformFlagsKHR.InheritBitKhr; - if (capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.IdentityBitKhr)) - { - preTransform = SurfaceTransformFlagsKHR.IdentityBitKhr; - } + private void DestroySwapChain() + { + Context.Swapchain?.DestroySwapchain(Context.Device, Swapchain, default); + Context.Surface?.DestroySurface(Context.Instance, Surface, default); - CompositeAlphaFlagsKHR compositeAlpha = CompositeAlphaFlagsKHR.InheritBitKhr; - if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr)) - { - compositeAlpha = CompositeAlphaFlagsKHR.OpaqueBitKhr; - } + index = 0; + } - PresentModeKHR presentMode = PresentModeKHR.FifoKhr; - foreach (PresentModeKHR item in new ReadOnlySpan(presentModes, (int)presentModeCount)) - { - if (item is PresentModeKHR.MailboxKhr) - { - presentMode = PresentModeKHR.MailboxKhr; + private void CreateTextures() + { + using ZenithMarshal.Scope scope = new(); - break; - } - } + uint swapchainImageCount = 0; + Context.Swapchain?.GetSwapchainImages(Context.Device, Swapchain, &swapchainImageCount, default).Success(); - 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(); - - swapChainFrameBuffer.CreateFrameBuffers(createInfo.ImageExtent.Width, createInfo.ImageExtent.Height, []); - - AcquireNextImage(); - } - else + 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++) { - swapChainFrameBuffer.CreateFrameBuffers(Desc.Surface.Width, Desc.Surface.Height, Desc.Surface.Handles); + textures[i] = new(Context, desc, swapchainImages[i], new(default, 0, false, false)); } } - private void DestroySwapChain() + private void DestroyTextures() { - swapChainFrameBuffer.DestroyFrameBuffers(); - - if (Swapchain.Handle is not 0) + for (int i = 0; i < textures.Length; i++) { - Context.Swapchain?.DestroySwapchain(Context.Device, Swapchain, null); - - Swapchain = default; + textures[i].Dispose(); } - - ImageIndex = 0; } private void AcquireNextImage() { - fixed (uint* pImageIndex = &ImageIndex) - { - (Context.Swapchain?.AcquireNextImage(Context.Device, Swapchain, ulong.MaxValue, default, fence.Fence, pImageIndex) ?? Result.ErrorInitializationFailed).Success(); + Context.Swapchain?.AcquireNextImage(Context.Device, Swapchain, ulong.MaxValue, default, fence.Fence, ref index).Success(); - fence.Wait(); - } + fence.Wait(); } } diff --git a/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs b/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs deleted file mode 100644 index 01de96cf..00000000 --- a/sources/Zenith.NET.Vulkan/VKSwapChainFrameBuffer.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Zenith.NET.Vulkan; - -internal unsafe class VKSwapChainFrameBuffer(VKGraphicsContext context, VKSwapChain swapChain) : GraphicsResource(context) -{ - private VKTexture? depthStencilTarget; - private VKTexture[] colorTargets = []; - private VKFrameBuffer[] frameBuffers = []; - - public VKFrameBuffer this[uint index] => frameBuffers[index]; - - public void CreateFrameBuffers(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]; - 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); - } - - 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 - }); - } - } - - public void DestroyFrameBuffers() - { - foreach (VKFrameBuffer frameBuffer in frameBuffers) - { - frameBuffer.Dispose(); - } - frameBuffers = []; - - foreach (VKTexture texture in colorTargets) - { - texture.Dispose(); - } - colorTargets = []; - - depthStencilTarget?.Dispose(); - depthStencilTarget = null; - } - - protected override void SetResourceName(string name) - { - } - - protected override void Destroy() - { - DestroyFrameBuffers(); - } -} diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index e2f16161..88945368 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -4,300 +4,109 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKTexture : Texture { + private readonly Dictionary attachmentViews = []; + public Image Image; + public VKAllocation Allocation; + public VKTexture(VKGraphicsContext context, TextureDesc desc) : base(context, desc) { - using ZenithMarshal.Scope scope = new(); + ImageCreateInfo createInfo = CreateInfo(desc, context.QueueFamilies); - (SharingMode sharingMode, uint queueFamilyIndexCount, nint pQueueFamilyIndices) = context.GetSharingModeInfo(scope); + context.Vk.CreateImage(context.Device, &createInfo, default, out Image).Success(); - ImageCreateInfo createInfo = new() + ImageMemoryRequirementsInfo2 requirementsInfo2 = 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 = ZenithHelper.FlattenArrayLayerCount(desc), - Samples = VKFormats.Vulkan(desc.SampleCount), - Usage = VKFormats.Vulkan(desc.Format, desc.Flags).UsageFlags, - SharingMode = sharingMode, - QueueFamilyIndexCount = queueFamilyIndexCount, - PQueueFamilyIndices = (uint*)pQueueFamilyIndices + SType = StructureType.ImageMemoryRequirementsInfo2, + Image = Image }; - context.Vk.CreateImage(context.Device, &createInfo, null, out Image).Success(); + MemoryRequirements2 requirements2 = new() { SType = StructureType.MemoryRequirements2 }; + requirements2.AddNext(out MemoryDedicatedRequirements dedicatedRequirements); - DeviceMemory = new(context, this); + context.Vk.GetImageMemoryRequirements2(context.Device, &requirementsInfo2, &requirements2); - View = new(context, new() + MemoryAllocateInfo allocateInfo = new() { - Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers - }); + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements2.MemoryRequirements.Size, + MemoryTypeIndex = context.FindMemoryTypeIndex(requirements2.MemoryRequirements.MemoryTypeBits, MemoryResidency.GpuOnly) + }; - Layouts = new ImageLayout[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(Layouts, ImageLayout.Undefined); - } + if (dedicatedRequirements.PrefersDedicatedAllocation || dedicatedRequirements.RequiresDedicatedAllocation) + { + allocateInfo.AddNext(out MemoryDedicatedAllocateInfo dedicatedAllocateInfo); + dedicatedAllocateInfo.Image = Image; + } - public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image) : base(context, desc) - { - 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, true); View = new(context, new() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) }); - - Layouts = new ImageLayout[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(Layouts, ImageLayout.Undefined); } - public VKTexture(VKGraphicsContext context, TextureDesc desc, ExternalMemoryHandleTypeFlags handleTypes, nint handle) : base(context, desc) + public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image, VKAllocation allocation) : 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 = ZenithHelper.FlattenArrayLayerCount(desc), - 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); + Image = image; + Allocation = allocation; View = new(context, new() { Texture = this, - FirstMipLevel = 0, - MipLevelCount = desc.MipLevels, - FirstArrayLayer = 0, - ArrayLayerCount = desc.ArrayLayers + Type = desc.Type, + Format = desc.Format, + Range = TextureSubresourceRange.All(this) }); - - Layouts = new ImageLayout[ZenithHelper.SubresourceCount(desc)]; - Array.Fill(Layouts, ImageLayout.Undefined); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public VKDeviceMemory? DeviceMemory { get; } - public VKTextureView View { get; } - public ImageLayout[] Layouts { get; } + public override ResourceHandle SampledHandle => View.SampledHandle; - 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 override ResourceHandle StorageHandle => View.StorageHandle; - public void TransitionLayout(VKCommandBuffer commandBuffer, TextureSlice slice, ImageLayout newLayout) + public override nint GetNativeObject(NativeObjectType type) { - TransitionLayout(commandBuffer, slice.MipLevel, 1, slice.ArrayLayer, 1, slice.Face, 1, newLayout); + return 0; } - public ImageView CreateAttachmentView(TextureSlice slice) + public ImageView GetAttachmentView(TextureSubresource subresource) { - ImageViewCreateInfo createInfo = new() + if (!attachmentViews.TryGetValue(subresource, out ImageView view)) { - SType = StructureType.ImageViewCreateInfo, - Image = Image, - ViewType = ImageViewType.Type2D, - Format = VKFormats.Vulkan(Desc.Format), - SubresourceRange = new() + ImageViewCreateInfo createInfo = new() { - AspectMask = VKFormats.Vulkan(Desc.Format, Desc.Flags).AspectFlags, - BaseMipLevel = slice.MipLevel, - LevelCount = 1, - BaseArrayLayer = ZenithHelper.FlattenArrayLayerIndex(Desc, slice), - LayerCount = 1 - } - }; + 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 + } + }; - ImageView imageView; - Context.Vk.CreateImageView(Context.Device, &createInfo, null, &imageView).Success(); + Context.Vk.CreateImageView(Context.Device, &createInfo, default, out view).Success(); + + attachmentViews[subresource] = view; + } - return imageView; + return view; } protected override void SetResourceName(string name) @@ -317,13 +126,46 @@ 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(); - if (DeviceMemory is not null) + if (Allocation.OwnsResource) { - Context.Vk.DestroyImage(Context.Device, Image, null); + Context.Vk.DestroyImage(Context.Device, Image, default); + } - DeviceMemory.Dispose(); + if (Allocation.OwnsMemory) + { + Context.Vk.FreeMemory(Context.Device, Allocation.DeviceMemory, default); } } + + public static ImageCreateInfo CreateInfo(TextureDesc desc, QueueFamilies queueFamilies) + { + return 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).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 = queueFamilies.SharingMode, + QueueFamilyIndexCount = queueFamilies.IndexCount, + PQueueFamilyIndices = queueFamilies.Indices + }; + } } diff --git a/sources/Zenith.NET.Vulkan/VKTextureView.cs b/sources/Zenith.NET.Vulkan/VKTextureView.cs index 111f180f..7efc58a7 100644 --- a/sources/Zenith.NET.Vulkan/VKTextureView.cs +++ b/sources/Zenith.NET.Vulkan/VKTextureView.cs @@ -2,89 +2,60 @@ namespace Zenith.NET.Vulkan; -internal unsafe class VKTextureView : TextureView +internal unsafe class VKTextureView(VKGraphicsContext context, TextureViewDesc desc) : TextureView(context, desc) { - public ImageView ImageView; + private VKDescriptorToken? sampledToken; + private VKDescriptorToken? storageToken; - public VKTextureView(VKGraphicsContext context, TextureViewDesc desc) : base(context, desc) - { - ImageViewCreateInfo createInfo = new() - { - SType = StructureType.ImageViewCreateInfo, - Image = desc.Texture.Vulkan().Image, - ViewType = Resolve(desc), - Format = VKFormats.Vulkan(desc.Texture.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 - } - }; - - 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 override ResourceHandle SampledHandle => (sampledToken ??= CreateToken(DescriptorType.SampledImage, ImageLayout.ShaderReadOnlyOptimal)).ResourceHandle; - public DescriptorImageInfo SrvImageInfo { get; } + public override ResourceHandle StorageHandle => (storageToken ??= CreateToken(DescriptorType.StorageImage, ImageLayout.General)).ResourceHandle; - public DescriptorImageInfo UavImageInfo { get; } - - public void TransitionLayout(VKCommandBuffer commandBuffer, ImageLayout newLayout) + public override nint GetNativeObject(NativeObjectType type) { - Desc.Texture.Vulkan().TransitionLayout(commandBuffer, - Desc.FirstMipLevel, - Desc.MipLevelCount, - Desc.FirstArrayLayer, - Desc.ArrayLayerCount, - 0, - ZenithHelper.FaceCount(Desc.Texture.Desc), - newLayout); + return 0; } 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); + storageToken?.Dispose(); + sampledToken?.Dispose(); } - private static ImageViewType Resolve(TextureViewDesc desc) + private VKDescriptorToken CreateToken(DescriptorType type, ImageLayout layout) { - return VKFormats.Vulkan(desc.Texture.Desc.Type switch + 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() { - 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; + SType = StructureType.ResourceDescriptorInfoExt(), + Type = type, + Data = new() { PImage = &image } + }); } } diff --git a/sources/Zenith.NET.Vulkan/VKTimeline.cs b/sources/Zenith.NET.Vulkan/VKTimeline.cs new file mode 100644 index 00000000..578fc051 --- /dev/null +++ b/sources/Zenith.NET.Vulkan/VKTimeline.cs @@ -0,0 +1,89 @@ +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 nint GetNativeObject(NativeObjectType type) + { + return 0; + } + + protected override ulong GetCompletedValue() + { + Context.Vk.GetSemaphoreCounterValue(Context.Device, Semaphore, out ulong value).Success(); + + return value; + } + + 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.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index 4139bbd1..cbd0eb80 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -6,140 +6,101 @@ internal unsafe class VKTopLevelAccelerationStructure : TopLevelAccelerationStru { public AccelerationStructureKHR AccelerationStructure; - public ulong DeviceAddress; + public VKDescriptorToken Token; - public VKTopLevelAccelerationStructure(VKGraphicsContext context, TopLevelAccelerationStructureDesc desc, VKCommandBuffer commandBuffer) : base(context, desc) + public VKTopLevelAccelerationStructure(VKGraphicsContext context, VKCommandBuffer commandBuffer, TopLevelAccelerationStructureDesc desc) : base(context, desc) { using ZenithMarshal.Scope scope = new(); - InstanceBuffer = new(context, new() + Instance = new(context, new() { SizeInBytes = (uint)(sizeof(AccelerationStructureInstanceKHR) * desc.Instances.Length), - StrideInBytes = (uint)sizeof(AccelerationStructureInstanceKHR), - Flags = BufferUsageFlags.AccelerationStructure | BufferUsageFlags.MapWrite - }); + Residency = MemoryResidency.CpuWriteOnly + }, BufferUsageFlags.AccelerationStructureBuildInputReadOnlyBitKhr); - 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 - }; + 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); - context.AccelerationStructure?.GetAccelerationStructureBuildSizes(context.Device, AccelerationStructureBuildTypeKHR.DeviceKhr, &buildInfo, &buildRangeInfo.PrimitiveCount, &sizeInfo); - - BufferDesc accelerationStructureBufferDesc = new() + Storage = new(context, new() { SizeInBytes = (uint)sizeInfo.AccelerationStructureSize, - StrideInBytes = (uint)sizeInfo.AccelerationStructureSize - }; + Residency = MemoryResidency.GpuOnly + }, BufferUsageFlags.AccelerationStructureStorageBitKhr); - AccelerationStructureBuffer = new(context, accelerationStructureBufferDesc, VkBufferUsageFlags.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 = AccelerationStructureBuffer.Buffer, + Buffer = Storage.Buffer, Size = sizeInfo.AccelerationStructureSize, Type = AccelerationStructureTypeKHR.TopLevelKhr }; - context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, null, out AccelerationStructure).Success(); + context.AccelerationStructure?.CreateAccelerationStructure(context.Device, &createInfo, default, out AccelerationStructure).Success(); + + info.DstAccelerationStructure = AccelerationStructure; + info.ScratchData = new() { DeviceAddress = Scratch.DeviceAddress }; - AccelerationStructureDeviceAddressInfoKHR addressInfo = new() + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); + context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AllCommandsBit); + + AccelerationStructureDeviceAddressInfoKHR deviceAddressInfo = new() { SType = StructureType.AccelerationStructureDeviceAddressInfoKhr, AccelerationStructure = AccelerationStructure }; - DeviceAddress = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &addressInfo) ?? 0; - - ScratchBuffer = new(context, new() + DeviceAddressRangeEXT addressRange = 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 + Address = context.AccelerationStructure?.GetAccelerationStructureDeviceAddress(context.Device, &deviceAddressInfo) ?? 0, + Size = sizeInfo.AccelerationStructureSize }; - context.Vk.CmdPipelineBarrier(commandBuffer.CommandBuffer, - PipelineStageFlags.AccelerationStructureBuildBitKhr, - PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit, - 0, - 1, - &barrier, - 0, - null, - 0, - null); + Token = context.ResourceHeap.Allocate(new ResourceDescriptorInfoEXT() + { + SType = StructureType.ResourceDescriptorInfoExt(), + Type = DescriptorType.AccelerationStructureKhr, + Data = new() { PAddressRange = &addressRange } + }); } public new VKGraphicsContext Context => (VKGraphicsContext)base.Context; - public VKBuffer InstanceBuffer { get; } + public VKBuffer Instance { get; } - public VKBuffer AccelerationStructureBuffer { get; } + public VKBuffer Storage { get; } - public VKBuffer ScratchBuffer { get; } + public VKBuffer Scratch { get; } + + public override ResourceHandle Handle => Token.ResourceHandle; 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; + using ZenithMarshal.Scope scope = new(); - Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &buildInfo, &pBuildRangeInfo); + 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 }; - MemoryBarrier barrier = new() - { - SType = StructureType.MemoryBarrier, - SrcAccessMask = AccessFlags.AccelerationStructureWriteBitKhr, - DstAccessMask = AccessFlags.AccelerationStructureReadBitKhr - }; + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AccelerationStructureBuildBitKhr); + Context.AccelerationStructure?.CmdBuildAccelerationStructures(commandBuffer.CommandBuffer, 1, &info, &buildRangeInfos); + BuildSyncBarrier(commandBuffer, PipelineStageFlags2.AllCommandsBit); + } - Context.Vk.CmdPipelineBarrier(commandBuffer.CommandBuffer, - PipelineStageFlags.AccelerationStructureBuildBitKhr, - PipelineStageFlags.FragmentShaderBit | PipelineStageFlags.ComputeShaderBit, - 0, - 1, - &barrier, - 0, - null, - 0, - null); + public override nint GetNativeObject(NativeObjectType type) + { + return 0; } protected override void SetResourceName(string name) @@ -159,20 +120,22 @@ protected override void SetResourceName(string name) protected override void Destroy() { - Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, null); + Token.Dispose(); - ScratchBuffer.Dispose(); - AccelerationStructureBuffer.Dispose(); - InstanceBuffer.Dispose(); + Context.AccelerationStructure?.DestroyAccelerationStructure(Context.Device, AccelerationStructure, default); + + Scratch.Dispose(); + Storage.Dispose(); + Instance.Dispose(); } - private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc, out AccelerationStructureGeometryKHR geometry, out AccelerationStructureBuildRangeInfoKHR buildRangeInfo) + private AccelerationStructureBuildGeometryInfoKHR Info(ZenithMarshal.Scope scope, TopLevelAccelerationStructureDesc desc, out uint* maxPrimitiveCounts, out AccelerationStructureBuildRangeInfoKHR* buildRangeInfos) { uint instanceCount = (uint)desc.Instances.Length; - MappedMemory mappedMemory = InstanceBuffer.Map(); + nint pointer = Instance.Map(); - AccelerationStructureInstanceKHR* instances = (AccelerationStructureInstanceKHR*)mappedMemory.Pointer; + AccelerationStructureInstanceKHR* instances = (AccelerationStructureInstanceKHR*)pointer; for (uint i = 0; i < instanceCount; i++) { RayTracingInstance instance = desc.Instances[i]; @@ -180,29 +143,62 @@ private void FillInstanceBuffer(TopLevelAccelerationStructureDesc desc, out Acce instances[i] = new() { Transform = VKFormats.Vulkan(instance.Transform), - InstanceCustomIndex = instance.ID, - Mask = instance.Mask, + InstanceCustomIndex = instance.InstanceId, + Mask = instance.VisibilityMask, Flags = VKFormats.Vulkan(instance.Flags), AccelerationStructureReference = instance.AccelerationStructure.Vulkan().DeviceAddress }; } - InstanceBuffer.Unmap(); + Instance.Unmap(); + + maxPrimitiveCounts = (uint*)ZenithMarshal.AllocateAndFill(scope, [instanceCount]); + buildRangeInfos = (AccelerationStructureBuildRangeInfoKHR*)ZenithMarshal.AllocateAndFill(scope, [new AccelerationStructureBuildRangeInfoKHR() { PrimitiveCount = instanceCount }]); - geometry = new() + return new() { - SType = StructureType.AccelerationStructureGeometryKhr, - GeometryType = GeometryTypeKHR.InstancesKhr, - Geometry = new() - { - Instances = 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.AccelerationStructureGeometryInstancesDataKhr, - Data = new() { DeviceAddress = InstanceBuffer.DeviceAddress } + 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) + { + 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 }; - buildRangeInfo = new() { PrimitiveCount = instanceCount }; + commandBuffer.Context.Vk.CmdPipelineBarrier2(commandBuffer.CommandBuffer, &dependencyInfo); } } diff --git a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs index 2abc3e1b..1757cc43 100644 --- a/sources/Zenith.NET.Vulkan/VKValidationLayer.cs +++ b/sources/Zenith.NET.Vulkan/VKValidationLayer.cs @@ -4,7 +4,7 @@ namespace Zenith.NET.Vulkan; internal unsafe class VKValidationLayer : ValidationLayer { - private readonly PfnDebugUtilsMessengerCallbackEXT pfnUserCallback; + private readonly PfnDebugUtilsMessengerCallbackEXT callback; private readonly DebugUtilsMessengerEXT messenger; public VKValidationLayer(VKGraphicsContext context) : base(context) @@ -19,36 +19,38 @@ public VKValidationLayer(VKGraphicsContext context) : base(context) MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt | DebugUtilsMessageTypeFlagsEXT.ValidationBitExt | DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt, - PfnUserCallback = pfnUserCallback = new(UserCallback) + PfnUserCallback = callback = new(Callback) }; - context.DebugUtils?.CreateDebugUtilsMessenger(context.Instance, &createInfo, null, out messenger).Success(); + 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); + Context.DebugUtils?.DestroyDebugUtilsMessenger(Context.Instance, messenger, default); - pfnUserCallback.Dispose(); + callback.Dispose(); } - private uint UserCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, - DebugUtilsMessageTypeFlagsEXT messageTypes, - DebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) + private uint Callback(DebugUtilsMessageSeverityFlagsEXT severity, DebugUtilsMessageTypeFlagsEXT types, DebugUtilsMessengerCallbackDataEXT* callbackData, void* userData) { - Report(MessageSource.GraphicsAPI, messageSeverity switch + Report(severity switch { DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => MessageSeverity.Error, DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => MessageSeverity.Warning, - _ => MessageSeverity.Message - }, ZenithMarshal.StringFromPointer((nint)pCallbackData->PMessage, StringEncoding.UTF8)); + _ => MessageSeverity.Info + }, ZenithMarshal.StringFromPointer((nint)callbackData->PMessage, StringEncoding.UTF8)); return Vk.False; } diff --git a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj index 39132514..476a45fb 100644 --- a/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj +++ b/sources/Zenith.NET.Vulkan/Zenith.NET.Vulkan.csproj @@ -9,8 +9,8 @@ - + diff --git a/sources/Zenith.NET/BlendStates.cs b/sources/Zenith.NET/BlendStates.cs deleted file mode 100644 index 75a0d8d9..00000000 --- a/sources/Zenith.NET/BlendStates.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace Zenith.NET; - -public static class BlendStates -{ - public static readonly BlendState Default = new() - { - AlphaToCoverageEnable = false, - IndependentBlendEnable = false, - RenderTarget0 = new() - { - SrcBlend = Blend.One, - DestBlend = Blend.Zero, - BlendOp = BlendOp.Add, - SrcBlendAlpha = Blend.One, - DestBlendAlpha = Blend.Zero, - BlendOpAlpha = BlendOp.Add, - Flags = ColorComponentFlags.All - } - }; - - public static readonly BlendState Additive = new() - { - RenderTarget0 = Default.RenderTarget0 with - { - BlendEnable = true, - SrcBlend = Blend.SrcAlpha, - DestBlend = Blend.One, - SrcBlendAlpha = Blend.SrcAlpha, - DestBlendAlpha = Blend.One - } - }; - - public static readonly BlendState AlphaBlend = new() - { - RenderTarget0 = Default.RenderTarget0 with - { - BlendEnable = true, - SrcBlend = Blend.SrcAlpha, - DestBlend = Blend.InverseSrcAlpha, - SrcBlendAlpha = Blend.SrcAlpha, - DestBlendAlpha = Blend.InverseSrcAlpha - } - }; - - public static readonly BlendState NonPremultiplied = new() - { - RenderTarget0 = Default.RenderTarget0 with - { - BlendEnable = true, - SrcBlend = Blend.SrcAlpha, - DestBlend = Blend.InverseSrcAlpha, - SrcBlendAlpha = Blend.SrcAlpha, - DestBlendAlpha = Blend.InverseSrcAlpha - } - }; - - public static readonly BlendState Opaque = new() - { - RenderTarget0 = Default.RenderTarget0 with - { - BlendEnable = true - } - }; - - public static readonly BlendState ColorDisabled = new() - { - RenderTarget0 = Default.RenderTarget0 with - { - BlendEnable = true, - Flags = ColorComponentFlags.None - } - }; -} 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/Buffer.cs b/sources/Zenith.NET/Buffer.cs index 61fc302a..39fee364 100644 --- a/sources/Zenith.NET/Buffer.cs +++ b/sources/Zenith.NET/Buffer.cs @@ -1,39 +1,62 @@ 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; public ref readonly BufferDesc Desc => ref desc; - public abstract MappedMemory Map(); + public abstract ResourceHandle ConstantHandle { get; } + + public abstract ResourceHandle StorageReadOnlyHandle { get; } + + public abstract ResourceHandle StorageReadWriteHandle { get; } + + public abstract nint Map(); 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.Residency is MemoryResidency.CpuWriteOnly) { - return; + nint pointer = Map(); + + unsafe + { + new ReadOnlySpan((void*)data.Pointer, (int)data.SizeInBytes).CopyTo(new((void*)(pointer + offsetInBytes), (int)data.SizeInBytes)); + } + + Unmap(); } + else + { + CommandBuffer commandBuffer = Context.TransferQueue.CommandBuffer(); + + commandBuffer.Upload(this, offsetInBytes, data); + commandBuffer.Submit().Wait(); + } + } - if (desc.Flags.HasFlag(BufferUsageFlags.MapRead) || desc.Flags.HasFlag(BufferUsageFlags.MapWrite)) + public void Download(uint offsetInBytes, BufferData data) + { + if (desc.Residency is MemoryResidency.CpuReadOnly) { - MappedMemory mappedMemory = Map(); + nint pointer = Map(); unsafe { - data.CopyTo(new((void*)(mappedMemory.Pointer + offsetInBytes), data.Length)); + new ReadOnlySpan((void*)(pointer + offsetInBytes), (int)data.SizeInBytes).CopyTo(new((void*)data.Pointer, (int)data.SizeInBytes)); } Unmap(); } else { - CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + CommandBuffer commandBuffer = Context.TransferQueue.CommandBuffer(); - commandBuffer.Upload(this, offsetInBytes, data); - commandBuffer.Submit(true); + commandBuffer.Download(this, offsetInBytes, data); + commandBuffer.Submit().Wait(); } } } diff --git a/sources/Zenith.NET/BufferView.cs b/sources/Zenith.NET/BufferView.cs index c2882bf7..3d41fbc3 100644 --- a/sources/Zenith.NET/BufferView.cs +++ b/sources/Zenith.NET/BufferView.cs @@ -1,8 +1,14 @@ namespace Zenith.NET; -public abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : GraphicsResource(context), IBindableResource +public abstract class BufferView(GraphicsContext context, BufferViewDesc desc) : GraphicsResource(context) { private BufferViewDesc desc = desc; public ref readonly BufferViewDesc Desc => ref desc; + + public abstract ResourceHandle ConstantHandle { get; } + + public abstract ResourceHandle StorageReadOnlyHandle { get; } + + public abstract ResourceHandle StorageReadWriteHandle { get; } } 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 443a9877..5319d14e 100644 --- a/sources/Zenith.NET/CommandBuffer.cs +++ b/sources/Zenith.NET/CommandBuffer.cs @@ -1,178 +1,238 @@ -using System.Runtime.InteropServices; +using System.Numerics; 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) + public CommandQueue Queue => queue; + + public TimelineValue Submit(params ReadOnlySpan waits) { - queue.Submit(this); + return queue.Submit(waits, this); + } - if (waitForCompletion) - { - queue.WaitIdle(); - } + public void Barrier(BarrierStages before, BarrierStages after) + { + BarrierImpl(before, after); } - public void Upload(Buffer buffer, uint offsetInBytes, ReadOnlySpan data) where T : unmanaged + public void Transition(Texture texture, TextureSubresource subresource, TextureLayout before, TextureLayout after) { - if (data.Length is 0) + TransitionImpl(texture, subresource, before, after); + } + + public void Upload(Buffer buffer, uint offsetInBytes, BufferData data) + { + Buffer transferBuffer = Context.Uploader.Buffer(this, data.SizeInBytes, new() { - return; - } + Pointer = data.Pointer, + RowSizeInBytes = data.SizeInBytes, + SrcRowStrideInBytes = data.SizeInBytes, + DstRowStrideInBytes = data.SizeInBytes, + Rows = 1 + }); - ReadOnlySpan byteData = MemoryMarshal.AsBytes(data); + CopyBuffer(transferBuffer, 0, buffer, offsetInBytes, data.SizeInBytes); + } - Buffer temporary = Context.Uploader.Buffer(this, (uint)byteData.Length); - temporary.Upload(byteData, 0); + public void Download(Buffer buffer, uint offsetInBytes, BufferData data) + { + Buffer transferBuffer = Context.Downloader.Buffer(this, data.SizeInBytes, new() + { + Pointer = data.Pointer, + RowSizeInBytes = data.SizeInBytes, + SrcRowStrideInBytes = data.SizeInBytes, + DstRowStrideInBytes = data.SizeInBytes, + Rows = 1 + }); - CopyBuffer(temporary, 0, buffer, offsetInBytes, (uint)byteData.Length); + CopyBuffer(buffer, offsetInBytes, transferBuffer, 0, data.SizeInBytes); } - public void Upload(Texture texture, TextureSlice slice, TextureOffset offset, TextureExtent extent, ReadOnlySpan data) where T : unmanaged + public void Upload(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) { - if (data.Length is 0) + 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++) { - return; + Buffer transferBuffer = Context.Uploader.Buffer(this, alignedDepthPitchInBytes, new() + { + Pointer = (nint)(data.Pointer + (data.SliceStrideInBytes * i)), + RowSizeInBytes = rowPitchInBytes, + SrcRowStrideInBytes = data.RowStrideInBytes, + DstRowStrideInBytes = alignedRowPitchInBytes, + Rows = blocksHigh + }); + + CopyBufferToTexture(transferBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes, texture, subresource, offset, sliceExtent); + + offset.Z++; } + } - ReadOnlySpan byteData = MemoryMarshal.AsBytes(data); + public void Download(Texture texture, TextureSubresource subresource, Offset3D offset, Extent3D extent, TextureData data) + { + const uint RowPitchAlignment = 256; + const uint DepthPitchAlignment = 512; - 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 rowPitchInBytes = ZenithHelper.SizeInBytes(texture.Desc.Format) * blocksWide; - uint sliceRowPitchAlignInBytes = ZenithHelper.Align(sliceRowPitchInBytes, GraphicsContext.TextureRowPitchAlignment); - uint sliceDepthPitchAlignInBytes = ZenithHelper.Align(sliceRowPitchAlignInBytes * blocksHigh, GraphicsContext.TextureDepthPitchAlignment); + uint alignedRowPitchInBytes = ZenithHelper.Align(rowPitchInBytes, RowPitchAlignment); + uint alignedDepthPitchInBytes = ZenithHelper.Align(alignedRowPitchInBytes * blocksHigh, DepthPitchAlignment); - TextureExtent sliceExtent = extent with { Depth = 1 }; + Extent3D 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 + Buffer transferBuffer = Context.Downloader.Buffer(this, alignedDepthPitchInBytes, new() { - 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)); - } - } + Pointer = (nint)(data.Pointer + (data.SliceStrideInBytes * i)), + RowSizeInBytes = rowPitchInBytes, + SrcRowStrideInBytes = alignedRowPitchInBytes, + DstRowStrideInBytes = data.RowStrideInBytes, + Rows = blocksHigh + }); - temporary.Unmap(); - - CopyBufferToTexture(temporary, 0, texture, slice, offset, sliceExtent); + CopyTextureToBuffer(texture, subresource, offset, sliceExtent, transferBuffer, 0, alignedRowPitchInBytes, alignedDepthPitchInBytes); offset.Z++; } } - 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, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent) + public void CopyBufferToTexture(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent) { - CopyBufferToTextureImpl(src, srcOffsetInBytes, dest, destSlice, destOffset, destExtent); + CopyBufferToTextureImpl(src, srcOffsetInBytes, srcRowStrideInBytes, srcSliceStrideInBytes, dst, dstSubresource, dstOffset, dstExtent); } - 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 dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent) { - CopyTextureImpl(src, srcSlice, srcOffset, dest, destSlice, destOffset, extent); + CopyTextureImpl(src, srcSubresource, srcOffset, dst, dstSubresource, dstOffset, 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 dst, uint dstOffsetInBytes, uint dstRowStrideInBytes, uint dstSliceStrideInBytes) { - CopyTextureToBufferImpl(src, srcSlice, srcOffset, srcExtent, dest, destOffsetInBytes); + CopyTextureToBufferImpl(src, srcSubresource, srcOffset, srcExtent, dst, dstOffsetInBytes, dstRowStrideInBytes, dstSliceStrideInBytes); } - public void ResolveTexture(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice) + public void ResolveTexture(Texture src, TextureSubresource srcSubresource, Texture dst, TextureSubresource dstSubresource) { - ResolveTextureImpl(src, srcSlice, dest, destSlice); + 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) + public void UpdateAccelerationStructure(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc) { - Context.ValidationLayer?.ValidateDesc(accelerationStructure.Desc, newDesc); - UpdateAccelerationStructureImpl(accelerationStructure, newDesc); accelerationStructure.Refresh(newDesc); } - public void BeginRenderPass(FrameBuffer frameBuffer, ClearValue clearValue, params IEnumerable preprocessResourceTables) + public void UpdateAccelerationStructure(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc) { - Scissor[] scissors = new Scissor[Math.Max(frameBuffer.ColorAttachmentCount, 1)]; - Viewport[] viewports = new Viewport[Math.Max(frameBuffer.ColorAttachmentCount, 1)]; + UpdateAccelerationStructureImpl(accelerationStructure, newDesc); - Array.Fill(scissors, new() { Width = frameBuffer.Width, Height = frameBuffer.Height }); - Array.Fill(viewports, new() { Width = frameBuffer.Width, Height = frameBuffer.Height, MaxDepth = 1 }); + accelerationStructure.Refresh(newDesc); + } - SetScissorsImpl(scissors); - SetViewportsImpl(viewports); + public void BeginRenderPass(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment) + { + int attachmentCount = colorAttachments.Length > 0 ? colorAttachments.Length : depthStencilAttachment is null ? 0 : 1; - foreach (ResourceTable resourceTable in preprocessResourceTables) + Span scissors = stackalloc Scissor[attachmentCount]; + Span viewports = stackalloc Viewport[attachmentCount]; + + if (colorAttachments.Length > 0) { - resourceTable.Preprocess(this); - } + for (int i = 0; i < colorAttachments.Length; i++) + { + ColorAttachment attachment = colorAttachments[i]; - BeginRenderPassImpl(frameBuffer, clearValue); + ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, + attachment.Texture.Desc.Height, + 0, + attachment.Subresource.MipLevel, + out uint width, + out uint height, + out _); - currentFrameBuffer = frameBuffer; - } + scissors[i] = new() + { + Width = width, + Height = height + }; - public void EndRenderPass() - { - if (currentFrameBuffer is null) - { - return; + viewports[i] = new() + { + Width = width, + Height = height, + MaxDepth = 1.0f + }; + } } + else if (depthStencilAttachment.HasValue) + { + DepthStencilAttachment attachment = depthStencilAttachment.Value; - EndRenderPassImpl(currentFrameBuffer); + ZenithHelper.MipDimensions(attachment.Texture.Desc.Width, + attachment.Texture.Desc.Height, + 0, + attachment.Subresource.MipLevel, + out uint width, + out uint height, + out _); - currentFrameBuffer = null; - } + scissors[0] = new() + { + Width = width, + Height = height + }; - public void SetScissors(Scissor[] scissors) - { - if (scissors.Length is 0) - { - return; + viewports[0] = new() + { + Width = width, + Height = height, + MaxDepth = 1.0f + }; } + SetViewportsImpl(viewports); SetScissorsImpl(scissors); + SetBlendConstantImpl(Vector4.One); + SetStencilReferenceImpl(0); + BeginRenderPassImpl(colorAttachments, depthStencilAttachment); } - public void SetViewports(Viewport[] viewports) + public void EndRenderPass() { - if (viewports.Length is 0) - { - return; - } - - SetViewportsImpl(viewports); + EndRenderPassImpl(); } public void SetPipeline(GraphicsPipeline pipeline) @@ -196,44 +256,59 @@ public void SetPipeline(MeshShadingPipeline pipeline) currentPipeline = pipeline; } - public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint index) + public void SetViewports(ReadOnlySpan viewports) { - if (currentPipeline is not GraphicsPipeline pipeline) - { - return; - } + SetViewportsImpl(viewports); + } - SetVertexBufferImpl(pipeline, buffer, offsetInBytes, index); + public void SetScissors(ReadOnlySpan scissors) + { + SetScissorsImpl(scissors); + } + + public void SetBlendConstant(Vector4 blendConstant) + { + SetBlendConstantImpl(blendConstant); } - public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat format) + public void SetStencilReference(uint stencilReference) { - if (currentPipeline is not GraphicsPipeline pipeline) + SetStencilReferenceImpl(stencilReference); + } + + public void SetVertexBuffer(Buffer buffer, uint offsetInBytes, uint slot) + { + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } - SetIndexBufferImpl(pipeline, buffer, offsetInBytes, format); + SetVertexBufferImpl(pipeline, buffer, offsetInBytes, slot); } - public void SetResourceTable(ResourceTable resourceTable) + public void SetIndexBuffer(Buffer buffer, uint offsetInBytes, IndexFormat indexFormat) { - if (currentPipeline is null) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } - SetResourceTableImpl(currentPipeline, resourceTable); + SetIndexBufferImpl(pipeline, buffer, offsetInBytes, indexFormat); + } - if (currentFrameBuffer is null) + public void SetConstantBuffer(Buffer buffer, uint offsetInBytes) + { + if (!TryGetCurrentPipeline(out Pipeline pipeline)) { - resourceTable.Preprocess(this); + return; } + + SetConstantBufferImpl(pipeline, buffer, offsetInBytes); } public void Draw(uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance) { - if (currentPipeline is not GraphicsPipeline pipeline) + if (!TryGetCurrentPipeline(out GraphicsPipeline pipeline)) { return; } @@ -243,7 +318,7 @@ 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(out GraphicsPipeline pipeline)) { return; } @@ -253,7 +328,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(out GraphicsPipeline pipeline)) { return; } @@ -263,7 +338,7 @@ 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(out GraphicsPipeline pipeline)) { return; } @@ -273,7 +348,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(out ComputePipeline pipeline)) { return; } @@ -283,7 +358,7 @@ public void Dispatch(uint groupCountX, uint groupCountY, uint groupCountZ) public void DispatchIndirect(Buffer indirectBuffer, uint offsetInBytes) { - if (currentPipeline is not ComputePipeline pipeline) + if (!TryGetCurrentPipeline(out ComputePipeline pipeline)) { return; } @@ -293,7 +368,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(out MeshShadingPipeline pipeline)) { return; } @@ -303,7 +378,7 @@ 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(out MeshShadingPipeline pipeline)) { return; } @@ -313,41 +388,21 @@ 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); } public void BeginDebugEvent(string label) { - if (string.IsNullOrWhiteSpace(label)) - { - return; - } - BeginDebugEventImpl(label); } @@ -358,11 +413,6 @@ public void EndDebugEvent() public void InsertDebugMarker(string label) { - if (string.IsNullOrWhiteSpace(label)) - { - return; - } - InsertDebugMarkerImpl(label); } @@ -381,42 +431,72 @@ internal void Reset() ResetImpl(); Context.Uploader.Release(this); + Context.Downloader.Release(this); currentPipeline = null; - currentFrameBuffer = null; } protected override void Destroy() { Context.Uploader.Release(this); + Context.Downloader.Release(this); currentPipeline = null; - currentFrameBuffer = null; } - protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dest, uint destOffsetInBytes, uint sizeInBytes); + private bool TryGetCurrentPipeline(out Pipeline pipeline) + { + if (currentPipeline is not null) + { + pipeline = currentPipeline; + + return true; + } + + pipeline = null!; + + return false; + } + + private bool TryGetCurrentPipeline(out TPipeline pipeline) where TPipeline : Pipeline + { + if (currentPipeline is TPipeline typedPipeline) + { + pipeline = typedPipeline; + + return true; + } + + pipeline = null!; + + return false; + } + + protected abstract void BarrierImpl(BarrierStages before, BarrierStages after); + + protected abstract void TransitionImpl(Texture texture, TextureSubresource subresource, TextureLayout before, TextureLayout after); - protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent destExtent); + protected abstract void CopyBufferImpl(Buffer src, uint srcOffsetInBytes, Buffer dst, uint dstOffsetInBytes, uint sizeInBytes); - protected abstract void CopyTextureImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, Texture dest, TextureSlice destSlice, TextureOffset destOffset, TextureExtent extent); + protected abstract void CopyBufferToTextureImpl(Buffer src, uint srcOffsetInBytes, uint srcRowStrideInBytes, uint srcSliceStrideInBytes, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D dstExtent); - protected abstract void CopyTextureToBufferImpl(Texture src, TextureSlice srcSlice, TextureOffset srcOffset, TextureExtent srcExtent, Buffer dest, uint destOffsetInBytes); + protected abstract void CopyTextureImpl(Texture src, TextureSubresource srcSubresource, Offset3D srcOffset, Texture dst, TextureSubresource dstSubresource, Offset3D dstOffset, Extent3D extent); - protected abstract void ResolveTextureImpl(Texture src, TextureSlice srcSlice, Texture dest, TextureSlice destSlice); + 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); protected abstract BottomLevelAccelerationStructure BuildAccelerationStructureImpl(BottomLevelAccelerationStructureDesc desc); protected abstract TopLevelAccelerationStructure BuildAccelerationStructureImpl(TopLevelAccelerationStructureDesc desc); - protected abstract void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc); - - protected abstract void BeginRenderPassImpl(FrameBuffer frameBuffer, ClearValue clearValue); + protected abstract void UpdateAccelerationStructureImpl(BottomLevelAccelerationStructure accelerationStructure, BottomLevelAccelerationStructureDesc newDesc); - protected abstract void EndRenderPassImpl(FrameBuffer frameBuffer); + protected abstract void UpdateAccelerationStructureImpl(TopLevelAccelerationStructure accelerationStructure, TopLevelAccelerationStructureDesc newDesc); - protected abstract void SetScissorsImpl(Scissor[] scissors); + protected abstract void BeginRenderPassImpl(ReadOnlySpan colorAttachments, DepthStencilAttachment? depthStencilAttachment); - protected abstract void SetViewportsImpl(Viewport[] viewports); + protected abstract void EndRenderPassImpl(); protected abstract void SetPipelineImpl(GraphicsPipeline pipeline); @@ -424,11 +504,19 @@ 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 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 format); + protected abstract void SetIndexBufferImpl(GraphicsPipeline pipeline, Buffer buffer, uint offsetInBytes, IndexFormat indexFormat); - protected abstract void SetResourceTableImpl(Pipeline pipeline, ResourceTable resourceTable); + 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/CommandQueue.cs b/sources/Zenith.NET/CommandQueue.cs index 9fde2e09..865261cd 100644 --- a/sources/Zenith.NET/CommandQueue.cs +++ b/sources/Zenith.NET/CommandQueue.cs @@ -3,68 +3,96 @@ 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 = []; public CommandQueueType Type { get; } = type; + public abstract Timeline Timeline { get; } + public CommandBuffer CommandBuffer() { + Poll(); + using Lock.Scope _ = @lock.EnterScope(); - CommandBuffer commandBuffer = available.Count is 0 ? CreateCommandBuffer() : available.Dequeue(); + CommandBuffer commandBuffer = commandBuffers.Count is 0 ? CreateCommandBuffer() : commandBuffers.Dequeue(); commandBuffer.Begin(); return commandBuffer; } - public void WaitIdle() + public double GetElapsedNanoseconds(ulong startTimestamp, ulong endTimestamp) { - using Lock.Scope _ = @lock.EnterScope(); + double timestampPeriod = GetTimestampPeriod(out uint validBits); - if (execution.Count is 0) + ulong elapsedTicks = unchecked(endTimestamp - startTimestamp); + + if (validBits is < 64) { - return; + elapsedTicks &= (1UL << (int)validBits) - 1; } - WaitIdleImpl(); + return elapsedTicks * timestampPeriod; + } - while (execution.TryDequeue(out CommandBuffer? commandBuffer)) - { - commandBuffer.Reset(); + internal TimelineValue Submit(ReadOnlySpan waits, CommandBuffer commandBuffer) + { + Poll(); - available.Enqueue(commandBuffer); - } + using Lock.Scope _ = @lock.EnterScope(); + + commandBuffer.End(); + + SubmitImpl(waits, commandBuffer); + + TimelineValue timelineValue = Timeline.Signal(); + + submitteds.Enqueue(new(commandBuffer, timelineValue)); + + return timelineValue; } - internal void Submit(CommandBuffer commandBuffer) + internal void Poll() { using Lock.Scope _ = @lock.EnterScope(); - commandBuffer.End(); + while (submitteds.TryPeek(out Submitted submitted) && submitted.TimelineValue.IsCompleted) + { + submitteds.Dequeue(); - SubmitImpl(commandBuffer); + submitted.CommandBuffer.Reset(); - execution.Enqueue(commandBuffer); + commandBuffers.Enqueue(submitted.CommandBuffer); + } } protected abstract CommandBuffer CreateCommandBuffer(); - protected abstract void WaitIdleImpl(); + protected abstract double GetTimestampPeriod(out uint validBits); - protected abstract void SubmitImpl(CommandBuffer commandBuffer); + protected abstract void SubmitImpl(ReadOnlySpan waits, CommandBuffer commandBuffer); protected override void Destroy() { - while (available.TryDequeue(out CommandBuffer? commandBuffer)) + Timeline.Dispose(); + + while (commandBuffers.TryDequeue(out CommandBuffer? commandBuffer)) { commandBuffer.Dispose(); } - while (execution.TryDequeue(out CommandBuffer? commandBuffer)) + while (submitteds.TryDequeue(out Submitted submitted)) { - commandBuffer.Dispose(); + submitted.CommandBuffer.Dispose(); } } + + private readonly struct Submitted(CommandBuffer commandBuffer, TimelineValue timelineValue) + { + public readonly CommandBuffer CommandBuffer = commandBuffer; + + public readonly TimelineValue TimelineValue = timelineValue; + } } diff --git a/sources/Zenith.NET/DepthStencilStates.cs b/sources/Zenith.NET/DepthStencilStates.cs deleted file mode 100644 index 92867876..00000000 --- a/sources/Zenith.NET/DepthStencilStates.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Zenith.NET; - -public static class DepthStencilStates -{ - public static readonly DepthStencilState Default = new() - { - DepthEnable = true, - DepthWriteEnable = true, - DepthFunc = ComparisonFunc.LessEqual, - StencilEnable = false, - StencilReadMask = 0xFF, - StencilWriteMask = 0xFF, - FrontFace = new() - { - StencilFailOp = StencilOp.Keep, - StencilDepthFailOp = StencilOp.Keep, - StencilPassOp = StencilOp.Keep, - StencilFunc = ComparisonFunc.Always - }, - BackFace = new() - { - StencilFailOp = StencilOp.Keep, - StencilDepthFailOp = StencilOp.Keep, - StencilPassOp = StencilOp.Keep, - StencilFunc = ComparisonFunc.Always - } - }; - - public static readonly DepthStencilState DefaultInverted = Default with - { - DepthFunc = ComparisonFunc.GreaterEqual - }; - - public static readonly DepthStencilState DepthRead = Default with - { - DepthWriteEnable = false - }; - - public static readonly DepthStencilState None = Default with - { - DepthEnable = false, - DepthWriteEnable = false - }; -} diff --git a/sources/Zenith.NET/DisposableObject.cs b/sources/Zenith.NET/DisposableObject.cs index a34deed6..c35ecc86 100644 --- a/sources/Zenith.NET/DisposableObject.cs +++ b/sources/Zenith.NET/DisposableObject.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public abstract class DisposableObject : IDisposableObject +public abstract class DisposableObject : IDisposable { private volatile uint isDisposed; diff --git a/sources/Zenith.NET/Downloader.cs b/sources/Zenith.NET/Downloader.cs new file mode 100644 index 00000000..c9955133 --- /dev/null +++ b/sources/Zenith.NET/Downloader.cs @@ -0,0 +1,116 @@ +namespace Zenith.NET; + +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 = []; + + public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, TransferLayout layout) + { + 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, + Usages = BufferUsages.TransferDst, + Residency = MemoryResidency.CpuReadOnly + })); + } + + leases.Add(lease.Borrow(layout)); + + 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 void CleanupExpiredLeases() + { + available.RemoveAll(static item => item.TryExpire()); + } + + private class Lease(Buffer buffer) + { + private DateTime expirationTime = DateTime.UtcNow + LeaseLifetime; + + private TransferLayout layout; + + public Buffer Buffer { get; } = buffer; + + public bool HasCapacityFor(uint sizeInBytes) + { + return Buffer.Desc.SizeInBytes >= sizeInBytes; + } + + public Lease Borrow(TransferLayout layout) + { + this.layout = layout; + + return this; + } + + public bool TryExpire() + { + if (DateTime.UtcNow >= expirationTime) + { + Release(); + + return true; + } + + return false; + } + + public Lease Renew() + { + expirationTime = DateTime.UtcNow + LeaseLifetime; + + layout.Download(Buffer); + layout = default; + + return this; + } + + public void Release() + { + Buffer.Dispose(); + } + } +} 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/BarrierStages.cs b/sources/Zenith.NET/Enums/BarrierStages.cs new file mode 100644 index 00000000..6745175e --- /dev/null +++ b/sources/Zenith.NET/Enums/BarrierStages.cs @@ -0,0 +1,19 @@ +namespace Zenith.NET; + +[Flags] +public enum BarrierStages +{ + None = 0, + + VertexShading = 1 << 0, + + FragmentShading = 1 << 1, + + ComputeShading = 1 << 2, + + Copy = 1 << 3, + + Resolve = 1 << 4, + + All = 1 << 5 +} diff --git a/sources/Zenith.NET/Enums/Blend.cs b/sources/Zenith.NET/Enums/Blend.cs deleted file mode 100644 index 1d25ebe9..00000000 --- a/sources/Zenith.NET/Enums/Blend.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Zenith.NET; - -public enum Blend -{ - Zero, - - One, - - SrcAlpha, - - InverseSrcAlpha, - - DestAlpha, - - InverseDestAlpha, - - SrcColor, - - InverseSrcColor, - - DestColor, - - InverseDestColor, - - 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/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..025507ce --- /dev/null +++ b/sources/Zenith.NET/Enums/BufferUsages.cs @@ -0,0 +1,23 @@ +namespace Zenith.NET; + +[Flags] +public enum BufferUsages +{ + None = 0, + + Vertex = 1 << 0, + + Index = 1 << 1, + + Indirect = 1 << 2, + + Constant = 1 << 3, + + StorageReadOnly = 1 << 4, + + StorageReadWrite = 1 << 5, + + TransferSrc = 1 << 6, + + TransferDst = 1 << 7 +} 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/ColorComponentFlags.cs b/sources/Zenith.NET/Enums/ColorWrites.cs similarity index 84% rename from sources/Zenith.NET/Enums/ColorComponentFlags.cs rename to sources/Zenith.NET/Enums/ColorWrites.cs index ba1b463d..46af0abd 100644 --- a/sources/Zenith.NET/Enums/ColorComponentFlags.cs +++ b/sources/Zenith.NET/Enums/ColorWrites.cs @@ -1,7 +1,7 @@ namespace Zenith.NET; [Flags] -public enum ColorComponentFlags +public enum ColorWrites { None = 0, 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/Enums/ComparisonFunc.cs b/sources/Zenith.NET/Enums/CompareOp.cs similarity index 83% rename from sources/Zenith.NET/Enums/ComparisonFunc.cs rename to sources/Zenith.NET/Enums/CompareOp.cs index 067e247f..f5d0426a 100644 --- a/sources/Zenith.NET/Enums/ComparisonFunc.cs +++ b/sources/Zenith.NET/Enums/CompareOp.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public enum ComparisonFunc +public enum CompareOp { Never, diff --git a/sources/Zenith.NET/Enums/ElementFormat.cs b/sources/Zenith.NET/Enums/ElementFormat.cs index 4e62dedd..9587dc36 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, @@ -79,4 +79,4 @@ public enum ElementFormat Int3, Int4 -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Enums/ElementSemantic.cs b/sources/Zenith.NET/Enums/ElementSemantic.cs index 76b28624..d3fbe849 100644 --- a/sources/Zenith.NET/Enums/ElementSemantic.cs +++ b/sources/Zenith.NET/Enums/ElementSemantic.cs @@ -10,13 +10,11 @@ public enum ElementSemantic Tangent, - Binormal, + Bitangent, Color, 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/LoadOp.cs new file mode 100644 index 00000000..63d41e53 --- /dev/null +++ b/sources/Zenith.NET/Enums/LoadOp.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public enum LoadOp +{ + Load, + + Clear, + + DontCare +} diff --git a/sources/Zenith.NET/Enums/MemoryResidency.cs b/sources/Zenith.NET/Enums/MemoryResidency.cs new file mode 100644 index 00000000..fa464c52 --- /dev/null +++ b/sources/Zenith.NET/Enums/MemoryResidency.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public enum MemoryResidency +{ + GpuOnly, + + CpuReadOnly, + + CpuWriteOnly +} 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/MessageSource.cs b/sources/Zenith.NET/Enums/MessageSource.cs deleted file mode 100644 index a2bc5f42..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/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/NativeTextureType.cs b/sources/Zenith.NET/Enums/NativeTextureType.cs new file mode 100644 index 00000000..307ca4cb --- /dev/null +++ b/sources/Zenith.NET/Enums/NativeTextureType.cs @@ -0,0 +1,18 @@ +namespace Zenith.NET; + +public enum NativeTextureType +{ + D3D11TextureNtHandle, + + D3D12ResourceNtHandle, + + MTLSharedTextureHandle, + + IOSurfaceRef, + + VulkanOpaqueNtHandle, + + VulkanOpaquePosixFileDescriptor, + + VulkanAndroidHardwareBuffer +} 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/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/RayTracingInstanceFlags.cs b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs index 6d1d859f..01c609bc 100644 --- a/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs +++ b/sources/Zenith.NET/Enums/RayTracingInstanceFlags.cs @@ -5,11 +5,11 @@ public enum RayTracingInstanceFlags { None = 0, - TriangleCullDisable = 1 << 0, + FrontCounterClockwise = 1 << 0, - TriangleFrontCounterClockwise = 1 << 1, + DisableCull = 1 << 1, ForceOpaque = 1 << 2, - ForceNoOpaque = 1 << 3 + ForceNonOpaque = 1 << 3 } diff --git a/sources/Zenith.NET/Enums/ResourceType.cs b/sources/Zenith.NET/Enums/ResourceType.cs deleted file mode 100644 index 8064f5bc..00000000 --- a/sources/Zenith.NET/Enums/ResourceType.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Zenith.NET; - -public enum ResourceType -{ - ConstantBuffer, - - StructuredBuffer, - - StructuredBufferReadWrite, - - Texture, - - TextureReadWrite, - - Sampler, - - AccelerationStructure -} diff --git a/sources/Zenith.NET/Enums/ShaderStageFlags.cs b/sources/Zenith.NET/Enums/ShaderStageFlags.cs deleted file mode 100644 index 29d1f842..00000000 --- a/sources/Zenith.NET/Enums/ShaderStageFlags.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum ShaderStageFlags -{ - None = 0, - - Vertex = 1 << 0, - - Pixel = 1 << 1, - - Compute = 1 << 2, - - Amplification = 1 << 3, - - Mesh = 1 << 4 -} \ 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..4d7b4033 --- /dev/null +++ b/sources/Zenith.NET/Enums/StoreOp.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public enum StoreOp +{ + Store, + + DontCare +} 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/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/Enums/TextureLayout.cs b/sources/Zenith.NET/Enums/TextureLayout.cs new file mode 100644 index 00000000..c7d5dc40 --- /dev/null +++ b/sources/Zenith.NET/Enums/TextureLayout.cs @@ -0,0 +1,28 @@ +namespace Zenith.NET; + +public enum TextureLayout +{ + Undefined, + + Common, + + Sampled, + + Storage, + + ColorAttachment, + + DepthStencilAttachment, + + DepthStencilReadOnly, + + CopySrc, + + CopyDst, + + ResolveSrc, + + ResolveDst, + + Present +} 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/TextureUsageFlags.cs b/sources/Zenith.NET/Enums/TextureUsageFlags.cs deleted file mode 100644 index 61835550..00000000 --- a/sources/Zenith.NET/Enums/TextureUsageFlags.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Zenith.NET; - -[Flags] -public enum TextureUsageFlags -{ - None = 0, - - RenderTarget = 1 << 0, - - DepthStencil = 1 << 1, - - ShaderResource = 1 << 2, - - UnorderedAccess = 1 << 3 -} diff --git a/sources/Zenith.NET/Enums/TextureUsages.cs b/sources/Zenith.NET/Enums/TextureUsages.cs new file mode 100644 index 00000000..3ada0a1e --- /dev/null +++ b/sources/Zenith.NET/Enums/TextureUsages.cs @@ -0,0 +1,19 @@ +namespace Zenith.NET; + +[Flags] +public enum TextureUsages +{ + None = 0, + + Sampled = 1 << 0, + + Storage = 1 << 1, + + ColorAttachment = 1 << 2, + + DepthStencilAttachment = 1 << 3, + + TransferSrc = 1 << 4, + + TransferDst = 1 << 5 +} 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 9facf070..7e6d14ca 100644 --- a/sources/Zenith.NET/GraphicsContext.cs +++ b/sources/Zenith.NET/GraphicsContext.cs @@ -2,168 +2,147 @@ public abstract class GraphicsContext : DisposableObject { - public const uint ConstantBufferAlignment = 256; - - public const uint TextureRowPitchAlignment = 256; - - public const uint TextureDepthPitchAlignment = 512; - - protected GraphicsContext(Backend backend, bool useValidationLayer) + protected GraphicsContext(GraphicsApi graphicsApi, bool useValidationLayer) { - Backend = backend; + GraphicsApi = graphicsApi; Initialize(useValidationLayer, out Capabilities capabilities, - out CommandQueue graphics, - out CommandQueue compute, - out CommandQueue copy, + out CommandQueue graphicsQueue, + out CommandQueue computeQueue, + out CommandQueue transferQueue, out ValidationLayer? validationLayer); Capabilities = capabilities; - Graphics = graphics; - Compute = compute; - Copy = copy; + GraphicsQueue = graphicsQueue; + ComputeQueue = computeQueue; + TransferQueue = transferQueue; ValidationLayer = validationLayer; Uploader = new(this); + Downloader = new(this); } - public Backend Backend { get; } + public GraphicsApi GraphicsApi { get; } 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 TransferQueue { get; } internal ValidationLayer? ValidationLayer { get; } internal Uploader Uploader { get; } - public event EventHandler? ValidationMessage; + internal Downloader Downloader { get; } + + public event EventHandler? ValidationMessage; public SwapChain CreateSwapChain(SwapChainDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateSwapChainImpl(desc); } - public FrameBuffer CreateFrameBuffer(FrameBufferDesc desc) + public Heap CreateHeap(HeapDesc desc) { - ValidationLayer?.ValidateDesc(desc); - - return CreateFrameBufferImpl(desc); + return CreateHeapImpl(desc); } - public Shader CreateShader(ShaderDesc desc) + public SizeAndAlignment GetSizeAndAlignment(BufferDesc desc) { - ValidationLayer?.ValidateDesc(desc); + return GetSizeAndAlignmentImpl(desc); + } - return CreateShaderImpl(desc); + public SizeAndAlignment GetSizeAndAlignment(TextureDesc 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) + public Texture CreateTexture(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture) { - ValidationLayer?.ValidateDesc(desc); + return CreateTextureImpl(desc, nativeTextureType, nativeTexture); + } + public TextureView CreateTextureView(TextureViewDesc desc) + { return CreateTextureViewImpl(desc); } public Sampler CreateSampler(SamplerDesc desc) { - ValidationLayer?.ValidateDesc(desc); - return CreateSamplerImpl(desc); } - public ResourceLayout CreateResourceLayout(ResourceLayoutDesc desc) - { - ValidationLayer?.ValidateDesc(desc); - - return CreateResourceLayoutImpl(desc); - } - - public ResourceTable CreateResourceTable(ResourceTableDesc desc) + public Shader CreateShader(ShaderDesc desc) { - ValidationLayer?.ValidateDesc(desc); - - return CreateResourceTableImpl(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); } + public abstract nint GetNativeObject(NativeObjectType type); + protected override void Destroy() { - Graphics.Dispose(); - Compute.Dispose(); - Copy.Dispose(); + GraphicsQueue.Dispose(); + ComputeQueue.Dispose(); + TransferQueue.Dispose(); ValidationLayer?.Dispose(); Uploader.Dispose(); + Downloader.Dispose(); } 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 transferQueue, out ValidationLayer? validationLayer); protected abstract SwapChain CreateSwapChainImpl(SwapChainDesc desc); - protected abstract FrameBuffer CreateFrameBufferImpl(FrameBufferDesc desc); + protected abstract Heap CreateHeapImpl(HeapDesc desc); - protected abstract Shader CreateShaderImpl(ShaderDesc desc); + protected abstract SizeAndAlignment GetSizeAndAlignmentImpl(BufferDesc desc); + + protected abstract SizeAndAlignment GetSizeAndAlignmentImpl(TextureDesc desc); protected abstract Buffer CreateBufferImpl(BufferDesc desc); @@ -171,13 +150,13 @@ protected abstract void Initialize(bool useValidationLayer, protected abstract Texture CreateTextureImpl(TextureDesc desc); + protected abstract Texture CreateTextureImpl(TextureDesc desc, NativeTextureType nativeTextureType, nint nativeTexture); + protected abstract TextureView CreateTextureViewImpl(TextureViewDesc desc); protected abstract Sampler CreateSamplerImpl(SamplerDesc desc); - protected abstract ResourceLayout CreateResourceLayoutImpl(ResourceLayoutDesc desc); - - protected abstract ResourceTable CreateResourceTableImpl(ResourceTableDesc desc); + protected abstract Shader CreateShaderImpl(ShaderDesc desc); protected abstract GraphicsPipeline CreateGraphicsPipelineImpl(GraphicsPipelineDesc desc); @@ -187,7 +166,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/GraphicsResource.cs b/sources/Zenith.NET/GraphicsResource.cs index 8dcac064..ccc8805a 100644 --- a/sources/Zenith.NET/GraphicsResource.cs +++ b/sources/Zenith.NET/GraphicsResource.cs @@ -11,15 +11,14 @@ public string Name { field = value; - if (!string.IsNullOrWhiteSpace(value)) - { - SetResourceName(value); - } + SetResourceName(value); } } } = string.Empty; protected GraphicsContext Context => context; + public abstract nint GetNativeObject(NativeObjectType type); + protected abstract void SetResourceName(string name); } diff --git a/sources/Zenith.NET/Heap.cs b/sources/Zenith.NET/Heap.cs new file mode 100644 index 00000000..f8c7175f --- /dev/null +++ b/sources/Zenith.NET/Heap.cs @@ -0,0 +1,22 @@ +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) + { + return CreateBufferImpl(offsetInBytes, desc); + } + + public Texture CreateTexture(ulong offsetInBytes, TextureDesc 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/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/IDisposableObject.cs b/sources/Zenith.NET/Interfaces/IDisposableObject.cs deleted file mode 100644 index a58a59bb..00000000 --- a/sources/Zenith.NET/Interfaces/IDisposableObject.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Zenith.NET; - -public interface IDisposableObject : IDisposable -{ - bool IsDisposed { get; } -} 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/RasterizerStates.cs b/sources/Zenith.NET/RasterizerStates.cs deleted file mode 100644 index 94da7783..00000000 --- a/sources/Zenith.NET/RasterizerStates.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Zenith.NET; - -public static class RasterizerStates -{ - public static readonly RasterizerState Default = new() - { - CullMode = CullMode.None, - FillMode = FillMode.Solid, - FrontFace = FrontFace.CounterClockwise, - DepthBias = 0, - DepthBiasClamp = 0.0f, - SlopeScaledDepthBias = 0.0f, - DepthClipEnable = true, - ScissorEnable = false - }; - - public static readonly RasterizerState CullFront = Default with - { - CullMode = CullMode.Front - }; - - public static readonly RasterizerState CullBack = Default with - { - CullMode = CullMode.Back - }; - - public static readonly RasterizerState CullNone = Default with - { - CullMode = CullMode.None - }; - - public static readonly RasterizerState WireframeCullFront = Default with - { - CullMode = CullMode.Front, - FillMode = FillMode.Wireframe - }; - - public static readonly RasterizerState WireframeCullBack = Default with - { - CullMode = CullMode.Back, - FillMode = FillMode.Wireframe - }; - - public static readonly RasterizerState Wireframe = Default with - { - CullMode = CullMode.None, - FillMode = FillMode.Wireframe - }; -} 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 deleted file mode 100644 index 8e9082e6..00000000 --- a/sources/Zenith.NET/ResourceTable.cs +++ /dev/null @@ -1,15 +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; - - internal void Preprocess(CommandBuffer commandBuffer) - { - PreprocessImpl(commandBuffer); - } - - protected abstract void PreprocessImpl(CommandBuffer commandBuffer); -} diff --git a/sources/Zenith.NET/Sampler.cs b/sources/Zenith.NET/Sampler.cs index 7569c729..4ff48e79 100644 --- a/sources/Zenith.NET/Sampler.cs +++ b/sources/Zenith.NET/Sampler.cs @@ -1,8 +1,10 @@ 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; public ref readonly SamplerDesc Desc => ref desc; + + public abstract ResourceHandle Handle { get; } } diff --git a/sources/Zenith.NET/Structs/AttachmentFormats.cs b/sources/Zenith.NET/Structs/AttachmentFormats.cs new file mode 100644 index 00000000..46417a15 --- /dev/null +++ b/sources/Zenith.NET/Structs/AttachmentFormats.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public struct AttachmentFormats +{ + public PixelFormat[] ColorFormats; + + public PixelFormat? DepthStencilFormat; + + public SampleCount SampleCount; +} diff --git a/sources/Zenith.NET/Structs/BlendState.cs b/sources/Zenith.NET/Structs/BlendState.cs index 5e11d7f5..f1fc0d0f 100644 --- a/sources/Zenith.NET/Structs/BlendState.cs +++ b/sources/Zenith.NET/Structs/BlendState.cs @@ -1,24 +1,74 @@ namespace Zenith.NET; -public record struct BlendState +public 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; -} \ No newline at end of file + 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/BlendStateRenderTarget.cs b/sources/Zenith.NET/Structs/BlendStateRenderTarget.cs deleted file mode 100644 index df35c77f..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 Blend SrcBlend; - - public Blend DestBlend; - - public BlendOp BlendOp; - - public Blend SrcBlendAlpha; - - public Blend DestBlendAlpha; - - public BlendOp BlendOpAlpha; - - public ColorComponentFlags Flags; -} diff --git a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs index b6e38f58..fad42b00 100644 --- a/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs +++ b/sources/Zenith.NET/Structs/BottomLevelAccelerationStructureDesc.cs @@ -1,8 +1,8 @@ namespace Zenith.NET; -public record struct BottomLevelAccelerationStructureDesc +public struct BottomLevelAccelerationStructureDesc { public RayTracingGeometry[] Geometries; - public AccelerationStructureBuildFlags Flags; + public AccelerationStructureBuildFlags BuildFlags; } diff --git a/sources/Zenith.NET/Structs/BufferData.cs b/sources/Zenith.NET/Structs/BufferData.cs new file mode 100644 index 00000000..c5987655 --- /dev/null +++ b/sources/Zenith.NET/Structs/BufferData.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public struct BufferData +{ + public nint Pointer; + + public uint SizeInBytes; +} diff --git a/sources/Zenith.NET/Structs/BufferDesc.cs b/sources/Zenith.NET/Structs/BufferDesc.cs index de4a5743..594f9317 100644 --- a/sources/Zenith.NET/Structs/BufferDesc.cs +++ b/sources/Zenith.NET/Structs/BufferDesc.cs @@ -1,10 +1,89 @@ namespace Zenith.NET; -public record struct BufferDesc +public struct BufferDesc { public uint SizeInBytes; public uint StrideInBytes; - public BufferUsageFlags Flags; + public BufferUsages Usages; + + public MemoryResidency Residency; + + public static BufferDesc Vertex(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Usages = BufferUsages.Vertex | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc Index(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Usages = BufferUsages.Index | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc Indirect(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Usages = BufferUsages.Indirect | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc Constant(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Usages = BufferUsages.Constant | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc StorageReadOnly(uint sizeInBytes, uint strideInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes, + Usages = BufferUsages.StorageReadOnly | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc StorageReadWrite(uint sizeInBytes, uint strideInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = strideInBytes, + Usages = BufferUsages.StorageReadWrite | BufferUsages.TransferDst, + Residency = MemoryResidency.GpuOnly + }; + } + + public static BufferDesc Staging(uint sizeInBytes) + { + return new() + { + SizeInBytes = sizeInBytes, + StrideInBytes = 0, + Usages = BufferUsages.TransferSrc, + Residency = MemoryResidency.CpuWriteOnly + }; + } } diff --git a/sources/Zenith.NET/Structs/BufferViewDesc.cs b/sources/Zenith.NET/Structs/BufferViewDesc.cs index 472c771a..1295d57e 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; @@ -9,4 +9,37 @@ public record struct BufferViewDesc public uint SizeInBytes; public uint StrideInBytes; + + public static BufferViewDesc Constant(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/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..36669a50 --- /dev/null +++ b/sources/Zenith.NET/Structs/ColorAttachment.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace Zenith.NET; + +public struct ColorAttachment +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public LoadOp LoadOp; + + public StoreOp StoreOp; + + public Vector4 ClearColor; + + public static ColorAttachment Clear(Texture texture, Vector4 clearColor) + { + return new() + { + Texture = texture, + Subresource = new(), + LoadOp = LoadOp.Clear, + StoreOp = StoreOp.Store, + ClearColor = clearColor + }; + } + + public static ColorAttachment Load(Texture texture) + { + return new() + { + Texture = texture, + Subresource = new(), + LoadOp = LoadOp.Load, + StoreOp = StoreOp.Store, + ClearColor = Vector4.Zero + }; + } + + public static ColorAttachment DontCare(Texture texture) + { + return new() + { + Texture = texture, + Subresource = 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 new file mode 100644 index 00000000..d951590c --- /dev/null +++ b/sources/Zenith.NET/Structs/ColorAttachmentBlendState.cs @@ -0,0 +1,95 @@ +namespace Zenith.NET; + +public struct ColorAttachmentBlendState +{ + public bool IsBlendingEnabled; + + public BlendFactor SrcRgbFactor; + + public BlendFactor DstRgbFactor; + + public BlendOp RgbOp; + + public BlendFactor SrcAlphaFactor; + + public BlendFactor DstAlphaFactor; + + 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.OneMinusSrcAlpha, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.One, + DstAlphaFactor = BlendFactor.OneMinusSrcAlpha, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + }; + } + + public static ColorAttachmentBlendState Additive() + { + return new() + { + IsBlendingEnabled = true, + SrcRgbFactor = BlendFactor.SrcAlpha, + DstRgbFactor = BlendFactor.One, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.SrcAlpha, + DstAlphaFactor = BlendFactor.One, + AlphaOp = BlendOp.Add, + ColorWrites = ColorWrites.All + }; + } + + public static ColorAttachmentBlendState NonPremultiplied() + { + return new() + { + IsBlendingEnabled = true, + SrcRgbFactor = BlendFactor.SrcAlpha, + DstRgbFactor = BlendFactor.OneMinusSrcAlpha, + RgbOp = BlendOp.Add, + SrcAlphaFactor = BlendFactor.SrcAlpha, + DstAlphaFactor = BlendFactor.OneMinusSrcAlpha, + 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 + }; + } +} diff --git a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs index e659023f..9d7c4ed3 100644 --- a/sources/Zenith.NET/Structs/ComputePipelineDesc.cs +++ b/sources/Zenith.NET/Structs/ComputePipelineDesc.cs @@ -1,14 +1,6 @@ namespace Zenith.NET; -public record struct ComputePipelineDesc +public struct ComputePipelineDesc { - public Shader Compute; - - public ResourceLayout? ResourceLayout; - - public uint ThreadGroupSizeX; - - public uint ThreadGroupSizeY; - - public uint ThreadGroupSizeZ; + public Shader ComputeShader; } diff --git a/sources/Zenith.NET/Structs/DepthStencilAttachment.cs b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs new file mode 100644 index 00000000..29123ec9 --- /dev/null +++ b/sources/Zenith.NET/Structs/DepthStencilAttachment.cs @@ -0,0 +1,65 @@ +namespace Zenith.NET; + +public struct DepthStencilAttachment +{ + public Texture Texture; + + public TextureSubresource Subresource; + + public LoadOp DepthLoadOp; + + public StoreOp DepthStoreOp; + + public float ClearDepth; + + public LoadOp StencilLoadOp; + + 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 d96c15e5..90b2ee52 100644 --- a/sources/Zenith.NET/Structs/DepthStencilState.cs +++ b/sources/Zenith.NET/Structs/DepthStencilState.cs @@ -1,20 +1,95 @@ namespace Zenith.NET; -public record struct DepthStencilState +public struct DepthStencilState { - public bool DepthEnable; + public bool IsDepthEnabled; - public bool DepthWriteEnable; + public bool IsDepthWriteEnabled; - public ComparisonFunc DepthFunc; + public CompareOp DepthCompareOp; - public bool StencilEnable; + public bool IsStencilEnabled; public byte StencilReadMask; public byte StencilWriteMask; - public DepthStencilStateOp FrontFace; + public StencilFaceState FrontFace; - public DepthStencilStateOp BackFace; + 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/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/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..2dd5d5af 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 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/GraphicsPipelineDesc.cs b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs index 093d74a2..4f940fbc 100644 --- a/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/GraphicsPipelineDesc.cs @@ -1,18 +1,16 @@ namespace Zenith.NET; -public record struct GraphicsPipelineDesc +public struct GraphicsPipelineDesc { - public RenderStates RenderStates; + public Shader VertexShader; - public Shader Vertex; - - public Shader Pixel; - - public ResourceLayout? ResourceLayout; + public Shader FragmentShader; public InputLayout[] InputLayouts; public PrimitiveTopology PrimitiveTopology; - public Output Output; + public AttachmentFormats AttachmentFormats; + + public RenderState RenderState; } diff --git a/sources/Zenith.NET/Structs/HeapDesc.cs b/sources/Zenith.NET/Structs/HeapDesc.cs new file mode 100644 index 00000000..8087b7cf --- /dev/null +++ b/sources/Zenith.NET/Structs/HeapDesc.cs @@ -0,0 +1,35 @@ +namespace Zenith.NET; + +public struct HeapDesc +{ + 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/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 ccb880c8..51224223 100644 --- a/sources/Zenith.NET/Structs/InputElement.cs +++ b/sources/Zenith.NET/Structs/InputElement.cs @@ -1,12 +1,12 @@ namespace Zenith.NET; -public record struct InputElement +public struct InputElement { public ElementFormat Format; 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..d72e79cd 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[] Elements; diff --git a/sources/Zenith.NET/Structs/MappedMemory.cs b/sources/Zenith.NET/Structs/MappedMemory.cs deleted file mode 100644 index 6bec606a..00000000 --- a/sources/Zenith.NET/Structs/MappedMemory.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Zenith.NET; - -public readonly record struct MappedMemory(nint Pointer, uint SizeInBytes); diff --git a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs index d7af84d6..ab7d59bf 100644 --- a/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs +++ b/sources/Zenith.NET/Structs/MeshShadingPipelineDesc.cs @@ -1,30 +1,16 @@ namespace Zenith.NET; -public record struct MeshShadingPipelineDesc +public struct MeshShadingPipelineDesc { - public RenderStates RenderStates; + public Shader? TaskShader; - public Shader? Amplification; + public Shader MeshShader; - public Shader Mesh; - - public Shader Pixel; - - public ResourceLayout? ResourceLayout; + public Shader FragmentShader; public PrimitiveTopology PrimitiveTopology; - public Output Output; - - public uint AmplificationThreadGroupSizeX; - - public uint AmplificationThreadGroupSizeY; - - public uint AmplificationThreadGroupSizeZ; - - public uint MeshThreadGroupSizeX; - - public uint MeshThreadGroupSizeY; + public AttachmentFormats AttachmentFormats; - public uint MeshThreadGroupSizeZ; + public RenderState RenderState; } diff --git a/sources/Zenith.NET/Structs/TextureOffset.cs b/sources/Zenith.NET/Structs/Offset3D.cs similarity index 71% rename from sources/Zenith.NET/Structs/TextureOffset.cs rename to sources/Zenith.NET/Structs/Offset3D.cs index d1c1640e..f4a0adb0 100644 --- a/sources/Zenith.NET/Structs/TextureOffset.cs +++ b/sources/Zenith.NET/Structs/Offset3D.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct TextureOffset +public struct Offset3D { public uint X; 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/QueryHeapDesc.cs b/sources/Zenith.NET/Structs/QueryHeapDesc.cs index ed7deb71..23c6b4c6 100644 --- a/sources/Zenith.NET/Structs/QueryHeapDesc.cs +++ b/sources/Zenith.NET/Structs/QueryHeapDesc.cs @@ -1,8 +1,35 @@ namespace Zenith.NET; -public record struct QueryHeapDesc +public struct QueryHeapDesc { public QueryType Type; public uint 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 + }; + } + + public static QueryHeapDesc Timestamp(uint count) + { + return new() + { + Type = QueryType.Timestamp, + Count = count + }; + } } diff --git a/sources/Zenith.NET/Structs/RasterizerState.cs b/sources/Zenith.NET/Structs/RasterizerState.cs index 2c9afe2c..8087380e 100644 --- a/sources/Zenith.NET/Structs/RasterizerState.cs +++ b/sources/Zenith.NET/Structs/RasterizerState.cs @@ -1,20 +1,102 @@ namespace Zenith.NET; -public record struct RasterizerState +public struct RasterizerState { - public CullMode CullMode; - public FillMode FillMode; + public CullMode CullMode; + public FrontFace FrontFace; public int DepthBias; public float DepthBiasClamp; - public float SlopeScaledDepthBias; + public float DepthBiasSlopeScale; + + public bool IsDepthClipEnabled; + + 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 + }; + } + + 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 + }; + } + + 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 + }; + } + + 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 + }; + } - public bool DepthClipEnable; + 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 + }; + } - public bool ScissorEnable; + 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 + }; + } } diff --git a/sources/Zenith.NET/Structs/RayTracingAABBs.cs b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs similarity index 79% rename from sources/Zenith.NET/Structs/RayTracingAABBs.cs rename to sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs index b8470a79..2da0bd7b 100644 --- a/sources/Zenith.NET/Structs/RayTracingAABBs.cs +++ b/sources/Zenith.NET/Structs/RayTracingAabbGeometry.cs @@ -1,6 +1,6 @@ namespace Zenith.NET; -public record struct RayTracingAABBs +public struct RayTracingAabbGeometry { public Buffer Buffer; diff --git a/sources/Zenith.NET/Structs/RayTracingGeometry.cs b/sources/Zenith.NET/Structs/RayTracingGeometry.cs index 3ad5e841..3e8cc7aa 100644 --- a/sources/Zenith.NET/Structs/RayTracingGeometry.cs +++ b/sources/Zenith.NET/Structs/RayTracingGeometry.cs @@ -1,12 +1,34 @@ namespace Zenith.NET; -public record struct RayTracingGeometry +public struct RayTracingGeometry { public RayTracingGeometryType Type; - public RayTracingTriangles Triangles; + public RayTracingTriangleGeometry TriangleGeometry; - public RayTracingAABBs AABBs; + public RayTracingAabbGeometry AabbGeometry; - public RayTracingGeometryFlags Flags; + 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/RayTracingInstance.cs b/sources/Zenith.NET/Structs/RayTracingInstance.cs index 599c7ead..c92911c9 100644 --- a/sources/Zenith.NET/Structs/RayTracingInstance.cs +++ b/sources/Zenith.NET/Structs/RayTracingInstance.cs @@ -2,13 +2,13 @@ namespace Zenith.NET; -public record struct RayTracingInstance +public struct RayTracingInstance { public BottomLevelAccelerationStructure AccelerationStructure; - public uint ID; + public uint InstanceId; - public byte Mask; + public byte VisibilityMask; public Matrix4x4 Transform; diff --git a/sources/Zenith.NET/Structs/RayTracingTriangles.cs b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs similarity index 90% rename from sources/Zenith.NET/Structs/RayTracingTriangles.cs rename to sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs index 217dd673..7499cd72 100644 --- a/sources/Zenith.NET/Structs/RayTracingTriangles.cs +++ b/sources/Zenith.NET/Structs/RayTracingTriangleGeometry.cs @@ -2,7 +2,7 @@ namespace Zenith.NET; -public record struct RayTracingTriangles +public struct RayTracingTriangleGeometry { public Buffer VertexBuffer; diff --git a/sources/Zenith.NET/Structs/RenderState.cs b/sources/Zenith.NET/Structs/RenderState.cs new file mode 100644 index 00000000..2cd62590 --- /dev/null +++ b/sources/Zenith.NET/Structs/RenderState.cs @@ -0,0 +1,10 @@ +namespace Zenith.NET; + +public struct RenderState +{ + public RasterizerState Rasterizer; + + public DepthStencilState DepthStencil; + + public BlendState Blend; +} diff --git a/sources/Zenith.NET/Structs/RenderStates.cs b/sources/Zenith.NET/Structs/RenderStates.cs deleted file mode 100644 index 964ba5be..00000000 --- a/sources/Zenith.NET/Structs/RenderStates.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Numerics; - -namespace Zenith.NET; - -public record struct RenderStates -{ - public RasterizerState RasterizerState; - - public DepthStencilState DepthStencilState; - - public BlendState BlendState; - - public uint StencilReference; - - public Vector4? BlendFactor; -} 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/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/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/ResourceTableDesc.cs b/sources/Zenith.NET/Structs/ResourceTableDesc.cs deleted file mode 100644 index 31c227ea..00000000 --- a/sources/Zenith.NET/Structs/ResourceTableDesc.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Zenith.NET; - -public record struct ResourceTableDesc -{ - public ResourceLayout Layout; - - public IBindableResource[] Resources; -} diff --git a/sources/Zenith.NET/Structs/SamplerDesc.cs b/sources/Zenith.NET/Structs/SamplerDesc.cs index 2361937c..073ffb35 100644 --- a/sources/Zenith.NET/Structs/SamplerDesc.cs +++ b/sources/Zenith.NET/Structs/SamplerDesc.cs @@ -1,24 +1,123 @@ namespace Zenith.NET; -public record struct SamplerDesc +public struct SamplerDesc { - public AddressMode U; + public FilterMode MinFilter; - public AddressMode V; + public FilterMode MagFilter; - public AddressMode W; + public FilterMode MipFilter; - public Filter Filter; + public AddressMode AddressU; - public ComparisonFunc ComparisonFunc; + public AddressMode AddressV; + + public AddressMode AddressW; + + public CompareOp CompareOp; public uint MaxAnisotropy; + public float LodBias; + public float MinLod; public float MaxLod; - public float LodBias; - public BorderColor BorderColor; + + public static SamplerDesc LinearWrap() + { + return new() + { + MinFilter = FilterMode.Linear, + MagFilter = FilterMode.Linear, + MipFilter = 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, + MipFilter = 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, + MipFilter = 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, + MipFilter = 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, + MipFilter = 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/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 206cfb0c..3348e686 100644 --- a/sources/Zenith.NET/Structs/ShaderDesc.cs +++ b/sources/Zenith.NET/Structs/ShaderDesc.cs @@ -1,10 +1,10 @@ namespace Zenith.NET; -public record struct ShaderDesc +public struct ShaderDesc { - public byte[] ShaderBytes; + public string Name; - public string EntryPoint; + public byte[] CodeBytes; - public ShaderStageFlags Stage; + public ThreadGroupSize ThreadGroupSize; } diff --git a/sources/Zenith.NET/Structs/SizeAndAlignment.cs b/sources/Zenith.NET/Structs/SizeAndAlignment.cs new file mode 100644 index 00000000..d3b6373e --- /dev/null +++ b/sources/Zenith.NET/Structs/SizeAndAlignment.cs @@ -0,0 +1,8 @@ +namespace Zenith.NET; + +public readonly struct SizeAndAlignment(ulong sizeInBytes, ulong alignmentInBytes) +{ + public readonly ulong SizeInBytes = sizeInBytes; + + public readonly ulong AlignmentInBytes = alignmentInBytes; +} diff --git a/sources/Zenith.NET/Structs/StencilFaceState.cs b/sources/Zenith.NET/Structs/StencilFaceState.cs new file mode 100644 index 00000000..d4a5223e --- /dev/null +++ b/sources/Zenith.NET/Structs/StencilFaceState.cs @@ -0,0 +1,23 @@ +namespace Zenith.NET; + +public struct StencilFaceState +{ + public StencilOp FailOp; + + public StencilOp DepthFailOp; + + 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/Surface.cs b/sources/Zenith.NET/Structs/Surface.cs index c1d6fc00..f0623658 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; @@ -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, - Handles = [sharedHandle], - Width = width, - Height = height - }; - } } diff --git a/sources/Zenith.NET/Structs/SwapChainDesc.cs b/sources/Zenith.NET/Structs/SwapChainDesc.cs index f2d5eb46..449b0d70 100644 --- a/sources/Zenith.NET/Structs/SwapChainDesc.cs +++ b/sources/Zenith.NET/Structs/SwapChainDesc.cs @@ -1,10 +1,8 @@ namespace Zenith.NET; -public record struct SwapChainDesc +public struct SwapChainDesc { public Surface Surface; - public PixelFormat ColorTargetFormat; - - public PixelFormat? DepthStencilTargetFormat; + public PixelFormat Format; } diff --git a/sources/Zenith.NET/Structs/TextureData.cs b/sources/Zenith.NET/Structs/TextureData.cs new file mode 100644 index 00000000..fe6e0116 --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureData.cs @@ -0,0 +1,12 @@ +namespace Zenith.NET; + +public struct TextureData +{ + public nint Pointer; + + public uint SizeInBytes; + + public uint RowStrideInBytes; + + public uint SliceStrideInBytes; +} diff --git a/sources/Zenith.NET/Structs/TextureDesc.cs b/sources/Zenith.NET/Structs/TextureDesc.cs index 770c42b3..3b1e01c0 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; @@ -18,5 +18,149 @@ public record struct TextureDesc public SampleCount SampleCount; - public TextureUsageFlags Flags; + 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.TransferDst + }; + } + + 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.TransferDst + }; + } + + 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.TransferDst + }; + } + + 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.TransferDst + }; + } + + 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.TransferDst + }; + } + + 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.TransferDst + }; + } + + 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 = cubeCount * 6, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.TransferDst + }; + } + + public static TextureDesc ColorAttachment(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.ColorAttachment + }; + } + + public static TextureDesc DepthStencilAttachment(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.Sampled | TextureUsages.DepthStencilAttachment + }; + } } 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..82156aed 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 struct TextureSubresource { public uint MipLevel; public uint ArrayLayer; - - public uint Face; } diff --git a/sources/Zenith.NET/Structs/TextureSubresourceRange.cs b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs new file mode 100644 index 00000000..c8e5d108 --- /dev/null +++ b/sources/Zenith.NET/Structs/TextureSubresourceRange.cs @@ -0,0 +1,23 @@ +namespace Zenith.NET; + +public struct TextureSubresourceRange +{ + public uint BaseMipLevel; + + public uint LevelCount; + + 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/Structs/TextureViewDesc.cs b/sources/Zenith.NET/Structs/TextureViewDesc.cs index 187a3a69..3437d199 100644 --- a/sources/Zenith.NET/Structs/TextureViewDesc.cs +++ b/sources/Zenith.NET/Structs/TextureViewDesc.cs @@ -1,14 +1,131 @@ namespace Zenith.NET; -public record struct TextureViewDesc +public struct TextureViewDesc { public Texture Texture; - public uint FirstMipLevel; + public TextureType Type; - public uint MipLevelCount; + public PixelFormat Format; - public uint FirstArrayLayer; + public TextureSubresourceRange Range; - public uint ArrayLayerCount; + 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 + } + }; + } } 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; +} diff --git a/sources/Zenith.NET/Structs/TimelineValue.cs b/sources/Zenith.NET/Structs/TimelineValue.cs new file mode 100644 index 00000000..e3f3c55e --- /dev/null +++ b/sources/Zenith.NET/Structs/TimelineValue.cs @@ -0,0 +1,16 @@ +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.IsCompleted(Value); + + public void Wait() + { + Timeline.Wait(Value); + Timeline.Queue.Poll(); + } +} diff --git a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs index 00e54715..8d85e37e 100644 --- a/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs +++ b/sources/Zenith.NET/Structs/TopLevelAccelerationStructureDesc.cs @@ -1,8 +1,8 @@ namespace Zenith.NET; -public record struct TopLevelAccelerationStructureDesc +public struct TopLevelAccelerationStructureDesc { public RayTracingInstance[] Instances; - public AccelerationStructureBuildFlags Flags; + public AccelerationStructureBuildFlags BuildFlags; } diff --git a/sources/Zenith.NET/Structs/TransferLayout.cs b/sources/Zenith.NET/Structs/TransferLayout.cs new file mode 100644 index 00000000..ddbcc5f9 --- /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) + { + nint pointer = buffer.Map(); + + unsafe + { + for (uint row = 0; row < Rows; row++) + { + new ReadOnlySpan((void*)(Pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); + } + } + + buffer.Unmap(); + } + + public readonly void Download(Buffer buffer) + { + nint pointer = buffer.Map(); + + unsafe + { + for (uint row = 0; row < Rows; row++) + { + new ReadOnlySpan((void*)(pointer + (SrcRowStrideInBytes * row)), (int)RowSizeInBytes).CopyTo(new((void*)(Pointer + (DstRowStrideInBytes * row)), (int)RowSizeInBytes)); + } + } + + buffer.Unmap(); + } +} 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/SwapChain.cs b/sources/Zenith.NET/SwapChain.cs index f505168c..9e7ecb57 100644 --- a/sources/Zenith.NET/SwapChain.cs +++ b/sources/Zenith.NET/SwapChain.cs @@ -6,9 +6,14 @@ public abstract class SwapChain(GraphicsContext context, SwapChainDesc desc) : G public ref readonly SwapChainDesc Desc => ref desc; - public abstract FrameBuffer FrameBuffer { get; } + public abstract Texture Drawable { get; } - public abstract void Present(); + public void Present() + { + PresentImpl(); + + Context.GraphicsQueue.Timeline.Signal().Wait(); + } public void Resize(uint width, uint height) { @@ -29,6 +34,8 @@ 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/Texture.cs b/sources/Zenith.NET/Texture.cs index b0debb5e..ca05e722 100644 --- a/sources/Zenith.NET/Texture.cs +++ b/sources/Zenith.NET/Texture.cs @@ -1,21 +1,32 @@ 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 abstract ResourceHandle SampledHandle { get; } + + public abstract ResourceHandle StorageHandle { get; } + + public void Upload(TextureSubresource subresource, TextureLayout currentLayout, TextureLayout finalLayout, Offset3D offset, Extent3D extent, TextureData data) { - if (data.Length is 0) - { - return; - } + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); - CommandBuffer commandBuffer = Context.Copy.CommandBuffer(); + commandBuffer.Transition(this, subresource, currentLayout, TextureLayout.CopyDst); + commandBuffer.Upload(this, subresource, offset, extent, data); + commandBuffer.Transition(this, subresource, TextureLayout.CopyDst, finalLayout); + commandBuffer.Submit().Wait(); + } + + public void Download(TextureSubresource subresource, TextureLayout currentLayout, TextureLayout finalLayout, Offset3D offset, Extent3D extent, TextureData data) + { + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); - commandBuffer.Upload(this, slice, offset, extent, data); - commandBuffer.Submit(true); + commandBuffer.Transition(this, subresource, currentLayout, TextureLayout.CopySrc); + commandBuffer.Download(this, subresource, offset, extent, data); + commandBuffer.Transition(this, subresource, TextureLayout.CopySrc, finalLayout); + commandBuffer.Submit().Wait(); } } diff --git a/sources/Zenith.NET/TextureView.cs b/sources/Zenith.NET/TextureView.cs index 0bd089ff..2ea722f4 100644 --- a/sources/Zenith.NET/TextureView.cs +++ b/sources/Zenith.NET/TextureView.cs @@ -1,8 +1,12 @@ 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; public ref readonly TextureViewDesc Desc => ref desc; + + public abstract ResourceHandle SampledHandle { get; } + + public abstract ResourceHandle StorageHandle { get; } } diff --git a/sources/Zenith.NET/Timeline.cs b/sources/Zenith.NET/Timeline.cs new file mode 100644 index 00000000..db5db1b8 --- /dev/null +++ b/sources/Zenith.NET/Timeline.cs @@ -0,0 +1,40 @@ +namespace Zenith.NET; + +public abstract class Timeline(GraphicsContext context, CommandQueue queue) : GraphicsResource(context) +{ + private readonly Lock @lock = new(); + + private ulong nextValue; + + public CommandQueue Queue { get; } = queue; + + public TimelineValue Signal() + { + using Lock.Scope _ = @lock.EnterScope(); + + SignalImpl(++nextValue); + + return new(this, nextValue); + } + + internal bool IsCompleted(ulong value) + { + return value <= GetCompletedValue(); + } + + internal void Wait(ulong value) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (!IsCompleted(value)) + { + WaitImpl(value); + } + } + + protected abstract ulong GetCompletedValue(); + + protected abstract void SignalImpl(ulong value); + + protected abstract void WaitImpl(ulong value); +} diff --git a/sources/Zenith.NET/TopLevelAccelerationStructure.cs b/sources/Zenith.NET/TopLevelAccelerationStructure.cs index 8efa2898..dbf24852 100644 --- a/sources/Zenith.NET/TopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET/TopLevelAccelerationStructure.cs @@ -1,11 +1,13 @@ 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; public ref readonly TopLevelAccelerationStructureDesc Desc => ref desc; + public abstract ResourceHandle Handle { get; } + internal void Refresh(TopLevelAccelerationStructureDesc newDesc) { desc = newDesc; diff --git a/sources/Zenith.NET/Uploader.cs b/sources/Zenith.NET/Uploader.cs index 30f3b454..e5e1ecaa 100644 --- a/sources/Zenith.NET/Uploader.cs +++ b/sources/Zenith.NET/Uploader.cs @@ -2,11 +2,13 @@ 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 = []; - public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) + public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes, TransferLayout layout) { using Lock.Scope _ = @lock.EnterScope(); @@ -20,12 +22,12 @@ public Buffer Buffer(CommandBuffer commandBuffer, uint sizeInBytes) lease = new(context.CreateBuffer(new() { SizeInBytes = sizeInBytes, - StrideInBytes = 1, - Flags = BufferUsageFlags.MapWrite + Usages = BufferUsages.TransferSrc, + Residency = MemoryResidency.CpuWriteOnly })); } - leases.Add(lease); + leases.Add(lease.Borrow(layout)); return lease.Buffer; } @@ -66,7 +68,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; @@ -75,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) @@ -89,7 +98,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 9a4e6aca..c2f4dd14 100644 --- a/sources/Zenith.NET/ValidationLayer.cs +++ b/sources/Zenith.NET/ValidationLayer.cs @@ -2,980 +2,8 @@ public abstract class ValidationLayer(GraphicsContext context) : GraphicsResource(context) { - protected void Report(MessageSource source, MessageSeverity severity, string message) + protected void Report(MessageSeverity severity, string message) { - Context.OnValidationMessage(new(source, severity, message)); + Context.OnValidationMessage(new(severity, message)); } - - 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(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) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "ShaderDesc.ShaderBytes")); - } - - if (string.IsNullOrWhiteSpace(desc.EntryPoint)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrWhitespace, "ShaderDesc.EntryPoint")); - } - - if (!Enum.IsDefined(desc.Stage)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "ShaderDesc.Stage", desc.Stage)); - } - } - - internal void ValidateDesc(BufferDesc desc) - { - if (desc.SizeInBytes is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "BufferDesc.SizeInBytes")); - } - - if (desc.StrideInBytes is 0) - { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.IsZeroWarning, "BufferDesc.StrideInBytes", "buffer types")); - } - - if (desc.Flags is BufferUsageFlags.None) - { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.IsSetToNoneWarning, "BufferDesc.Flags")); - } - } - - 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)) - { - 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)); - } - - 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")); - } - - if (desc.ArrayLayers is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "TextureDesc.ArrayLayers")); - } - - if (!Enum.IsDefined(desc.SampleCount)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "TextureDesc.SampleCount", desc.SampleCount)); - } - - if (desc.Flags is TextureUsageFlags.None) - { - ReportFrameworkMessage(MessageSeverity.Warning, 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) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "TextureViewDesc.Texture")); - - return; - } - - if (desc.FirstMipLevel >= desc.Texture.Desc.MipLevels) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.FirstMipLevel", "the number of mip levels in the texture")); - } - - if (desc.MipLevelCount is 0 || desc.FirstMipLevel + desc.MipLevelCount > desc.Texture.Desc.MipLevels) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.MipLevelCount", "the texture mip levels")); - } - - if (desc.FirstArrayLayer >= desc.Texture.Desc.ArrayLayers) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThan, "TextureViewDesc.FirstArrayLayer", "the number of array layers in the texture")); - } - - if (desc.ArrayLayerCount is 0 || desc.FirstArrayLayer + desc.ArrayLayerCount > desc.Texture.Desc.ArrayLayers) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeWithinBounds, "TextureViewDesc.ArrayLayerCount", "the texture array layers")); - } - } - - internal void ValidateDesc(SamplerDesc desc) - { - 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.Filter)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.Filter", desc.Filter)); - } - - if (desc.Filter is Filter.Anisotropic && desc.MaxAnisotropy is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "SamplerDesc.MaxAnisotropy")); - } - - if (desc.MaxAnisotropy > ValidationConstants.MaxAnisotropy) - { - 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")); - } - - if (!Enum.IsDefined(desc.BorderColor)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "SamplerDesc.BorderColor", desc.BorderColor)); - } - } - - 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) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ResourceTableDesc.Layout")); - - return; - } - - if (desc.Resources is null) - { - 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; - } - } - - internal void ValidateDesc(GraphicsPipelineDesc desc) - { - CheckRenderStates("GraphicsPipelineDesc", desc.RenderStates); - - 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")); - } - - 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")); - } - - if (desc.ResourceLayout?.IsDisposed is true) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "GraphicsPipelineDesc.ResourceLayout")); - } - - if (desc.InputLayouts is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "GraphicsPipelineDesc.InputLayouts")); - } - else - { - for (int i = 0; i < desc.InputLayouts.Length; i++) - { - CheckInputLayout($"GraphicsPipelineDesc.InputLayouts[{i}]", desc.InputLayouts[i]); - } - } - - if (!Enum.IsDefined(desc.PrimitiveTopology)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "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; - } - - foreach (InputElement element in inputLayout.Elements) - { - if (!Enum.IsDefined(element.Format)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Elements.ElementFormat", element.Format)); - } - - if (!Enum.IsDefined(element.Semantic)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.Elements.ElementSemantic", element.Semantic)); - } - } - } - } - - internal void ValidateDesc(ComputePipelineDesc desc) - { - if (desc.Compute is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "ComputePipelineDesc.Compute")); - } - else if (desc.Compute.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ComputePipelineDesc.Compute")); - } - - if (desc.ResourceLayout?.IsDisposed is true) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "ComputePipelineDesc.ResourceLayout")); - } - - if (desc.ThreadGroupSizeX is 0 || desc.ThreadGroupSizeY is 0 || desc.ThreadGroupSizeZ is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "ComputePipelineDesc thread group sizes (ThreadGroupSizeX, ThreadGroupSizeY, ThreadGroupSizeZ)")); - } - } - - internal void ValidateDesc(MeshShadingPipelineDesc desc) - { - CheckRenderStates("MeshShadingPipelineDesc", desc.RenderStates); - - if (desc.Amplification?.IsDisposed is true) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Amplification")); - } - - if (desc.Mesh is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.Mesh")); - } - else if (desc.Mesh.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Mesh")); - } - - if (desc.Pixel is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, "MeshShadingPipelineDesc.Pixel")); - } - else if (desc.Pixel.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.Pixel")); - } - - if (desc.ResourceLayout?.IsDisposed is true) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, "MeshShadingPipelineDesc.ResourceLayout")); - } - - if (desc.PrimitiveTopology is not PrimitiveTopology.LineList and not PrimitiveTopology.TriangleList) - { - ReportFrameworkMessage(MessageSeverity.Error, 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)) - { - ReportFrameworkMessage(MessageSeverity.Error, 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) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "MeshShadingPipelineDesc mesh thread group sizes (MeshThreadGroupSizeX, MeshThreadGroupSizeY, MeshThreadGroupSizeZ)")); - } - } - - internal void ValidateDesc(QueryHeapDesc desc) - { - if (!Enum.IsDefined(desc.Type)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, "QueryHeapDesc.Type", desc.Type)); - } - - if (desc.Count is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeGreaterThanZero, "QueryHeapDesc.Count")); - } - } - - internal void ValidateDesc(BottomLevelAccelerationStructureDesc desc) - { - if (desc.Geometries is null || desc.Geometries.Length is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "BottomLevelAccelerationStructureDesc.Geometries")); - - return; - } - - for (int i = 0; i < desc.Geometries.Length; i++) - { - CheckRayTracingGeometry($"BottomLevelAccelerationStructureDesc.Geometries[{i}]", desc.Geometries[i]); - } - - void CheckRayTracingGeometry(string name, RayTracingGeometry rayTracingGeometry) - { - 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; - } - } - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc desc) - { - if (desc.Instances is null || desc.Instances.Length is 0) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNullOrEmpty, "TopLevelAccelerationStructureDesc.Instances")); - - return; - } - - for (int i = 0; i < desc.Instances.Length; i++) - { - CheckRayTracingInstance($"TopLevelAccelerationStructureDesc.Instances[{i}]", desc.Instances[i]); - } - - void CheckRayTracingInstance(string name, RayTracingInstance rayTracingInstance) - { - if (rayTracingInstance.AccelerationStructure is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.AccelerationStructure")); - - return; - } - - if (rayTracingInstance.AccelerationStructure.IsDisposed) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeDisposed, $"{name}.AccelerationStructure")); - - return; - } - - if (rayTracingInstance.ID > ValidationConstants.MaxRayTracingInstanceID) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustBeLessThanOrEqualTo, $"{name}.ID", ValidationConstants.MaxRayTracingInstanceID)); - } - } - } - - internal void ValidateDesc(TopLevelAccelerationStructureDesc oldDesc, TopLevelAccelerationStructureDesc newDesc) - { - ValidateDesc(newDesc); - - if (newDesc.Instances is null) - { - return; - } - - if (oldDesc.Instances.Length != newDesc.Instances.Length) - { - ReportFrameworkMessage(MessageSeverity.Error, ValidationMessages.InstanceCountMustRemainSame); - } - } - - 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)); - } - - if (!Enum.IsDefined(renderStates.RasterizerState.FillMode)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.RasterizerState.FillMode", renderStates.RasterizerState.FillMode)); - } - - if (!Enum.IsDefined(renderStates.RasterizerState.FrontFace)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.RasterizerState.FrontFace", renderStates.RasterizerState.FrontFace)); - } - - if (!Enum.IsDefined(renderStates.DepthStencilState.DepthFunc)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.RenderStates.DepthStencilState.DepthFunc", renderStates.DepthStencilState.DepthFunc)); - } - - CheckDepthStencilStateOp($"{name}.RenderStates.DepthStencilState.FrontFace", renderStates.DepthStencilState.FrontFace); - CheckDepthStencilStateOp($"{name}.RenderStates.DepthStencilState.BackFace", renderStates.DepthStencilState.BackFace); - - 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); - - void CheckDepthStencilStateOp(string name, DepthStencilStateOp depthStencilStateOp) - { - if (!Enum.IsDefined(depthStencilStateOp.StencilFailOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilFailOp", depthStencilStateOp.StencilFailOp)); - } - - if (!Enum.IsDefined(depthStencilStateOp.StencilDepthFailOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilDepthFailOp", depthStencilStateOp.StencilDepthFailOp)); - } - - if (!Enum.IsDefined(depthStencilStateOp.StencilPassOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilPassOp", depthStencilStateOp.StencilPassOp)); - } - - if (!Enum.IsDefined(depthStencilStateOp.StencilFunc)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.StencilFunc", depthStencilStateOp.StencilFunc)); - } - } - - void CheckBlendStateRenderTarget(string name, BlendStateRenderTarget blendStateRenderTarget) - { - if (!Enum.IsDefined(blendStateRenderTarget.SrcBlend)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.SrcBlend", blendStateRenderTarget.SrcBlend)); - } - - if (!Enum.IsDefined(blendStateRenderTarget.DestBlend)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.DestBlend", blendStateRenderTarget.DestBlend)); - } - - if (!Enum.IsDefined(blendStateRenderTarget.BlendOp)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.BlendOp", blendStateRenderTarget.BlendOp)); - } - - if (!Enum.IsDefined(blendStateRenderTarget.SrcBlendAlpha)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.SrcBlendAlpha", blendStateRenderTarget.SrcBlendAlpha)); - } - - if (!Enum.IsDefined(blendStateRenderTarget.DestBlendAlpha)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.DestBlendAlpha", blendStateRenderTarget.DestBlendAlpha)); - } - - if (!Enum.IsDefined(blendStateRenderTarget.BlendOpAlpha)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.BlendOpAlpha", blendStateRenderTarget.BlendOpAlpha)); - } - } - } - - private void CheckOutput(string name, Output output) - { - if (output.ColorAttachments is null) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.MustNotBeNull, $"{name}.ColorAttachments")); - - return; - } - - for (int i = 0; i < output.ColorAttachments.Length; i++) - { - if (!Enum.IsDefined(output.ColorAttachments[i])) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.ColorAttachments[{i}]", output.ColorAttachments[i])); - } - } - - if (output.DepthStencilAttachment is not null && !Enum.IsDefined(output.DepthStencilAttachment.Value)) - { - ReportFrameworkMessage(MessageSeverity.Error, string.Format(ValidationMessages.HasInvalidValue, $"{name}.DepthStencilAttachment", output.DepthStencilAttachment.Value)); - } - - if (output.ColorAttachments.Length is 0 && output.DepthStencilAttachment is null) - { - ReportFrameworkMessage(MessageSeverity.Warning, string.Format(ValidationMessages.HasNoAttachments, name)); - } - } - - private void ReportFrameworkMessage(MessageSeverity severity, string message) - { - Report(MessageSource.Framework, severity, message); - } -} - -file static class ValidationConstants -{ - public const int CubeMapFaceCount = 6; - - public const int MaxAnisotropy = 16; - - 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 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 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."; -} \ No newline at end of file diff --git a/sources/Zenith.NET/ValidationMessageArgs.cs b/sources/Zenith.NET/ValidationMessageEventArgs.cs similarity index 55% rename from sources/Zenith.NET/ValidationMessageArgs.cs rename to sources/Zenith.NET/ValidationMessageEventArgs.cs index c66e872b..53b0f1f5 100644 --- a/sources/Zenith.NET/ValidationMessageArgs.cs +++ b/sources/Zenith.NET/ValidationMessageEventArgs.cs @@ -1,12 +1,10 @@ namespace Zenith.NET; -public class ValidationMessageArgs(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; public DateTimeOffset Timestamp { get; } = DateTimeOffset.UtcNow; -} \ No newline at end of file +} diff --git a/sources/Zenith.NET/Zenith.NET.csproj b/sources/Zenith.NET/Zenith.NET.csproj index bace72ae..df2bd0f1 100644 --- a/sources/Zenith.NET/Zenith.NET.csproj +++ b/sources/Zenith.NET/Zenith.NET.csproj @@ -5,6 +5,10 @@ IDE0130 + + + + diff --git a/sources/Zenith.NET/ZenithCompiler.cs b/sources/Zenith.NET/ZenithCompiler.cs new file mode 100644 index 00000000..c55250b3 --- /dev/null +++ b/sources/Zenith.NET/ZenithCompiler.cs @@ -0,0 +1,77 @@ +using Slangc.NET; + +namespace Zenith.NET; + +public static class ZenithCompiler +{ + public static ShaderDesc CompileFromFile(GraphicsApi graphicsApi, string file, string name, string[]? searchPaths = null) + { + return new() + { + Name = name, + CodeBytes = SlangCompiler.CompileWithReflection([file, .. Arguments(graphicsApi, name, searchPaths)], out SlangReflection reflection), + ThreadGroupSize = ThreadGroupSize(reflection, name) + }; + } + + public static ShaderDesc CompileFromSource(GraphicsApi graphicsApi, string source, string name, string[]? searchPaths = null) + { + return new() + { + Name = name, + CodeBytes = SlangCompiler.CompileWithReflection(source, Arguments(graphicsApi, name, searchPaths), out SlangReflection reflection), + ThreadGroupSize = ThreadGroupSize(reflection, name) + }; + } + + private static string[] Arguments(GraphicsApi graphicsApi, string name, string[]? searchPaths) + { + List arguments = + [ + "-entry", name, + "-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", "-capability", "spvRayQueryKHR", "-fvk-use-entrypoint-name"]); + break; + } + + 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(); + } +} diff --git a/sources/Zenith.NET/ZenithHelper.cs b/sources/Zenith.NET/ZenithHelper.cs index 892b940c..54b87e47 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,40 +186,54 @@ 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 RowStrideInBytes(PixelFormat pixelFormat, uint width, uint height) { - return format switch + (_, _, uint blocksWide, _) = BlockLayout(pixelFormat, width, height); + + return SizeInBytes(pixelFormat) * blocksWide; + } + + public static uint SliceStrideInBytes(PixelFormat pixelFormat, uint width, uint height) + { + (_, _, _, uint blocksHigh) = BlockLayout(pixelFormat, width, height); + + return RowStrideInBytes(pixelFormat, width, height) * blocksHigh; + } + + public static uint SizeInBytes(ElementFormat elementFormat) + { + return elementFormat switch { 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 @@ -227,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 @@ -245,57 +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; - } - - public static ShaderStageFlags[] GraphicShaderStages() - { - return - [ - ShaderStageFlags.Vertex, - ShaderStageFlags.Pixel, - ShaderStageFlags.Amplification, - ShaderStageFlags.Mesh - ]; - } } diff --git a/sources/Zenith.NET/ZenithMarshal.cs b/sources/Zenith.NET/ZenithMarshal.cs index f8ef7dd6..41536736 100644 --- a/sources/Zenith.NET/ZenithMarshal.cs +++ b/sources/Zenith.NET/ZenithMarshal.cs @@ -9,15 +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 { - if (data.Length is 0) - { - return nint.Zero; - } + nint pointer = (nint)NativeMemory.AllocZeroed((nuint)(sizeof(T) * length)); - nint pointer = (nint)NativeMemory.Alloc((uint)(sizeof(T) * data.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); @@ -37,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 @@ -47,14 +50,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.Uni => Encoding.Unicode.GetBytes(value + '\0'), 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) @@ -73,8 +76,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 }; } @@ -83,13 +86,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 +}