forked from Misaki/GhostEngine
Refactor graphics architecture and resource management
Added DescriptorAllocator.cs to manage descriptor allocations for Direct3D 12. Added Texture2D.cs to handle 2D textures and GPU resource creation. Added DescriptorAllocatorExample.cs to demonstrate the new descriptor allocator interface. Changed project files to reference Misaki.HighPerformance.LowLevel instead of Misaki.HighPerformance.Unsafe. Changed _renderView type from IRenderer? to Renderer? in ScenePage.xaml.cs. Changed EngineCore.cs to remove explicit graphics API specification during initialization. Changed Logger.cs to enhance the Assert method with a DoesNotReturnIf attribute. Changed resource types in Mesh.cs from IResource to GraphicsResource. Removed multiple interfaces including ICommandBuffer, IDebugLayer, IGraphicsDevice, IPipelineResource, IRenderPass, IRenderer, IResource, and IResourceAllocator to simplify the graphics architecture. Removed D3D12DebugLayer class from DebugLayer.cs to streamline the debug layer implementation. Updated CommandList.cs and D3D12CommandBuffer.cs to implement a new command list structure for Direct3D 12. Updated Material.cs to improve handling of constant buffers and textures. Updated Shader.cs to include new structures for texture and property information. Updated GraphicsPipeline.cs to support the new graphics device and resource management system. Updated UnitTestAppWindow.xaml.cs to reflect changes in the renderer type and ensure proper resource management. Updated BindlessMeshRenderPass.cs and MeshRenderPass.cs to implement modern rendering techniques, including bindless textures and improved shader management. Updated CBufferCache.cs to align with the new resource management system and improve memory handling.
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public enum GraphicsAPI
|
||||
{
|
||||
None,
|
||||
D3D12
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.Shading;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public class Material : IDisposable
|
||||
public unsafe class Material : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, CBufferCache> _cbufferCaches;
|
||||
private readonly CBufferCache[] _cbufferCaches;
|
||||
private readonly Texture2D?[] _textures;
|
||||
private readonly Dictionary<string, int> _textureNameToSlotMap;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
@@ -21,10 +23,37 @@ public class Material : IDisposable
|
||||
{
|
||||
Shader = shader;
|
||||
|
||||
_cbufferCaches = new();
|
||||
foreach (var cbufferInfo in shader.ConstantBuffers.Values)
|
||||
if (shader.ConstantBuffers.Count > 0)
|
||||
{
|
||||
_cbufferCaches.Add(cbufferInfo.Name, new CBufferCache(cbufferInfo.Size));
|
||||
var maxSlot = shader.ConstantBuffers.Max(cb => cb.RegisterSlot);
|
||||
_cbufferCaches = new CBufferCache[maxSlot + 1];
|
||||
|
||||
foreach (var cbufferInfo in shader.ConstantBuffers)
|
||||
{
|
||||
_cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(cbufferInfo.Size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_cbufferCaches = Array.Empty<CBufferCache>();
|
||||
}
|
||||
|
||||
// Initialize texture storage
|
||||
if (shader.Textures.Count > 0)
|
||||
{
|
||||
var maxTextureSlot = shader.Textures.Max(t => t.RegisterSlot);
|
||||
_textures = new Texture2D?[maxTextureSlot + 1];
|
||||
_textureNameToSlotMap = new Dictionary<string, int>();
|
||||
|
||||
foreach (var textureInfo in shader.Textures)
|
||||
{
|
||||
_textureNameToSlotMap.Add(textureInfo.Name, (int)textureInfo.RegisterSlot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_textures = Array.Empty<Texture2D?>();
|
||||
_textureNameToSlotMap = new Dictionary<string, int>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,52 +62,191 @@ public class Material : IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a float property in the material's constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">The ID of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetFloat(int propertyId, in float value)
|
||||
{
|
||||
WriteToCache(propertyId, in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a float property in the material's constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetFloat(string propertyName, in float value)
|
||||
{
|
||||
WriteToCache(propertyName, in value);
|
||||
SetFloat(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Vector property in the material's constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">The ID of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetVector(int propertyId, in Vector4 value)
|
||||
{
|
||||
WriteToCache(propertyId, in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Vector property in the material's constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetVector(string propertyName, in Vector4 value)
|
||||
{
|
||||
WriteToCache(propertyName, in value);
|
||||
SetVector(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Matrix property in the material's constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The ID of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetMatrix(int propertyId, in Matrix4x4 value)
|
||||
{
|
||||
WriteToCache(propertyId, in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Matrix property in the material's constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property to set.</param>
|
||||
/// <param name="value">The value to set for the property.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetMatrix(string propertyName, in Matrix4x4 value)
|
||||
{
|
||||
WriteToCache(propertyName, in value);
|
||||
SetMatrix(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
private unsafe void WriteToCache<T>(string propertyName, in T value) where T : unmanaged
|
||||
/// <summary>
|
||||
/// Sets a texture property in the material.
|
||||
/// </summary>
|
||||
/// <param name="textureId">The ID of the texture to set.</param>
|
||||
/// <param name="texture">The texture to set.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetTexture(int textureId, Texture2D? texture)
|
||||
{
|
||||
if (!Shader.Properties.TryGetValue(propertyName, out var propInfo))
|
||||
if (textureId == -1)
|
||||
{
|
||||
throw new ArgumentException($"Property '{propertyName}' does not exist in the shader.", nameof(propertyName));
|
||||
throw new ArgumentException("Texture ID is invalid.");
|
||||
}
|
||||
|
||||
if (textureId >= _textures.Length)
|
||||
{
|
||||
throw new ArgumentException($"Texture ID {textureId} is out of range.");
|
||||
}
|
||||
|
||||
_textures[textureId] = texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a texture property in the material.
|
||||
/// </summary>
|
||||
/// <param name="textureName">The name of the texture to set.</param>
|
||||
/// <param name="texture">The texture to set.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetTexture(string textureName, Texture2D? texture)
|
||||
{
|
||||
if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot))
|
||||
{
|
||||
throw new ArgumentException($"Texture '{textureName}' not found in shader.");
|
||||
}
|
||||
|
||||
_textures[slot] = texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture property from the material.
|
||||
/// </summary>
|
||||
/// <param name="textureId">The ID of the texture to get.</param>
|
||||
/// <returns>The texture, or null if not set.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Texture2D? GetTexture(int textureId)
|
||||
{
|
||||
if (textureId == -1 || textureId >= _textures.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _textures[textureId];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture property from the material.
|
||||
/// </summary>
|
||||
/// <param name="textureName">The name of the texture to get.</param>
|
||||
/// <returns>The texture, or null if not set.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Texture2D? GetTexture(string textureName)
|
||||
{
|
||||
if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _textures[slot];
|
||||
}
|
||||
|
||||
private unsafe void WriteToCache<T>(int propertyId, in T value) where T : unmanaged
|
||||
{
|
||||
if (propertyId == -1)
|
||||
{
|
||||
throw new ArgumentException("Property ID is invalid.");
|
||||
}
|
||||
|
||||
var propInfo = Shader.Properties[propertyId];
|
||||
if (propInfo.Size != sizeof(T))
|
||||
{
|
||||
throw new ArgumentException($"Property '{propertyName}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.", nameof(propertyName));
|
||||
throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.");
|
||||
}
|
||||
|
||||
var cache = _cbufferCaches[propInfo.CBufferName];
|
||||
var cache = _cbufferCaches[propInfo.CBufferIndex];
|
||||
|
||||
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
|
||||
}
|
||||
|
||||
public void UploadAndBind(ICommandBuffer cmb)
|
||||
/// <summary>
|
||||
/// Uploads the material data to the GPU.
|
||||
/// </summary>
|
||||
public void UploadMaterialData()
|
||||
{
|
||||
foreach (var cache in _cbufferCaches.Values)
|
||||
foreach (var cache in _cbufferCaches)
|
||||
{
|
||||
cache.UploadToGpu();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (name, cache) in _cbufferCaches)
|
||||
internal void Bind(CommandList cmd)
|
||||
{
|
||||
// Bind constant buffers
|
||||
foreach (var cbufferInfo in Shader.ConstantBuffers)
|
||||
{
|
||||
var cbufferInfo = Shader.ConstantBuffers[name];
|
||||
cmb.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress);
|
||||
var cache = _cbufferCaches[cbufferInfo.RegisterSlot];
|
||||
cmd.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress);
|
||||
}
|
||||
|
||||
// Bind textures using descriptor table
|
||||
if (Shader.Textures.Count > 0)
|
||||
{
|
||||
// Get the first texture info to determine the root parameter index
|
||||
var textureInfo = Shader.Textures[0];
|
||||
var texture = _textures[0]; // Get the first texture
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
// Set descriptor table for SRVs
|
||||
cmd.NativeCommandList.Ptr->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, texture.SRVDescriptor.GpuHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,13 +257,11 @@ public class Material : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var cache in _cbufferCaches.Values)
|
||||
foreach (var cache in _cbufferCaches)
|
||||
{
|
||||
cache.Dispose();
|
||||
}
|
||||
|
||||
_cbufferCaches.Clear();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_disposed = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Numerics;
|
||||
@@ -16,8 +16,8 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
|
||||
private Bounds _boundingBox;
|
||||
|
||||
private IResource? _vertexBuffer;
|
||||
private IResource? _indexBuffer;
|
||||
private GraphicsResource? _vertexBuffer;
|
||||
private GraphicsResource? _indexBuffer;
|
||||
private VertexBufferView _vertexBufferView;
|
||||
private IndexBufferView _indexBufferView;
|
||||
|
||||
@@ -204,8 +204,7 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
/// <summary>
|
||||
/// Uploads the mesh data to GPU resources.
|
||||
/// </summary>
|
||||
/// <param name="cmb">The command buffer to record the upload commands.</param>
|
||||
public unsafe void UploadMeshData(ICommandBuffer cmb)
|
||||
public unsafe void UploadMeshData()
|
||||
{
|
||||
if (VertexCount == 0 || IndexCount == 0)
|
||||
{
|
||||
@@ -220,17 +219,13 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde
|
||||
_vertexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(vertexBufferSize);
|
||||
_indexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(indexBufferSize);
|
||||
|
||||
var vertexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(vertexBufferSize, false);
|
||||
var indexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(indexBufferSize, false);
|
||||
|
||||
vertexUploadBuffer.SetData(_vertices.AsSpan());
|
||||
indexUploadBuffer.SetData(_indices.AsSpan());
|
||||
|
||||
cmb.CopyResource(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize);
|
||||
cmb.CopyResource(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize);
|
||||
|
||||
cmb.BarrierTransition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
|
||||
cmb.BarrierTransition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
|
||||
using var uploadBatch = new ResourceUploadBatch();
|
||||
uploadBatch.Begin();
|
||||
uploadBatch.Upload(_vertexBuffer, _vertices.AsSpan());
|
||||
uploadBatch.Upload(_indexBuffer, _indices.AsSpan());
|
||||
uploadBatch.Transition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
|
||||
uploadBatch.Transition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
|
||||
uploadBatch.WaitForCompletion(uploadBatch.End());
|
||||
|
||||
_vertexBufferView = new VertexBufferView
|
||||
{
|
||||
|
||||
357
Ghost.Graphics/Data/Texture2D.cs
Normal file
357
Ghost.Graphics/Data/Texture2D.cs
Normal file
@@ -0,0 +1,357 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Runtime.InteropServices;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public unsafe class Texture2D : IDisposable
|
||||
{
|
||||
private UnsafeArray<byte> _cpuData;
|
||||
|
||||
private readonly GraphicsResource _resource;
|
||||
private readonly ShaderResourceDescriptor _srvDescriptor;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public Format Format
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint BytesPerPixel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint Pitch
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public uint DataSize
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal ShaderResourceDescriptor SRVDescriptor => _srvDescriptor;
|
||||
|
||||
public GraphicsResource? Resource => _resource;
|
||||
public ReadOnlySpan<byte> CpuData => _cpuData.AsSpan();
|
||||
|
||||
public Texture2D(uint width, uint height, Span<byte> data, Format format)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Format = format;
|
||||
BytesPerPixel = GetBytesPerPixel(format);
|
||||
Pitch = width * BytesPerPixel;
|
||||
DataSize = Pitch * height;
|
||||
|
||||
// Initialize CPU-side data
|
||||
_cpuData = new((int)DataSize, Allocator.Persistent);
|
||||
|
||||
if (!data.IsEmpty)
|
||||
{
|
||||
if (data.Length != DataSize)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
data.CopyTo(_cpuData.AsSpan());
|
||||
}
|
||||
|
||||
_resource = CreateGpuResource();
|
||||
_srvDescriptor = CreateShaderResourceView();
|
||||
}
|
||||
private static uint GetBytesPerPixel(Format format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.R8G8B8A8Unorm => 4,
|
||||
Format.R8G8B8A8UnormSrgb => 4,
|
||||
Format.B8G8R8A8Unorm => 4,
|
||||
Format.B8G8R8A8UnormSrgb => 4,
|
||||
Format.R8G8B8A8Uint => 4,
|
||||
Format.R8G8B8A8Sint => 4,
|
||||
Format.R8G8B8A8Snorm => 4,
|
||||
Format.R8G8Unorm => 2,
|
||||
Format.R8G8Uint => 2,
|
||||
Format.R8G8Sint => 2,
|
||||
Format.R8G8Snorm => 2,
|
||||
Format.R8Unorm => 1,
|
||||
Format.R8Uint => 1,
|
||||
Format.R8Sint => 1,
|
||||
Format.R8Snorm => 1,
|
||||
Format.A8Unorm => 1,
|
||||
Format.R16G16B16A16Float => 8,
|
||||
Format.R16G16B16A16Unorm => 8,
|
||||
Format.R16G16B16A16Uint => 8,
|
||||
Format.R16G16B16A16Sint => 8,
|
||||
Format.R16G16B16A16Snorm => 8,
|
||||
Format.R32G32B32A32Float => 16,
|
||||
Format.R32G32B32A32Uint => 16,
|
||||
Format.R32G32B32A32Sint => 16,
|
||||
Format.R32G32B32Float => 12,
|
||||
Format.R32G32B32Uint => 12,
|
||||
Format.R32G32B32Sint => 12,
|
||||
Format.R32G32Float => 8,
|
||||
Format.R32G32Uint => 8,
|
||||
Format.R32G32Sint => 8,
|
||||
Format.R32Float => 4,
|
||||
Format.R32Uint => 4,
|
||||
Format.R32Sint => 4,
|
||||
Format.R16G16Float => 4,
|
||||
Format.R16G16Unorm => 4,
|
||||
Format.R16G16Uint => 4,
|
||||
Format.R16G16Sint => 4,
|
||||
Format.R16G16Snorm => 4,
|
||||
Format.R16Float => 2,
|
||||
Format.R16Unorm => 2,
|
||||
Format.R16Uint => 2,
|
||||
Format.R16Sint => 2,
|
||||
Format.R16Snorm => 2,
|
||||
_ => throw new NotSupportedException($"Format {format} is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
private GraphicsResource CreateGpuResource()
|
||||
{
|
||||
var heapProperties = new HeapProperties(HeapType.Default);
|
||||
var resourceDesc = ResourceDescription.Tex2D(Format, Width, Height);
|
||||
|
||||
ComPtr<ID3D12Resource> textureResource = default;
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
|
||||
&heapProperties,
|
||||
HeapFlags.None,
|
||||
&resourceDesc,
|
||||
ResourceStates.Common,
|
||||
null,
|
||||
__uuidof<ID3D12Resource>(),
|
||||
textureResource.GetVoidAddressOf()
|
||||
);
|
||||
|
||||
return new(textureResource.Move());
|
||||
}
|
||||
|
||||
private ShaderResourceDescriptor CreateShaderResourceView()
|
||||
{
|
||||
var srvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateSRV();
|
||||
|
||||
// Create the actual SRV
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = Format,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateShaderResourceView(_resource.NativeResource.Ptr, &srvDesc, srvDescriptor.CpuHandle);
|
||||
|
||||
return srvDescriptor;
|
||||
}
|
||||
|
||||
private void CopyBufferToTexture(CommandList cmd, GraphicsResource srcBuffer, GraphicsResource dstTexture)
|
||||
{
|
||||
// Calculate the proper footprint for the placed subresource
|
||||
var resourceDesc = dstTexture.NativeResource.Ptr->GetDesc();
|
||||
|
||||
PlacedSubresourceFootprint footprint = new()
|
||||
{
|
||||
Footprint = new SubresourceFootprint
|
||||
{
|
||||
Format = Format,
|
||||
Width = Width,
|
||||
Height = Height,
|
||||
Depth = 1,
|
||||
RowPitch = (uint)((Pitch + 255) & ~255) // Align to 256 bytes
|
||||
}
|
||||
};
|
||||
|
||||
var srcLocation = new TextureCopyLocation(srcBuffer.NativeResource.Ptr, footprint);
|
||||
var dstLocation = new TextureCopyLocation(dstTexture.NativeResource.Ptr, 0);
|
||||
|
||||
cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the entire texture data on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="data">The texture data to set</param>
|
||||
public void SetData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (data.Length != DataSize)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
data.CopyTo(_cpuData.AsSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture data for a specific region on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="data">The texture data to set</param>
|
||||
/// <param name="x">Starting X coordinate</param>
|
||||
/// <param name="y">Starting Y coordinate</param>
|
||||
/// <param name="width">Width of the region</param>
|
||||
/// <param name="height">Height of the region</param>
|
||||
public void SetData(ReadOnlySpan<byte> data, uint x, uint y, uint width, uint height)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (x + width > Width || y + height > Height)
|
||||
{
|
||||
throw new ArgumentException("Region extends beyond texture bounds.");
|
||||
}
|
||||
|
||||
var expectedSize = width * height * BytesPerPixel;
|
||||
if (data.Length != expectedSize)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {expectedSize} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
for (uint row = 0; row < height; row++)
|
||||
{
|
||||
var srcOffset = (int)(row * width * BytesPerPixel);
|
||||
var dstOffset = (int)((y + row) * Pitch + x * BytesPerPixel);
|
||||
var rowSize = (int)(width * BytesPerPixel);
|
||||
|
||||
data.Slice(srcOffset, rowSize).CopyTo(_cpuData.AsSpan().Slice(dstOffset, rowSize));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a single pixel value on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <param name="color">The color data for the pixel</param>
|
||||
public void SetPixel(uint x, uint y, ReadOnlySpan<byte> color)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (x >= Width || y >= Height)
|
||||
{
|
||||
throw new ArgumentException("Pixel coordinates are outside texture bounds.");
|
||||
}
|
||||
|
||||
if (color.Length != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color data size mismatch. Expected {BytesPerPixel} bytes, got {color.Length} bytes.");
|
||||
}
|
||||
|
||||
var offset = (int)(y * Pitch + x * BytesPerPixel);
|
||||
color.CopyTo(_cpuData.AsSpan().Slice(offset, (int)BytesPerPixel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a single pixel value using generic color types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The color type (e.g., uint for RGBA32)</typeparam>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <param name="color">The color value</param>
|
||||
public void SetPixel<T>(uint x, uint y, T color) where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var colorSpan = new ReadOnlySpan<byte>(&color, sizeof(T));
|
||||
SetPixel(x, y, colorSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single pixel value from the CPU side data.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <returns>The pixel color data</returns>
|
||||
public ReadOnlySpan<byte> GetPixel(uint x, uint y)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (x >= Width || y >= Height)
|
||||
{
|
||||
throw new ArgumentException("Pixel coordinates are outside texture bounds.");
|
||||
}
|
||||
|
||||
var offset = (int)(y * Pitch + x * BytesPerPixel);
|
||||
return _cpuData.AsSpan().Slice(offset, (int)BytesPerPixel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single pixel value as a generic color type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The color type (e.g., uint for RGBA32)</typeparam>
|
||||
/// <param name="x">X coordinate of the pixel</param>
|
||||
/// <param name="y">Y coordinate of the pixel</param>
|
||||
/// <returns>The pixel color value</returns>
|
||||
public T GetPixel<T>(uint x, uint y) where T : unmanaged
|
||||
{
|
||||
if (sizeof(T) != BytesPerPixel)
|
||||
{
|
||||
throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var pixelData = GetPixel(x, y);
|
||||
return MemoryMarshal.Read<T>(pixelData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the CPU-side texture data to the GPU resource.
|
||||
/// </summary>
|
||||
public void UploadTextureData()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
Format.GetSurfaceInfo((int)Width, (int)Height, out var rowPitch, out var slicePitch);
|
||||
var initData = new SubresourceData()
|
||||
{
|
||||
pData = _cpuData.GetUnsafePtr(),
|
||||
RowPitch = rowPitch,
|
||||
SlicePitch = slicePitch
|
||||
};
|
||||
|
||||
using var uploadBatch = new ResourceUploadBatch();
|
||||
uploadBatch.Begin();
|
||||
uploadBatch.Transition(_resource, ResourceStates.Common, ResourceStates.CopyDest);
|
||||
uploadBatch.Upload(_resource, 0, &initData, 1);
|
||||
uploadBatch.Transition(_resource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource);
|
||||
uploadBatch.WaitForCompletion(uploadBatch.End());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cpuData.Dispose();
|
||||
_resource.Dispose();
|
||||
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseSRV(_srvDescriptor);
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public struct Vertex(Vector4 position, Vector4 normal, Vector4 tangent, Color128
|
||||
private static readonly byte[] s_normalBytes = Encoding.UTF8.GetBytes("NORMAL");
|
||||
private static readonly byte[] s_tangentBytes = Encoding.UTF8.GetBytes("TANGENT");
|
||||
private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR");
|
||||
private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("UV");
|
||||
private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD");
|
||||
|
||||
public static byte* PositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]);
|
||||
public static byte* NormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]);
|
||||
|
||||
Reference in New Issue
Block a user