forked from Misaki/GhostEngine
Refactoring Rendering backend
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
internal struct CBufferCache
|
||||
internal struct CBufferCache : IDisposable
|
||||
{
|
||||
public UnsafeArray<byte> CpuData
|
||||
{
|
||||
@@ -11,18 +12,23 @@ internal struct CBufferCache
|
||||
set;
|
||||
}
|
||||
|
||||
public BufferHandle GpuResource
|
||||
public Handle<GraphicsBuffer> GpuResource
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
private readonly uint _alignedSize;
|
||||
|
||||
internal unsafe CBufferCache(BufferHandle buffer, uint bufferSize)
|
||||
internal unsafe CBufferCache(Handle<GraphicsBuffer> buffer, uint bufferSize)
|
||||
{
|
||||
_alignedSize = (bufferSize + 255u) & ~255u;
|
||||
|
||||
CpuData = new((int)_alignedSize, Allocator.Persistent);
|
||||
GpuResource = buffer;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
CpuData.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Render target view (RTV) descriptor.
|
||||
/// </summary>
|
||||
public readonly struct RenderTargetDescriptor
|
||||
{
|
||||
public uint Index
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public static RenderTargetDescriptor Invalid => new() { Index = ~0u };
|
||||
|
||||
public bool IsValid => Index != ~0u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth stencil view (DSV) descriptor.
|
||||
/// </summary>
|
||||
public readonly struct DepthStencilDescriptor
|
||||
{
|
||||
public uint Index
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public static DepthStencilDescriptor Invalid => new() { Index = ~0u };
|
||||
|
||||
public bool IsValid => Index != ~0u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shader resource view (SRV) descriptor.
|
||||
/// </summary>
|
||||
public readonly struct ShaderResourceDescriptor
|
||||
{
|
||||
public uint Index
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public static ShaderResourceDescriptor Invalid => new() { Index = ~0u };
|
||||
|
||||
public bool IsValid => Index != ~0u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sampler descriptor.
|
||||
/// </summary>
|
||||
public readonly struct SamplerDescriptor
|
||||
{
|
||||
public uint Index
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public static SamplerDescriptor Invalid => new() { Index = ~0u };
|
||||
|
||||
public bool IsValid => Index != ~0u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bindless descriptor
|
||||
/// </summary>
|
||||
public readonly struct BindlessDescriptor
|
||||
{
|
||||
public uint Index
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public static BindlessDescriptor Invalid => new() { Index = ~0u };
|
||||
|
||||
public bool IsValid => Index != ~0u;
|
||||
}
|
||||
188
Ghost.Graphics/Data/Material.cs
Normal file
188
Ghost.Graphics/Data/Material.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public struct Material : IHandleType
|
||||
{
|
||||
internal UnsafeArray<CBufferCache> _cBufferCaches;
|
||||
|
||||
public Identifier<Shader> Shader
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
foreach (var cache in _cBufferCaches)
|
||||
{
|
||||
cache.Dispose();
|
||||
}
|
||||
|
||||
_cBufferCaches.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct MaterialAccessor
|
||||
{
|
||||
private ref Material _materialData;
|
||||
private ref Shader _shader;
|
||||
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
internal MaterialAccessor(ref Material materialData, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
_materialData = ref materialData;
|
||||
_shader = ref resourceDatabase.GetShaderReference(materialData.Shader);
|
||||
}
|
||||
|
||||
private readonly 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 '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var cache = _materialData._cBufferCaches[propInfo.CBufferIndex];
|
||||
|
||||
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
|
||||
}
|
||||
|
||||
/// <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 readonly 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 readonly void SetFloat(string propertyName, in float value)
|
||||
{
|
||||
SetFloat(_shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a uint property in the material's constant buffer (useful for texture indices).
|
||||
/// </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 readonly void SetUInt(int propertyId, in uint value)
|
||||
{
|
||||
WriteToCache(propertyId, in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a uint property in the material's constant buffer (useful for texture indices).
|
||||
/// </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 readonly void SetUInt(string propertyName, in uint value)
|
||||
{
|
||||
SetUInt(_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 readonly 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 readonly void SetVector(string propertyName, in Vector4 value)
|
||||
{
|
||||
SetVector(_shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Matrix 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 readonly 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 readonly void SetMatrix(string propertyName, in Matrix4x4 value)
|
||||
{
|
||||
SetMatrix(_shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a texture index for a shader property (for bindless texture access)
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
|
||||
/// <param name="texture">The bindless texture to reference</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void SetTextureIndex(string propertyName, Handle<Texture> texture)
|
||||
{
|
||||
SetUInt(propertyName, texture.BindlessDescriptor.Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh buffer indices for bindless vertex and index buffer access
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh whose buffer indices to set</param>
|
||||
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property</param>
|
||||
/// <param name="indexBufferIndexProperty">The name of the index buffer index property</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void SetMeshBufferIndex(ref readonly Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
|
||||
{
|
||||
SetUInt(vertexBufferIndexProperty, mesh.vertexBuffer.BindlessDescriptor.Index);
|
||||
SetUInt(indexBufferIndexProperty, mesh.indexBuffer.BindlessDescriptor.Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads all cached material data to the GPU using the specified command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cmb">The command buffer used to perform the upload operations to the GPU. Cannot be null.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void UploadMaterialData(ICommandBuffer cmb)
|
||||
{
|
||||
foreach (var cache in _materialData._cBufferCaches)
|
||||
{
|
||||
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public struct Material
|
||||
{
|
||||
internal UnsafeArray<CBufferCache> _cBufferCaches;
|
||||
|
||||
public Identifier<Shader> Shader
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct MaterialAccessor
|
||||
{
|
||||
private ref Material _materialData;
|
||||
private ref Shader _shader;
|
||||
|
||||
public MaterialAccessor(ref Material materialData, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
_materialData = ref materialData;
|
||||
_shader = ref resourceDatabase.GetShaderReference(materialData.Shader);
|
||||
}
|
||||
|
||||
private readonly 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 '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var cache = _materialData._cBufferCaches[propInfo.CBufferIndex];
|
||||
|
||||
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
|
||||
}
|
||||
|
||||
/// <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 readonly 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)
|
||||
{
|
||||
SetFloat(_shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a uint property in the material's constant buffer (useful for texture indices).
|
||||
/// </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 readonly void SetUInt(int propertyId, in uint value)
|
||||
{
|
||||
WriteToCache(propertyId, in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a uint property in the material's constant buffer (useful for texture indices).
|
||||
/// </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 SetUInt(string propertyName, in uint value)
|
||||
{
|
||||
SetUInt(_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 readonly 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)
|
||||
{
|
||||
SetVector(_shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Matrix 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 readonly 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)
|
||||
{
|
||||
SetMatrix(_shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a texture index for a shader property (for bindless texture access)
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
|
||||
/// <param name="texture">The bindless texture to reference</param>
|
||||
public void SetTexture(string propertyName, Texture2D texture)
|
||||
{
|
||||
SetUInt(propertyName, texture.DescriptorIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh buffer indices for bindless vertex and index buffer access
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh whose buffer indices to set</param>
|
||||
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
|
||||
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
|
||||
public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
|
||||
{
|
||||
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
|
||||
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads all cached material data to the GPU using the specified command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cmb">The command buffer used to perform the upload operations to the GPU. Cannot be null.</param>
|
||||
public readonly void UploadMaterialData(ICommandBuffer cmb)
|
||||
{
|
||||
foreach (var cache in _materialData._cBufferCaches)
|
||||
{
|
||||
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Material implementation for bindless rendering with SM 6.6 support
|
||||
/// </summary>
|
||||
public unsafe class MaterialClass : IDisposable
|
||||
{
|
||||
private readonly UnsafeArray<CBufferCache> _cbufferCaches;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal ReadOnlySpan<CBufferCache> CBufferCaches => _cbufferCaches.AsSpan();
|
||||
|
||||
public Identifier<Shader> Shader
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public MaterialClass(Identifier<Shader> shader, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
Shader = shader;
|
||||
|
||||
var shaderResource = resourceDatabase.GetShader(shader);
|
||||
|
||||
if (shaderResource.ConstantBuffers.Count > 0)
|
||||
{
|
||||
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
|
||||
_cbufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Allocator.Persistent);
|
||||
|
||||
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = cbufferInfo.Size,
|
||||
Usage = BufferUsage.Constant,
|
||||
MemoryType = MemoryType.Default,
|
||||
};
|
||||
|
||||
var buffer = resourceAllocator.CreateBuffer(in desc);
|
||||
|
||||
_cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
SetFloat(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a uint property in the material's constant buffer (useful for texture indices).
|
||||
/// </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 SetUInt(int propertyId, in uint value)
|
||||
{
|
||||
WriteToCache(propertyId, in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a uint property in the material's constant buffer (useful for texture indices).
|
||||
/// </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 SetUInt(string propertyName, in uint value)
|
||||
{
|
||||
SetUInt(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)
|
||||
{
|
||||
SetVector(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Matrix 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 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)
|
||||
{
|
||||
SetMatrix(Shader.GetPropertyId(propertyName), in value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a texture index for a shader property (for bindless texture access)
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
|
||||
/// <param name="texture">The bindless texture to reference</param>
|
||||
public void SetTexture(string propertyName, Texture2D texture)
|
||||
{
|
||||
SetUInt(propertyName, texture.DescriptorIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the mesh buffer indices for bindless vertex and index buffer access
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh whose buffer indices to set</param>
|
||||
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property (e.g., "_VertexBufferIndex")</param>
|
||||
/// <param name="indexBufferIndexProperty">The name of the index buffer index property (e.g., "_IndexBufferIndex")</param>
|
||||
public void SetMeshBuffer(MeshClass mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex")
|
||||
{
|
||||
SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex);
|
||||
SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex);
|
||||
}
|
||||
|
||||
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 '{propInfo.Name}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
|
||||
}
|
||||
|
||||
var cache = _cbufferCaches[propInfo.CBufferIndex];
|
||||
|
||||
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the material data to the GPU.
|
||||
/// </summary>
|
||||
public void UploadMaterialData()
|
||||
{
|
||||
foreach (var cache in _cbufferCaches)
|
||||
{
|
||||
cache.UploadToGpu();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var cache in _cbufferCaches)
|
||||
{
|
||||
cache.Dispose();
|
||||
}
|
||||
|
||||
// NOTE: We don't dispose the textures here as they might be shared
|
||||
// The user is responsible for disposing BindlessTexture2D instances
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
163
Ghost.Graphics/Data/Mesh.cs
Normal file
163
Ghost.Graphics/Data/Mesh.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public struct Mesh : IHandleType
|
||||
{
|
||||
public UnsafeList<Vertex> vertices;
|
||||
public UnsafeList<uint> indices;
|
||||
public AABB boundingBox;
|
||||
public Handle<GraphicsBuffer> vertexBuffer;
|
||||
public Handle<GraphicsBuffer> indexBuffer;
|
||||
|
||||
public Mesh()
|
||||
{
|
||||
vertexBuffer = Handle<GraphicsBuffer>.Invalid;
|
||||
indexBuffer = Handle<GraphicsBuffer>.Invalid;
|
||||
}
|
||||
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GraphicsBuffer> vertexBuffer, Handle<GraphicsBuffer> indexBuffer)
|
||||
{
|
||||
this.vertices = new(vertices.Length, Allocator.Persistent);
|
||||
this.indices = new(indices.Length, Allocator.Persistent);
|
||||
this.vertices.CopyFrom(vertices);
|
||||
this.indices.CopyFrom(indices);
|
||||
this.vertexBuffer = vertexBuffer;
|
||||
this.indexBuffer = indexBuffer;
|
||||
|
||||
this.ComputeBounds();
|
||||
}
|
||||
|
||||
public void ReleaseCpuResources()
|
||||
{
|
||||
vertices.Dispose();
|
||||
indices.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MeshExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the bounding box of the mesh based on its vertices.
|
||||
/// </summary>
|
||||
public static void ComputeBounds(ref this Mesh mesh)
|
||||
{
|
||||
if (mesh.vertices.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var min = new float3(float.MaxValue);
|
||||
var max = new float3(float.MinValue);
|
||||
foreach (var vertex in mesh.vertices)
|
||||
{
|
||||
var pos = vertex.position.xyz;
|
||||
min = math.min(min, pos);
|
||||
max = math.max(max, pos);
|
||||
}
|
||||
|
||||
mesh.boundingBox = new AABB(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute smooth per-vertex normals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices and indices are valid.
|
||||
/// </remarks>
|
||||
public static void ComputeNormal(ref this Mesh mesh)
|
||||
{
|
||||
if (!mesh.vertices.IsCreated || mesh.vertices.Count < 3
|
||||
|| !mesh.indices.IsCreated || mesh.indices.Count < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < mesh.indices.Count; i += 3)
|
||||
{
|
||||
var i0 = mesh.indices[i];
|
||||
var i1 = mesh.indices[i + 1];
|
||||
var i2 = mesh.indices[i + 2];
|
||||
|
||||
var v0 = mesh.vertices[i0];
|
||||
var v1 = mesh.vertices[i1];
|
||||
var v2 = mesh.vertices[i2];
|
||||
|
||||
var edge1 = v1.position - v0.position;
|
||||
var edge2 = v2.position - v0.position;
|
||||
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
|
||||
|
||||
mesh.vertices[i0].normal.xyz += faceNormal;
|
||||
mesh.vertices[i1].normal.xyz += faceNormal;
|
||||
mesh.vertices[i2].normal.xyz += faceNormal;
|
||||
}
|
||||
|
||||
for (var i = 0; i < mesh.vertices.Count; i++)
|
||||
{
|
||||
mesh.vertices[i].normal = math.normalize(mesh.vertices[i].normal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute per-vertex tangents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices, normals, and UVs are valid.
|
||||
/// </remarks>
|
||||
public static void ComputeTangents(ref this Mesh mesh)
|
||||
{
|
||||
var bitangents = new float4[mesh.vertices.Count];
|
||||
|
||||
for (var i = 0; i < mesh.indices.Count; i += 3)
|
||||
{
|
||||
var i0 = mesh.indices[i];
|
||||
var i1 = mesh.indices[i + 1];
|
||||
var i2 = mesh.indices[i + 2];
|
||||
|
||||
var v0 = mesh.vertices[i0];
|
||||
var v1 = mesh.vertices[i1];
|
||||
var v2 = mesh.vertices[i2];
|
||||
|
||||
var uv0 = mesh.vertices[i0].uv;
|
||||
var uv1 = mesh.vertices[i1].uv;
|
||||
var uv2 = mesh.vertices[i2].uv;
|
||||
|
||||
var deltaPos1 = v1.position - v0.position;
|
||||
var deltaPos2 = v2.position - v0.position;
|
||||
var deltaUV1 = uv1 - uv0;
|
||||
var deltaUV2 = uv2 - uv0;
|
||||
|
||||
var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
|
||||
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
|
||||
var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var idx = mesh.indices[i + j];
|
||||
var t = mesh.vertices[idx].tangent;
|
||||
mesh.vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
|
||||
|
||||
bitangents[idx] += bitangent;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < mesh.vertices.Count; i++)
|
||||
{
|
||||
var n = mesh.vertices[i].normal;
|
||||
var t = mesh.vertices[i].tangent;
|
||||
|
||||
var proj = n * math.dot(n, t);
|
||||
t = math.normalize(t - proj);
|
||||
|
||||
var b = bitangents[i];
|
||||
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
||||
|
||||
mesh.vertices[i].tangent = new float4(t.x, t.y, t.z, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public struct Mesh
|
||||
{
|
||||
public UnsafeList<Vertex> vertices;
|
||||
public UnsafeList<uint> indices;
|
||||
public AABB boundingBox;
|
||||
public BufferHandle vertexBuffer;
|
||||
public BufferHandle indexBuffer;
|
||||
|
||||
public Mesh()
|
||||
{
|
||||
vertexBuffer = BufferHandle.Invalid;
|
||||
indexBuffer = BufferHandle.Invalid;
|
||||
}
|
||||
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, BufferHandle vertexBuffer, BufferHandle indexBuffer)
|
||||
{
|
||||
this.vertices = new(vertices.Length, Allocator.Persistent);
|
||||
this.indices = new(indices.Length, Allocator.Persistent);
|
||||
this.vertices.CopyFrom(vertices);
|
||||
this.indices.CopyFrom(indices);
|
||||
this.vertexBuffer = vertexBuffer;
|
||||
this.indexBuffer = indexBuffer;
|
||||
|
||||
ComputeBounds();
|
||||
}
|
||||
|
||||
public void ComputeBounds()
|
||||
{
|
||||
if (vertices.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var min = new float3(float.MaxValue);
|
||||
var max = new float3(float.MinValue);
|
||||
foreach (var vertex in vertices)
|
||||
{
|
||||
var pos = vertex.position.xyz;
|
||||
min = math.min(min, pos);
|
||||
max = math.max(max, pos);
|
||||
}
|
||||
|
||||
boundingBox = new AABB(min, max);
|
||||
}
|
||||
|
||||
public void ReleaseCpuResources()
|
||||
{
|
||||
vertices.Dispose();
|
||||
indices.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe sealed class MeshClass : IDisposable
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
private UnsafeList<int> _indices;
|
||||
|
||||
private AABB _boundingBox;
|
||||
|
||||
private IBuffer? _vertexBuffer;
|
||||
private IBuffer? _indexBuffer;
|
||||
|
||||
public Span<Vertex> Vertices => _vertices.AsSpan();
|
||||
public Span<int> Indices => _indices.AsSpan();
|
||||
public AABB BoundingBox => _boundingBox;
|
||||
|
||||
public uint VertexCount => (uint)_vertices.Count;
|
||||
public uint IndexCount => (uint)_indices.Count;
|
||||
|
||||
public uint VertexBufferDescriptorIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vertexBuffer == null || !_vertexBuffer.Handle.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Vertex buffer is not created.");
|
||||
}
|
||||
|
||||
var bindlessDesc = _vertexBuffer.Handle.BindlessDescriptor;
|
||||
if (!bindlessDesc.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Vertex buffer is not created with bindless.");
|
||||
}
|
||||
|
||||
return bindlessDesc.Index;
|
||||
}
|
||||
}
|
||||
|
||||
public uint IndexBufferDescriptorIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_indexBuffer == null || !_indexBuffer.Handle.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Index buffer is not created.");
|
||||
}
|
||||
|
||||
var bindlessDesc = _indexBuffer.Handle.BindlessDescriptor;
|
||||
if (!bindlessDesc.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Index buffer is not created with bindless.");
|
||||
}
|
||||
|
||||
return bindlessDesc.Index;
|
||||
}
|
||||
}
|
||||
|
||||
public MeshClass(int initialVertexCapacity = 256, int initialIndexCapacity = 512)
|
||||
{
|
||||
_vertices = new(initialVertexCapacity, Allocator.Persistent);
|
||||
_indices = new(initialIndexCapacity, Allocator.Persistent);
|
||||
}
|
||||
|
||||
public MeshClass(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<int> indices)
|
||||
: this(vertices.Length, indices.Length)
|
||||
{
|
||||
_vertices = new(vertices.Length, Allocator.Persistent);
|
||||
_indices = new(indices.Length, Allocator.Persistent);
|
||||
|
||||
_vertices.CopyFrom(vertices);
|
||||
_indices.CopyFrom(indices);
|
||||
}
|
||||
|
||||
~MeshClass()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a vertex to the mesh with the specified attributes.
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex data to add</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddVertex(Vertex vertex)
|
||||
{
|
||||
_vertices.Add(vertex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a triangle to the mesh by specifying the indices of its three vertices.
|
||||
/// </summary>
|
||||
/// <param name="index0">The index of the first vertex in the triangle. Must be within the range of the current vertex count.</param>
|
||||
/// <param name="index1">The index of the second vertex in the triangle. Must be within the range of the current vertex count.</param>
|
||||
/// <param name="index2">The index of the third vertex in the triangle. Must be within the range of the current vertex count.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown if any of the specified indices are out of range for the current vertex count.</exception>
|
||||
public void AddTriangle(int index0, int index1, int index2)
|
||||
{
|
||||
if (index0 >= _vertices.Count || index1 >= _vertices.Count || index2 >= _vertices.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Index out of range for the current vertex count.");
|
||||
}
|
||||
|
||||
_indices.Add(index0);
|
||||
_indices.Add(index1);
|
||||
_indices.Add(index2);
|
||||
}
|
||||
|
||||
public void AddTriangles(params ReadOnlySpan<int> indices)
|
||||
{
|
||||
if (indices.Length % 3 != 0)
|
||||
{
|
||||
throw new ArgumentException("The number of indices must be a multiple of 3 to form triangles.");
|
||||
}
|
||||
|
||||
foreach (var index in indices)
|
||||
{
|
||||
if (index < 0 || index >= _vertices.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(indices), "Index out of range for the current vertex count.");
|
||||
}
|
||||
|
||||
_indices.Add(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduces the memory usage of the internal collections by resizing them to match their current element count.
|
||||
/// </summary>
|
||||
public void TrimExcess()
|
||||
{
|
||||
_vertices.Resize(_vertices.Count);
|
||||
_indices.Resize(_indices.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute smooth per-vertex normals.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices and indices are valid.
|
||||
/// </remarks>
|
||||
public void ComputeNormal()
|
||||
{
|
||||
if (!_vertices.IsCreated || _vertices.Count < 3
|
||||
|| !_indices.IsCreated || _indices.Count < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _indices.Count; i += 3)
|
||||
{
|
||||
var i0 = _indices[i];
|
||||
var i1 = _indices[i + 1];
|
||||
var i2 = _indices[i + 2];
|
||||
|
||||
var v0 = _vertices[i0];
|
||||
var v1 = _vertices[i1];
|
||||
var v2 = _vertices[i2];
|
||||
|
||||
var edge1 = v1.position - v0.position;
|
||||
var edge2 = v2.position - v0.position;
|
||||
var faceNormal = math.cross(edge1.xyz, edge2.xyz);
|
||||
|
||||
_vertices[i0].normal.xyz += faceNormal;
|
||||
_vertices[i1].normal.xyz += faceNormal;
|
||||
_vertices[i2].normal.xyz += faceNormal;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _vertices.Count; i++)
|
||||
{
|
||||
_vertices[i].normal = math.normalize(_vertices[i].normal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-compute per-vertex tangents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call this method before vertices, normals, and UVs are valid.
|
||||
/// </remarks>
|
||||
public void ComputeTangents()
|
||||
{
|
||||
var bitangents = new float4[_vertices.Count];
|
||||
|
||||
for (var i = 0; i < _indices.Count; i += 3)
|
||||
{
|
||||
var i0 = _indices[i];
|
||||
var i1 = _indices[i + 1];
|
||||
var i2 = _indices[i + 2];
|
||||
|
||||
var v0 = _vertices[i0];
|
||||
var v1 = _vertices[i1];
|
||||
var v2 = _vertices[i2];
|
||||
|
||||
var uv0 = _vertices[i0].uv;
|
||||
var uv1 = _vertices[i1].uv;
|
||||
var uv2 = _vertices[i2].uv;
|
||||
|
||||
var deltaPos1 = v1.position - v0.position;
|
||||
var deltaPos2 = v2.position - v0.position;
|
||||
var deltaUV1 = uv1 - uv0;
|
||||
var deltaUV2 = uv2 - uv0;
|
||||
|
||||
var r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
|
||||
var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
|
||||
var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var idx = _indices[i + j];
|
||||
var t = _vertices[idx].tangent;
|
||||
_vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
|
||||
|
||||
bitangents[idx] += bitangent;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _vertices.Count; i++)
|
||||
{
|
||||
var n = _vertices[i].normal;
|
||||
var t = _vertices[i].tangent;
|
||||
|
||||
var proj = n * math.dot(n, t);
|
||||
t = math.normalize(t - proj);
|
||||
|
||||
var b = bitangents[i];
|
||||
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
||||
|
||||
_vertices[i].tangent = new float4(t.x, t.y, t.z, w);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the bounding box of the mesh based on its vertices.
|
||||
/// </summary>
|
||||
public void ComputeBounds()
|
||||
{
|
||||
if (_vertices.Count == 0)
|
||||
{
|
||||
_boundingBox = AABB.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
var min = new float3(float.MaxValue);
|
||||
var max = new float3(float.MinValue);
|
||||
foreach (var vertex in _vertices)
|
||||
{
|
||||
var pos = vertex.position.xyz;
|
||||
min = math.min(min, pos);
|
||||
max = math.max(max, pos);
|
||||
}
|
||||
|
||||
_boundingBox = new AABB(min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the mesh data to GPU resources.
|
||||
/// </summary>
|
||||
public unsafe void UploadMeshData()
|
||||
{
|
||||
if (VertexCount == 0 || IndexCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DisposeGpuResources();
|
||||
|
||||
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
|
||||
var indexBufferSize = IndexCount * sizeof(int);
|
||||
_vertexBuffer = GraphicsBuffer.Create(vertexBufferSize, GraphicsBuffer.Usage.CopyDestination);
|
||||
_indexBuffer = GraphicsBuffer.Create(indexBufferSize, GraphicsBuffer.Usage.CopyDestination);
|
||||
|
||||
var uploadBatch = GraphicsPipeline.UploadBatch;
|
||||
uploadBatch.Upload(_vertexBuffer.NativeResource, _vertices.AsSpan());
|
||||
uploadBatch.Upload(_indexBuffer.NativeResource, _indices.AsSpan());
|
||||
uploadBatch.Transition(_vertexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer);
|
||||
uploadBatch.Transition(_indexBuffer.NativeResource, ResourceStates.CopyDest, ResourceStates.IndexBuffer);
|
||||
|
||||
// Create bindless descriptors for vertex and index buffers
|
||||
CreateBindlessDescriptors();
|
||||
}
|
||||
|
||||
internal void MarkNoLongerReadable()
|
||||
{
|
||||
_vertices.Dispose();
|
||||
_indices.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all vertex and index data and releases associated GPU resources.
|
||||
/// </summar>
|
||||
public void Clear()
|
||||
{
|
||||
_vertices.Clear();
|
||||
_indices.Clear();
|
||||
DisposeGpuResources();
|
||||
}
|
||||
|
||||
private void DisposeGpuResources()
|
||||
{
|
||||
_vertexBuffer?.Dispose();
|
||||
_vertexBuffer = null;
|
||||
|
||||
_indexBuffer?.Dispose();
|
||||
_indexBuffer = null;
|
||||
|
||||
if (_vertexBufferDescriptor.IsValid)
|
||||
{
|
||||
RenderSystem.GraphicsEngine.DescriptorAllocator.Release(_vertexBufferDescriptor);
|
||||
}
|
||||
|
||||
if (_indexBufferDescriptor.IsValid)
|
||||
{
|
||||
RenderSystem.GraphicsEngine.DescriptorAllocator.Release(_indexBufferDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_vertices.Dispose();
|
||||
_indices.Dispose();
|
||||
DisposeGpuResources();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the type of render texture.
|
||||
/// </summary>
|
||||
public enum RenderTextureType
|
||||
{
|
||||
/// <summary>
|
||||
/// Render target view - used for color output.
|
||||
/// </summary>
|
||||
ColorTarget,
|
||||
|
||||
/// <summary>
|
||||
/// Depth stencil view - used for depth/stencil testing.
|
||||
/// </summary>
|
||||
DepthStencil
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render texture class that encapsulates GPU resources for rendering.
|
||||
/// </summary>
|
||||
public unsafe class RenderTexture : Texture
|
||||
{
|
||||
private readonly RenderTextureType _renderTextureType;
|
||||
private readonly RenderTargetDescriptor? _rtvDescriptor;
|
||||
private readonly DepthStencilDescriptor? _dsvDescriptor;
|
||||
|
||||
private RenderTexture(uint width, uint height, Format format, RenderTextureType renderTextureType, in TextureHandle handle, BindlessDescriptor bindlessDescriptor, RenderTargetDescriptor? rtvDescriptor, DepthStencilDescriptor? dsvDescriptor)
|
||||
: base(width, height, format, in handle, bindlessDescriptor)
|
||||
{
|
||||
_renderTextureType = renderTextureType;
|
||||
_rtvDescriptor = rtvDescriptor;
|
||||
_dsvDescriptor = dsvDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this render texture.
|
||||
/// </summary>
|
||||
public RenderTextureType RenderTextureType => _renderTextureType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the render target view descriptor. Only valid for color render textures.
|
||||
/// </summary>
|
||||
internal RenderTargetDescriptor? RenderTargetView => _rtvDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the depth stencil view descriptor. Only valid for depth render textures.
|
||||
/// </summary>
|
||||
internal DepthStencilDescriptor? DepthStencilView => _dsvDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color render texture.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the render texture</param>
|
||||
/// <param name="height">Height of the render texture</param>
|
||||
/// <param name="format">Color format (e.g., Format.R8G8B8A8Unorm)</param>
|
||||
/// <returns>A new color render texture</returns>
|
||||
public static RenderTexture CreateColorTarget(uint width, uint height, Format format = Format.R8G8B8A8Unorm, bool tempResource = false)
|
||||
{
|
||||
ValidateColorFormat(format);
|
||||
|
||||
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 1, format, resFlags: ResourceFlags.AllowRenderTarget | ResourceFlags.AllowUnorderedAccess, tempResource: tempResource);
|
||||
|
||||
var resource = handle.ResourceHandle.GetAllocation().Resource;
|
||||
var bindlessDescriptor = CreateBindlessDescriptorForRenderTexture(resource, format);
|
||||
var rtvDescriptor = CreateRenderTargetView(resource, format);
|
||||
|
||||
return new RenderTexture(width, height, format, RenderTextureType.ColorTarget, in handle, bindlessDescriptor, rtvDescriptor, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a depth stencil render texture.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the render texture</param>
|
||||
/// <param name="height">Height of the render texture</param>
|
||||
/// <param name="format">Depth format (e.g., Format.D24UnormS8Uint, Format.D32Float)</param>
|
||||
/// <returns>A new depth stencil render texture</returns>
|
||||
public static RenderTexture CreateDepthStencil(uint width, uint height, Format format = Format.D24UnormS8Uint, bool tempResource = false)
|
||||
{
|
||||
ValidateDepthFormat(format);
|
||||
|
||||
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 1, format, resFlags: ResourceFlags.AllowDepthStencil, tempResource: tempResource);
|
||||
|
||||
var resource = handle.ResourceHandle.GetAllocation().Resource;
|
||||
var bindlessDescriptor = CreateBindlessDescriptorForRenderTexture(resource, GetShaderResourceFormat(format));
|
||||
var dsvDescriptor = CreateDepthStencilView(resource, format);
|
||||
|
||||
return new RenderTexture(width, height, format, RenderTextureType.DepthStencil, in handle, bindlessDescriptor, null, dsvDescriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the format is suitable for color render targets.
|
||||
/// </summary>
|
||||
private static void ValidateColorFormat(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.R8G8B8A8Unorm:
|
||||
case Format.R8G8B8A8UnormSrgb:
|
||||
case Format.B8G8R8A8Unorm:
|
||||
case Format.B8G8R8A8UnormSrgb:
|
||||
case Format.R16G16B16A16Float:
|
||||
case Format.R32G32B32A32Float:
|
||||
case Format.R16G16Float:
|
||||
case Format.R32Float:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Format {format} is not supported for color render targets.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the format is suitable for depth stencil targets.
|
||||
/// </summary>
|
||||
private static void ValidateDepthFormat(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.D32Float:
|
||||
case Format.D24UnormS8Uint:
|
||||
case Format.D16Unorm:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Format {format} is not supported for depth stencil targets.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader resource format for depth textures (for sampling depth in shaders).
|
||||
/// </summary>
|
||||
private static Format GetShaderResourceFormat(Format depthFormat)
|
||||
{
|
||||
return depthFormat switch
|
||||
{
|
||||
Format.D32Float => Format.R32Float,
|
||||
Format.D24UnormS8Uint => Format.R24UnormX8Typeless,
|
||||
Format.D16Unorm => Format.R16Unorm,
|
||||
_ => throw new ArgumentException($"Cannot determine shader resource format for depth format {depthFormat}")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bindless descriptor for render texture shader resource access.
|
||||
/// </summary>
|
||||
private static BindlessDescriptor CreateBindlessDescriptorForRenderTexture(ID3D12Resource* resource, Format srvFormat)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = srvFormat,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
device->CreateShaderResourceView(resource, &srvDesc, bindlessDescriptor.CpuHandle);
|
||||
return bindlessDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render target view for color render textures.
|
||||
/// </summary>
|
||||
private static RenderTargetDescriptor CreateRenderTargetView(ID3D12Resource* resource, Format format)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
|
||||
|
||||
var rtvDesc = new RenderTargetViewDescription
|
||||
{
|
||||
Format = format,
|
||||
ViewDimension = RtvDimension.Texture2D,
|
||||
Texture2D = new Texture2DRtv { MipSlice = 0 }
|
||||
};
|
||||
|
||||
device->CreateRenderTargetView(resource, &rtvDesc, rtvDescriptor.CpuHandle);
|
||||
return rtvDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a depth stencil view for depth render textures.
|
||||
/// </summary>
|
||||
private static DepthStencilDescriptor CreateDepthStencilView(ID3D12Resource* resource, Format format)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var dsvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateDSV();
|
||||
|
||||
var dsvDesc = new DepthStencilViewDescription
|
||||
{
|
||||
Format = format,
|
||||
ViewDimension = DsvDimension.Texture2D,
|
||||
Texture2D = new Texture2DDsv { MipSlice = 0 }
|
||||
};
|
||||
|
||||
device->CreateDepthStencilView(resource, &dsvDesc, dsvDescriptor.CpuHandle);
|
||||
return dsvDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the render texture with the specified color (for color targets only).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record clear commands</param>
|
||||
/// <param name="clearColor">Color to clear to</param>
|
||||
public void ClearColor(CommandList commandList, Color128 clearColor)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_renderTextureType != RenderTextureType.ColorTarget || _rtvDescriptor == null)
|
||||
{
|
||||
throw new InvalidOperationException("ClearColor can only be called on color render textures.");
|
||||
}
|
||||
|
||||
commandList.NativeCommandList.Ptr->ClearRenderTargetView(_rtvDescriptor.CpuHandle, (float*)&clearColor, 0, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the depth stencil render texture.
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record clear commands</param>
|
||||
/// <param name="clearDepth">Depth value to clear to (0.0 to 1.0)</param>
|
||||
/// <param name="clearStencil">Stencil value to clear to</param>
|
||||
public void ClearDepthStencil(CommandList commandList, ClearFlags flags, float clearDepth = 1.0f, byte clearStencil = 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_renderTextureType != RenderTextureType.DepthStencil || _dsvDescriptor == null)
|
||||
{
|
||||
throw new InvalidOperationException("ClearDepthStencil can only be called on depth stencil render textures.");
|
||||
}
|
||||
|
||||
commandList.NativeCommandList.Ptr->ClearDepthStencilView(_dsvDescriptor.CpuHandle, flags, clearDepth, clearStencil, 0, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transitions the render texture to the specified resource state.
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
/// <param name="newState">New resource state</param>
|
||||
internal void TransitionTo(CommandList commandList, ResourceStates newState)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
commandList.BarrierTransition(this, ResourceStates.Common, newState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to transition to render target state (for color targets).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
internal void TransitionToRenderTarget(CommandList commandList)
|
||||
{
|
||||
if (_renderTextureType != RenderTextureType.ColorTarget)
|
||||
{
|
||||
throw new InvalidOperationException("TransitionToRenderTarget can only be called on color render textures.");
|
||||
}
|
||||
|
||||
TransitionTo(commandList, ResourceStates.RenderTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to transition to depth write state (for depth targets).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
internal void TransitionToDepthWrite(CommandList commandList)
|
||||
{
|
||||
if (_renderTextureType != RenderTextureType.DepthStencil)
|
||||
{
|
||||
throw new InvalidOperationException("TransitionToDepthWrite can only be called on depth stencil render textures.");
|
||||
}
|
||||
|
||||
TransitionTo(commandList, ResourceStates.DepthWrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to transition to shader resource state (for reading in shaders).
|
||||
/// </summary>
|
||||
/// <param name="commandList">Command list to record transition</param>
|
||||
public void TransitionToShaderResource(CommandList commandList)
|
||||
{
|
||||
TransitionTo(commandList, ResourceStates.PixelShaderResource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the render texture and releases associated descriptors.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
|
||||
if (_rtvDescriptor != null)
|
||||
{
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseRTV(_rtvDescriptor);
|
||||
}
|
||||
|
||||
if (_dsvDescriptor != null)
|
||||
{
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseDSV(_dsvDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Ghost.Graphics/Data/RenderingContext.cs
Normal file
146
Ghost.Graphics/Data/RenderingContext.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using Win32;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public unsafe readonly ref struct RenderingContext
|
||||
{
|
||||
private readonly IRenderDevice _device;
|
||||
private readonly ICommandBuffer _cmd;
|
||||
private readonly ICommandBuffer _copyCmd;
|
||||
private readonly ICommandBuffer _computeCmd;
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
private readonly IPipelineLibrary _stateController;
|
||||
|
||||
internal RenderingContext(
|
||||
IRenderDevice device,
|
||||
ICommandBuffer cmd,
|
||||
ICommandBuffer copyCmd,
|
||||
ICommandBuffer computeCmd,
|
||||
IResourceAllocator resourceAllocator,
|
||||
IResourceDatabase resourceDatabase,
|
||||
IPipelineLibrary stateController)
|
||||
{
|
||||
_device = device;
|
||||
_cmd = cmd;
|
||||
_copyCmd = copyCmd;
|
||||
_computeCmd = computeCmd;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_stateController = stateController;
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
var mesh = _resourceAllocator.CreateMesh(vertices, indices);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
|
||||
{
|
||||
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
|
||||
vertexList.CopyFrom(vertices);
|
||||
indexList.CopyFrom(indices);
|
||||
|
||||
return CreateMesh(vertexList, indexList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads the mesh data to the GPU.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The handle point to the mesh buffer</param>
|
||||
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
|
||||
public void UploadMesh(Handle<Mesh> mesh, bool markMeshStatic)
|
||||
{
|
||||
ref var meshData = ref _resourceDatabase.GetMeshReference(mesh);
|
||||
var vertexState = _resourceDatabase.GetResourceState(meshData.vertexBuffer.AsResource());
|
||||
var indexState = _resourceDatabase.GetResourceState(meshData.indexBuffer.AsResource());
|
||||
var needVertexTransition = vertexState != ResourceState.CopyDest;
|
||||
var needIndexTransition = indexState != ResourceState.CopyDest;
|
||||
|
||||
if (needVertexTransition)
|
||||
{
|
||||
_copyCmd.ResourceBarrier(meshData.vertexBuffer.AsResource(), vertexState, ResourceState.CopyDest);
|
||||
_resourceDatabase.SetResourceState(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest);
|
||||
}
|
||||
|
||||
if (needIndexTransition)
|
||||
{
|
||||
_copyCmd.ResourceBarrier(meshData.indexBuffer.AsResource(), indexState, ResourceState.CopyDest);
|
||||
_resourceDatabase.SetResourceState(meshData.indexBuffer.AsResource(), ResourceState.CopyDest);
|
||||
}
|
||||
|
||||
_copyCmd.Upload(meshData.vertexBuffer, meshData.vertices.AsSpan());
|
||||
_copyCmd.Upload(meshData.indexBuffer, meshData.indices.AsSpan());
|
||||
|
||||
if (needVertexTransition)
|
||||
{
|
||||
_copyCmd.ResourceBarrier(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest, vertexState);
|
||||
_resourceDatabase.SetResourceState(meshData.vertexBuffer.AsResource(), vertexState);
|
||||
}
|
||||
|
||||
if (needIndexTransition)
|
||||
{
|
||||
_resourceDatabase.SetResourceState(meshData.indexBuffer.AsResource(), indexState);
|
||||
_copyCmd.ResourceBarrier(meshData.indexBuffer.AsResource(), ResourceState.CopyDest, indexState);
|
||||
}
|
||||
|
||||
if (markMeshStatic)
|
||||
{
|
||||
meshData.ReleaseCpuResources();
|
||||
}
|
||||
}
|
||||
|
||||
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
|
||||
{
|
||||
return _resourceAllocator.CreateTexture(in desc, tempResource);
|
||||
}
|
||||
|
||||
public void UploadTexture(Handle<Texture> texture, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var desc = _resourceDatabase.GetResourceDescription(texture.AsResource());
|
||||
desc.textureDescription.Format.GetSurfaceInfo((int)desc.textureDescription.Width, (int)desc.textureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
var subresourceData = new SubResourceData
|
||||
{
|
||||
pData = data.GetPointer(),
|
||||
rowPitch = rowPitch,
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
var sateBefore = _resourceDatabase.GetResourceState(texture.AsResource());
|
||||
var needTransition = sateBefore != ResourceState.CopyDest;
|
||||
|
||||
if (needTransition)
|
||||
{
|
||||
_copyCmd.ResourceBarrier(texture.AsResource(), sateBefore, ResourceState.CopyDest);
|
||||
_resourceDatabase.SetResourceState(texture.AsResource(), ResourceState.CopyDest);
|
||||
}
|
||||
|
||||
_copyCmd.Upload(texture, subresourceData);
|
||||
|
||||
if (needTransition)
|
||||
{
|
||||
_copyCmd.ResourceBarrier(texture.AsResource(), ResourceState.CopyDest, sateBefore);
|
||||
_resourceDatabase.SetResourceState(texture.AsResource(), sateBefore);
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderMesh(Handle<Mesh> mesh, Handle<Material> material)
|
||||
{
|
||||
_cmd.DrawMesh(mesh, material);
|
||||
}
|
||||
|
||||
public void ExecuteCopyCommands()
|
||||
{
|
||||
_device.CopyQueue.Submit(_copyCmd);
|
||||
_device.CopyQueue.WaitIdle();
|
||||
}
|
||||
}
|
||||
@@ -1,148 +1,30 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32.Graphics.D3D12MemoryAllocator;
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
public readonly struct ResourceHandle : IEquatable<ResourceHandle>
|
||||
public readonly struct GPUResource : IHandleType;
|
||||
public readonly struct Texture : IHandleType;
|
||||
public readonly struct GraphicsBuffer : IHandleType;
|
||||
|
||||
public static class ResourceHandleExtensions
|
||||
{
|
||||
private const int _INVALID_ID = -1;
|
||||
|
||||
public readonly int id;
|
||||
public readonly int generation;
|
||||
|
||||
public static ResourceHandle Invalid => new(_INVALID_ID, _INVALID_ID);
|
||||
|
||||
internal ResourceHandle(int id, int generation)
|
||||
public static Handle<GPUResource> AsResource(this Handle<Texture> texture)
|
||||
{
|
||||
this.id = id;
|
||||
this.generation = generation;
|
||||
return new Handle<GPUResource>(texture.id, texture.generation);
|
||||
}
|
||||
|
||||
public bool IsValid => id != _INVALID_ID && generation != _INVALID_ID;
|
||||
|
||||
public bool Equals(ResourceHandle other)
|
||||
public static Handle<GPUResource> AsResource(this Handle<GraphicsBuffer> buffer)
|
||||
{
|
||||
return id == other.id && generation == other.generation;
|
||||
return new Handle<GPUResource>(buffer.id, buffer.generation);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
internal static Handle<Texture> AsTexture(this Handle<GPUResource> resource)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (id * 397) ^ generation;
|
||||
}
|
||||
return new Handle<Texture>(resource.id, resource.generation);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
internal static Handle<GraphicsBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
|
||||
{
|
||||
return obj is ResourceHandle handle && Equals(handle);
|
||||
}
|
||||
|
||||
public static bool operator ==(ResourceHandle left, ResourceHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ResourceHandle left, ResourceHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct TextureHandle : IEquatable<TextureHandle>
|
||||
{
|
||||
private readonly ResourceHandle _resourceHandle;
|
||||
private readonly BindlessDescriptor _bindlessDescriptor;
|
||||
|
||||
public ResourceHandle ResourceHandle => _resourceHandle;
|
||||
public static TextureHandle Invalid => new(ResourceHandle.Invalid);
|
||||
|
||||
internal TextureHandle(ResourceHandle resourceHandle)
|
||||
{
|
||||
_resourceHandle = resourceHandle;
|
||||
_bindlessDescriptor = BindlessDescriptor.Invalid;
|
||||
}
|
||||
|
||||
internal TextureHandle(ResourceHandle resourceHandle, BindlessDescriptor descriptor)
|
||||
{
|
||||
_resourceHandle = resourceHandle;
|
||||
_bindlessDescriptor = descriptor;
|
||||
}
|
||||
|
||||
public bool IsValid => _resourceHandle.IsValid;
|
||||
|
||||
public bool Equals(TextureHandle other)
|
||||
{
|
||||
return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextureHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
|
||||
}
|
||||
|
||||
public static bool operator ==(TextureHandle left, TextureHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextureHandle left, TextureHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct BufferHandle : IEquatable<BufferHandle>
|
||||
{
|
||||
private readonly ResourceHandle _resourceHandle;
|
||||
private readonly BindlessDescriptor _bindlessDescriptor;
|
||||
|
||||
public static BufferHandle Invalid => new(ResourceHandle.Invalid);
|
||||
|
||||
public ResourceHandle ResourceHandle => _resourceHandle;
|
||||
public BindlessDescriptor BindlessDescriptor => _bindlessDescriptor;
|
||||
|
||||
internal BufferHandle(ResourceHandle resourceHandle)
|
||||
{
|
||||
_resourceHandle = resourceHandle;
|
||||
_bindlessDescriptor = BindlessDescriptor.Invalid;
|
||||
}
|
||||
|
||||
internal BufferHandle(ResourceHandle resourceHandle, BindlessDescriptor descriptor)
|
||||
{
|
||||
_resourceHandle = resourceHandle;
|
||||
_bindlessDescriptor = descriptor;
|
||||
}
|
||||
|
||||
public bool IsValid => _resourceHandle.IsValid;
|
||||
|
||||
public bool Equals(BufferHandle other)
|
||||
{
|
||||
return _resourceHandle.Equals(other._resourceHandle) && _bindlessDescriptor.Equals(other._bindlessDescriptor);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is BufferHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_resourceHandle, _bindlessDescriptor);
|
||||
}
|
||||
|
||||
public static bool operator ==(BufferHandle left, BufferHandle right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(BufferHandle left, BufferHandle right)
|
||||
{
|
||||
return !(left == right);
|
||||
return new Handle<GraphicsBuffer>(resource.id, resource.generation);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
@@ -47,16 +48,19 @@ internal readonly struct CBufferInfo
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ShaderPass
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A representation of a GPU shader, including its metadata about its resources.
|
||||
/// </summary>
|
||||
|
||||
// TODO: Multi pass and keyword support
|
||||
public struct Shader : IDisposable
|
||||
public struct Shader : IIdentifierType
|
||||
{
|
||||
private UnsafeList<CBufferInfo> _constantBuffers;
|
||||
private UnsafeList<PropertyInfo> _properties;
|
||||
private UnsafeList<TextureInfo> _regularTextures; // Add regular texture support
|
||||
// TODO: Possible to move this to unmanaged heap?
|
||||
private Dictionary<string, int> _propertyNameToIdMap;
|
||||
|
||||
@@ -64,26 +68,16 @@ public struct Shader : IDisposable
|
||||
|
||||
internal readonly UnsafeList<CBufferInfo> ConstantBuffers => _constantBuffers;
|
||||
internal readonly UnsafeList<PropertyInfo> Properties => _properties;
|
||||
internal readonly UnsafeList<TextureInfo> RegularTextures => _regularTextures;
|
||||
internal readonly Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
|
||||
|
||||
public Shader()
|
||||
{
|
||||
_constantBuffers = new(8, Allocator.Persistent);
|
||||
_properties = new(8, Allocator.Persistent);
|
||||
_regularTextures = new(8, Allocator.Persistent);
|
||||
_propertyNameToIdMap = new(8);
|
||||
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
internal void AddProperty(string name, PropertyInfo propertyInfo)
|
||||
{
|
||||
var id = _properties.Count;
|
||||
_properties.Add(propertyInfo);
|
||||
_propertyNameToIdMap[name] = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique, stable ID for a shader property.
|
||||
/// </summary>
|
||||
@@ -94,7 +88,7 @@ public struct Shader : IDisposable
|
||||
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
internal void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
@@ -103,7 +97,6 @@ public struct Shader : IDisposable
|
||||
|
||||
_constantBuffers.Dispose();
|
||||
_properties.Dispose();
|
||||
_regularTextures.Dispose();
|
||||
|
||||
_propertyNameToIdMap.Clear();
|
||||
_propertyNameToIdMap = null!;
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all texture types in the graphics pipeline.
|
||||
/// Provides common functionality for texture dimensions, format, and GPU resource management.
|
||||
/// </summary>
|
||||
public abstract unsafe class Texture : GraphicsResource
|
||||
{
|
||||
private readonly BindlessDescriptor _bindlessDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Width of the texture in pixels.
|
||||
/// </summary>
|
||||
public uint Width
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height of the texture in pixels.
|
||||
/// </summary>
|
||||
public uint Height
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes per pixel for the texture format.
|
||||
/// </summary>
|
||||
public uint BytesPerPixel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format of the texture.
|
||||
/// </summary>
|
||||
public Format Format
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of this texture in the global bindless descriptor heap.
|
||||
/// </summary>
|
||||
public uint DescriptorIndex => _bindlessDescriptor.Index;
|
||||
|
||||
internal Texture(uint width, uint height, Format format, in TextureHandle handle, BindlessDescriptor bindlessDescriptor)
|
||||
: base(handle.ResourceHandle)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
BytesPerPixel = GetBytesPerPixel(format);
|
||||
Format = format;
|
||||
_bindlessDescriptor = bindlessDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a bindless shader resource view descriptor for the texture.
|
||||
/// </summary>
|
||||
protected static BindlessDescriptor CreateBindlessShaderResourceView(ID3D12Resource* resource, Format format)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr;
|
||||
var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless();
|
||||
|
||||
var srvDesc = new ShaderResourceViewDescription
|
||||
{
|
||||
Format = format,
|
||||
ViewDimension = SrvDimension.Texture2D,
|
||||
Texture2D = new Texture2DSrv { MipLevels = 1 },
|
||||
Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
device->CreateShaderResourceView(resource, &srvDesc, bindlessDescriptor.CpuHandle);
|
||||
return bindlessDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bytes per pixel for the specified format.
|
||||
/// </summary>
|
||||
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,
|
||||
Format.D32Float => 4,
|
||||
Format.D24UnormS8Uint => 4,
|
||||
Format.D16Unorm => 2,
|
||||
_ => throw new NotSupportedException($"Format {format} is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the texture resources.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
GraphicsPipeline.DescriptorAllocator.Release(_bindlessDescriptor);
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
using Misaki.HighPerformance.Image;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
|
||||
namespace Ghost.Graphics.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Unified texture implementation that supports both bindless and regular descriptor table binding
|
||||
/// for use with SM 6.6 ResourceDescriptorHeap access and traditional frame buffer textures
|
||||
/// </summary>
|
||||
public unsafe class Texture2D : Texture
|
||||
{
|
||||
private UnTypedArray _cpuData;
|
||||
|
||||
public uint Pitch
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
private Texture2D(uint width, uint height, Format format, in TextureHandle handle, in ReadOnlySpan<byte> cpuData, BindlessDescriptor bindlessDescriptor)
|
||||
: base(width, height, format, in handle, bindlessDescriptor)
|
||||
{
|
||||
Pitch = width * BytesPerPixel;
|
||||
|
||||
uint alignment;
|
||||
if (BytesPerPixel > 4)
|
||||
{
|
||||
alignment = (uint)MemoryUtilities.AlignOf<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
alignment = (uint)MemoryUtilities.AlignOf<byte>();
|
||||
}
|
||||
|
||||
_cpuData = new UnTypedArray((uint)Size, alignment, ref AllocationManager.PersistentHandle);
|
||||
if (!cpuData.IsEmpty)
|
||||
{
|
||||
if ((uint)cpuData.Length > Size)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {Size} bytes, got {cpuData.Length} bytes.");
|
||||
}
|
||||
|
||||
_cpuData.CopyFrom(cpuData);
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture2D Create(uint width, uint height, in ReadOnlySpan<byte> data, Format format)
|
||||
{
|
||||
var handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(width, height, 0, format);
|
||||
var bindlessDescriptor = CreateBindlessShaderResourceView(handle.ResourceHandle.GetAllocation().Resource, format);
|
||||
|
||||
return new Texture2D(width, height, format, in handle, in data, bindlessDescriptor);
|
||||
}
|
||||
|
||||
public static Texture2D FromFile(string filePath)
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using var image = ImageResult.FromStream(stream, ColorComponents.RGBA);
|
||||
|
||||
return Create(image.Width, image.Height, image.AsSpan(), Format.R8G8B8A8Unorm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the entire texture data on the CPU side.
|
||||
/// </summary>
|
||||
/// <param name="data">The texture data to set</param>
|
||||
public void SetData<T>(ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if ((uint)data.Length > Size)
|
||||
{
|
||||
throw new ArgumentException($"Data size mismatch. Expected {Size} bytes, got {data.Length} bytes.");
|
||||
}
|
||||
|
||||
_cpuData.CopyFrom(data);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
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 = y * Pitch + x * BytesPerPixel;
|
||||
_cpuData.CopyFrom(color, 0u, offset, 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);
|
||||
fixed (byte* pPixel = pixelData)
|
||||
{
|
||||
return *(T*)pPixel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
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>
|
||||
/// Uploads the CPU-side texture data to the GPU resource.
|
||||
/// </summary>
|
||||
public void UploadTextureData()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
Format.GetSurfaceInfo((int)Width, (int)Height, out var rowPitch, out var slicePitch);
|
||||
var initData = new SubresourceData()
|
||||
{
|
||||
pData = _cpuData.GetUnsafePtr(),
|
||||
RowPitch = rowPitch,
|
||||
SlicePitch = slicePitch
|
||||
};
|
||||
|
||||
var uploadBatch = GraphicsPipeline.UploadBatch;
|
||||
uploadBatch.Transition(NativeResource, ResourceStates.Common, ResourceStates.CopyDest);
|
||||
uploadBatch.Upload(NativeResource, 0, &initData, 1);
|
||||
uploadBatch.Transition(NativeResource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
_cpuData.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user