using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.DXGI_Alias;

namespace Ghost.Graphics.D3D12.Utilities;

internal static unsafe class D3D12Utility
{
    [EditorBrowsable(EditorBrowsableState.Never)]
    public readonly ref struct IID_PPV
    {
        public readonly Guid* iid;
        public readonly void** ppv;

        public IID_PPV(Guid* iid, void** ppv)
        {
            this.iid = iid;
            this.ppv = ppv;
        }

        public void Deconstruct(out Guid* iid, out void** ppv)
        {
            iid = this.iid;
            ppv = this.ppv;
        }
    }

    public static Guid* IID_NULL
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL));
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static IID_PPV IID_PPV_ARGS<T>(ComPtr<T>* comPtr)
        where T : unmanaged, IUnknown.Interface
    {
        return new IID_PPV(__uuidof<T>(), (void**)comPtr);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Attach<T>(ref this UniquePtr<T> uPtr, T* other)
        where T : unmanaged, IUnknown.Interface
    {
        var ptr = uPtr.Get();
        if (ptr != null)
        {
            Logger.DebugAssert(ptr != other);
            var refCount = ptr->Release();
            Logger.DebugAssert(refCount == 0);
        }

        uPtr = new UniquePtr<T>(other);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Dispose<T>(ref this UniquePtr<T> uPtr)
        where T : unmanaged, IUnknown.Interface
    {
        var ptr = uPtr.Detach();
        if (ptr != null)
        {
            var refCount = ptr->Release();
            //Logger.DebugAssert(refCount == 0);
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Result ToResult(this HRESULT hr, [CallerArgumentExpression(nameof(hr))] string? op = null)
    {
        if (hr.SUCCEEDED)
        {
            return Result.Success();
        }

        return Result.Failure($"{op} failed with code {hr}");
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void** ReleaseAndGetVoidAddressOf<T>(ref this ComPtr<T> comPtr)
        where T : unmanaged, IUnknown.Interface
    {
        return (void**)comPtr.ReleaseAndGetAddressOf();
    }

    public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
        where T : unmanaged, ID3D12Object.Interface
    {
        if (name.IsEmpty)
        {
            return;
        }

        fixed (char* pName = name)
        {
            obj.SetName(pName);
        }
    }

    public static void SetName(ref this D3D12MA_Allocation obj, ReadOnlySpan<char> name)
    {
        if (name.IsEmpty)
        {
            return;
        }

        fixed (char* pName = name)
        {
            obj.SetName(pName);
        }
    }

    public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
    {
        return dimension switch
        {
            D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture1D,
            D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
            D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
            _ => throw new NotSupportedException($"Resource dimension {dimension} is not supported."),
        };
    }

    public static DXGI_FORMAT ToDXGIFormat(this TextureFormat format)
    {
        return format switch
        {
            TextureFormat.Unknown => DXGI_FORMAT_UNKNOWN,

            TextureFormat.R8_UNorm => DXGI_FORMAT_R8_UNORM,
            TextureFormat.R8_SNorm => DXGI_FORMAT_R8_SNORM,
            TextureFormat.R16_UNorm => DXGI_FORMAT_R16_UNORM,
            TextureFormat.R16_SNorm => DXGI_FORMAT_R16_SNORM,
            TextureFormat.R16_Float => DXGI_FORMAT_R16_FLOAT,
            TextureFormat.R32_UInt => DXGI_FORMAT_R32_UINT,
            TextureFormat.R32_SInt => DXGI_FORMAT_R32_SINT,

            TextureFormat.R8G8_UNorm => DXGI_FORMAT_R8G8_UNORM,
            TextureFormat.R8G8_SNorm => DXGI_FORMAT_R8G8_SNORM,
            TextureFormat.R16G16_UNorm => DXGI_FORMAT_R16G16_UNORM,
            TextureFormat.R16G16_SNorm => DXGI_FORMAT_R16G16_SNORM,
            TextureFormat.R16G16_Float => DXGI_FORMAT_R16G16_FLOAT,
            TextureFormat.R32G32_Float => DXGI_FORMAT_R32G32_FLOAT,

            TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT_R8G8B8A8_UNORM,
            TextureFormat.R8G8B8A8_SNorm => DXGI_FORMAT_R8G8B8A8_SNORM,
            TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT_B8G8R8A8_UNORM,

            TextureFormat.R10G10B10A2_UNorm => DXGI_FORMAT_R10G10B10A2_UNORM,

            TextureFormat.R16G16B16A16_Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
            TextureFormat.R32G32B32A32_Float => DXGI_FORMAT_R32G32B32A32_FLOAT,

            TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT_D24_UNORM_S8_UINT,
            TextureFormat.D32_Float => DXGI_FORMAT_D32_FLOAT,
            TextureFormat.R32_Typeless => DXGI_FORMAT_R32_TYPELESS,
            TextureFormat.R24G8_Typeless => DXGI_FORMAT_R24G8_TYPELESS,
            _ => throw new NotSupportedException($"Texture format {format} is not supported."),
        };
    }

    public static TextureFormat ToTextureFormat(this DXGI_FORMAT format)
    {
        return format switch
        {
            DXGI_FORMAT_UNKNOWN => TextureFormat.Unknown,

            DXGI_FORMAT_R8_UNORM => TextureFormat.R8_UNorm,
            DXGI_FORMAT_R8_SNORM => TextureFormat.R8_SNorm,
            DXGI_FORMAT_R16_UNORM => TextureFormat.R16_UNorm,
            DXGI_FORMAT_R16_SNORM => TextureFormat.R16_SNorm,
            DXGI_FORMAT_R16_FLOAT => TextureFormat.R16_Float,
            DXGI_FORMAT_R32_UINT => TextureFormat.R32_UInt,
            DXGI_FORMAT_R32_SINT => TextureFormat.R32_SInt,

            DXGI_FORMAT_R8G8_UNORM => TextureFormat.R8G8_UNorm,
            DXGI_FORMAT_R8G8_SNORM => TextureFormat.R8G8_SNorm,
            DXGI_FORMAT_R16G16_UNORM => TextureFormat.R16G16_UNorm,
            DXGI_FORMAT_R16G16_SNORM => TextureFormat.R16G16_SNorm,
            DXGI_FORMAT_R16G16_FLOAT => TextureFormat.R16G16_Float,
            DXGI_FORMAT_R32G32_FLOAT => TextureFormat.R32G32_Float,

            DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat.R8G8B8A8_UNorm,
            DXGI_FORMAT_R8G8B8A8_SNORM => TextureFormat.R8G8B8A8_SNorm,
            DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNorm,

            DXGI_FORMAT_R10G10B10A2_UNORM => TextureFormat.R10G10B10A2_UNorm,

            DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_Float,
            DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float,

            DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt,
            DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float,
            DXGI_FORMAT_R32_TYPELESS => TextureFormat.R32_Typeless,
            DXGI_FORMAT_R24G8_TYPELESS => TextureFormat.R24G8_Typeless,
            _ => throw new NotSupportedException($"DXGI format {format} is not supported.")
        };
    }

    public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state)
    {
        var d3dStates = D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON;

        if (state.HasFlag(ResourceState.VertexAndConstantBuffer))
        {
            d3dStates |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
        }

        if (state.HasFlag(ResourceState.IndexBuffer))
        {
            d3dStates |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
        }

        if (state.HasFlag(ResourceState.RenderTarget))
        {
            d3dStates |= D3D12_RESOURCE_STATE_RENDER_TARGET;
        }

        if (state.HasFlag(ResourceState.UnorderedAccess))
        {
            d3dStates |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
        }

        if (state.HasFlag(ResourceState.DepthWrite))
        {
            d3dStates |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
        }

        if (state.HasFlag(ResourceState.DepthRead))
        {
            d3dStates |= D3D12_RESOURCE_STATE_DEPTH_READ;
        }

        if (state.HasFlag(ResourceState.PixelShaderResource))
        {
            d3dStates |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
        }

        if (state.HasFlag(ResourceState.CopyDest))
        {
            d3dStates |= D3D12_RESOURCE_STATE_COPY_DEST;
        }

        if (state.HasFlag(ResourceState.CopySource))
        {
            d3dStates |= D3D12_RESOURCE_STATE_COPY_SOURCE;
        }

        if (state.HasFlag(ResourceState.GenericRead))
        {
            d3dStates |= D3D12_RESOURCE_STATE_GENERIC_READ;
        }

        if (state.HasFlag(ResourceState.IndirectArgument))
        {
            d3dStates |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
        }

        if (state.HasFlag(ResourceState.NonPixelShaderResource))
        {
            d3dStates |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
        }

        return d3dStates;
    }

    public static ResourceState ToResourceState(this D3D12_RESOURCE_STATES states)
    {
        var resourceState = ResourceState.Common;

        if (states.HasFlag(D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER))
        {
            resourceState |= ResourceState.VertexAndConstantBuffer;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_INDEX_BUFFER))
        {
            resourceState |= ResourceState.IndexBuffer;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_RENDER_TARGET))
        {
            resourceState |= ResourceState.RenderTarget;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
        {
            resourceState |= ResourceState.UnorderedAccess;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_WRITE))
        {
            resourceState |= ResourceState.DepthWrite;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_READ))
        {
            resourceState |= ResourceState.DepthRead;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
        {
            resourceState |= ResourceState.PixelShaderResource;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_DEST))
        {
            resourceState |= ResourceState.CopyDest;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_SOURCE))
        {
            resourceState |= ResourceState.CopySource;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_GENERIC_READ))
        {
            resourceState |= ResourceState.GenericRead;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT))
        {
            resourceState |= ResourceState.IndirectArgument;
        }

        if (states.HasFlag(D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE))
        {
            resourceState |= ResourceState.NonPixelShaderResource;
        }

        return resourceState;
    }

    public static D3D12_FILTER ToD3D12Filter(this TextureFilterMode filterMode)
    {
        return filterMode switch
        {
            TextureFilterMode.Point => D3D12_FILTER_MIN_MAG_MIP_POINT,
            TextureFilterMode.Bilinear => D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT,
            TextureFilterMode.Trilinear => D3D12_FILTER_MIN_MAG_MIP_LINEAR,
            TextureFilterMode.Anisotropic => D3D12_FILTER_ANISOTROPIC,
            _ => throw new ArgumentException($"Unknown texture filter mode: {filterMode}")
        };
    }

    public static D3D12_TEXTURE_ADDRESS_MODE ToD3D12TextureAddressMode(this TextureAddressMode addressMode)
    {
        return addressMode switch
        {
            TextureAddressMode.Repeat => D3D12_TEXTURE_ADDRESS_MODE_WRAP,
            TextureAddressMode.Mirror => D3D12_TEXTURE_ADDRESS_MODE_MIRROR,
            TextureAddressMode.Clamp => D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
            TextureAddressMode.Border => D3D12_TEXTURE_ADDRESS_MODE_BORDER,
            TextureAddressMode.MirrorOnce => D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE,
            _ => throw new ArgumentException($"Unknown texture address mode: {addressMode}")
        };
    }

    public static D3D12_COMPARISON_FUNC ToD3D12ComparisonFunc(this ComparisonFunction func)
    {
        return func switch
        {
            ComparisonFunction.Never => D3D12_COMPARISON_FUNC_NEVER,
            ComparisonFunction.Less => D3D12_COMPARISON_FUNC_LESS,
            ComparisonFunction.Equal => D3D12_COMPARISON_FUNC_EQUAL,
            ComparisonFunction.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
            ComparisonFunction.Greater => D3D12_COMPARISON_FUNC_GREATER,
            ComparisonFunction.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
            ComparisonFunction.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
            ComparisonFunction.Always => D3D12_COMPARISON_FUNC_ALWAYS,
            _ => throw new ArgumentException($"Unknown comparison function: {func}")
        };
    }

    public static D3D12_COMPARISON_FUNC ToD3DCompare(this ZTest z)
    {
        return z switch
        {
            ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER,
            ZTest.Less => D3D12_COMPARISON_FUNC_LESS,
            ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
            ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL,
            ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
            ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER,
            ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
            ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS,
            _ => D3D12_COMPARISON_FUNC_LESS_EQUAL
        };
    }

    public static D3D12_COMMAND_LIST_TYPE ToCommandListType(CommandBufferType type)
    {
        return type switch
        {
            CommandBufferType.Graphics => D3D12_COMMAND_LIST_TYPE_DIRECT,
            CommandBufferType.Compute => D3D12_COMMAND_LIST_TYPE_COMPUTE,
            CommandBufferType.Copy => D3D12_COMMAND_LIST_TYPE_COPY,
            _ => throw new ArgumentException($"Unknown command buffer type: {type}")
        };
    }

    public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this TextureUsage usage)
    {
        var flags = D3D12_RESOURCE_FLAG_NONE;

        if (usage.HasFlag(TextureUsage.RenderTarget))
        {
            flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
        }

        if (usage.HasFlag(TextureUsage.DepthStencil))
        {
            flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
        }

        if (usage.HasFlag(TextureUsage.UnorderedAccess))
        {
            flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
        }

        return flags;
    }

    public static D3D12_HEAP_TYPE ToD3D12HeapType(this HeapType heapType)
    {
        return heapType switch
        {
            HeapType.Default => D3D12_HEAP_TYPE_DEFAULT,
            HeapType.Upload => D3D12_HEAP_TYPE_UPLOAD,
            HeapType.Readback => D3D12_HEAP_TYPE_READBACK,
            _ => throw new ArgumentException($"Unknown heap type: {heapType}")
        };
    }

    public static HeapType ToHeapType(this D3D12_HEAP_TYPE heapType)
    {
        return heapType switch
        {
            D3D12_HEAP_TYPE_DEFAULT => HeapType.Default,
            D3D12_HEAP_TYPE_UPLOAD => HeapType.Upload,
            D3D12_HEAP_TYPE_READBACK => HeapType.Readback,
            _ => throw new ArgumentException($"Unknown D3D12 heap type: {heapType}")
        };
    }

    public static D3D12_HEAP_FLAGS ToD3D12HeapFlags(this HeapFlags flags)
    {
        return flags switch
        {
            HeapFlags.None => D3D12_HEAP_FLAG_NONE,
            HeapFlags.Shared => D3D12_HEAP_FLAG_SHARED,
            HeapFlags.AllowOnlyBuffers => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
            HeapFlags.AllowOnlyTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
            HeapFlags.AllowOnlyRTAndDS => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
            HeapFlags.AllowAllBufferAndTexture => D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES,
            _ => throw new ArgumentException($"Unknown heap flags: {flags}")
        };
    }

    public static HeapFlags ToHeapFlags(this D3D12_HEAP_FLAGS flags)
    {
        if (flags == D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES)
        {
            return HeapFlags.AllowAllBufferAndTexture;
        }

        return flags switch
        {
            D3D12_HEAP_FLAG_NONE => HeapFlags.None,
            D3D12_HEAP_FLAG_SHARED => HeapFlags.Shared,
            D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS => HeapFlags.AllowOnlyBuffers,
            D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES => HeapFlags.AllowOnlyTextures,
            D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES => HeapFlags.AllowOnlyRTAndDS,
            _ => throw new ArgumentException($"Unknown D3D12 heap flags: {flags}")
        };
    }

    public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
    {
        var dxgiFormat = desc.Format.ToDXGIFormat();

        if (desc.Usage.HasFlag(TextureUsage.DepthStencil) && desc.Usage.HasFlag(TextureUsage.ShaderResource))
        {
            if (dxgiFormat == DXGI_FORMAT_D32_FLOAT)
            {
                dxgiFormat = DXGI_FORMAT_R32_TYPELESS;
            }
            else if (dxgiFormat == DXGI_FORMAT_D24_UNORM_S8_UINT)
            {
                dxgiFormat = DXGI_FORMAT_R24G8_TYPELESS;
            }
        }

        var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
        var mipLevels = desc.MipLevels == 0
            ? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
            : (ushort)desc.MipLevels;

        var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
        return desc.Dimension switch
        {
            TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                flags: resourceFlags),
            TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                (ushort)desc.Slice,
                                flags: resourceFlags),
            TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                arraySize: 6,
                                flags: resourceFlags),
            TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                arraySize: (ushort)desc.Slice,
                                flags: resourceFlags),
            TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                arraySize: (ushort)(desc.Slice * 6),
                                flags: resourceFlags),
            _ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
        };
    }

    public static D3D12_RESOURCE_DESC1 ToD3D12ResourceDesc1(this in TextureDesc desc)
    {
        var dxgiFormat = desc.Format.ToDXGIFormat();

        if (desc.Usage.HasFlag(TextureUsage.DepthStencil) && desc.Usage.HasFlag(TextureUsage.ShaderResource))
        {
            if (dxgiFormat == DXGI_FORMAT_D32_FLOAT)
            {
                dxgiFormat = DXGI_FORMAT_R32_TYPELESS;
            }
            else if (dxgiFormat == DXGI_FORMAT_D24_UNORM_S8_UINT)
            {
                dxgiFormat = DXGI_FORMAT_R24G8_TYPELESS;
            }
        }

        var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
        var mipLevels = desc.MipLevels == 0
            ? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
            : (ushort)desc.MipLevels;

        var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
        return desc.Dimension switch
        {
            TextureDimension.Texture2D => D3D12_RESOURCE_DESC1.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                flags: resourceFlags),
            TextureDimension.Texture3D => D3D12_RESOURCE_DESC1.Tex3D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                (ushort)desc.Slice,
                                flags: resourceFlags),
            TextureDimension.TextureCube => D3D12_RESOURCE_DESC1.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                arraySize: 6,
                                flags: resourceFlags),
            TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC1.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                arraySize: (ushort)desc.Slice,
                                flags: resourceFlags),
            TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC1.Tex2D(
                                dxgiFormat,
                                desc.Width,
                                desc.Height,
                                mipLevels: mipLevels,
                                arraySize: (ushort)(desc.Slice * 6),
                                flags: resourceFlags),
            _ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
        };
    }

    public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this BufferUsage usage)
    {
        var flags = D3D12_RESOURCE_FLAG_NONE;

        if (usage.HasFlag(BufferUsage.UnorderedAccess))
        {
            flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
        }

        return flags;
    }

    public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in BufferDesc desc)
    {
        var alignedSize = desc.Size;
        if (desc.Usage.HasFlag(BufferUsage.Constant))
        {
            // D3D12 CBV size must be 256-byte aligned
            alignedSize = (uint)(desc.Size + 255) & ~255u;
        }

        var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
        return D3D12_RESOURCE_DESC.Buffer(alignedSize, resourceFlags);
    }

    public static D3D12_RESOURCE_DESC1 ToD3D12ResourceDesc1(this in BufferDesc desc)
    {
        var alignedSize = desc.Size;
        if (desc.Usage.HasFlag(BufferUsage.Constant))
        {
            // D3D12 CBV size must be 256-byte aligned
            alignedSize = (uint)(desc.Size + 255) & ~255u;
        }

        var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
        return D3D12_RESOURCE_DESC1.Buffer(alignedSize, resourceFlags);
    }

    public static ResourceDesc GetResourceDesc(ID3D12Resource* pResource, ResourceViewGroup viewGroup)
    {
        D3D12_HEAP_PROPERTIES heapProperties;
        D3D12_HEAP_FLAGS heapFlags;

        ThrowIfFailed(pResource->GetHeapProperties(&heapProperties, &heapFlags));
        var desc = pResource->GetDesc();

        if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
        {
            return ResourceDesc.Buffer(new BufferDesc
            {
                Size = (uint)desc.Width,
                Stride = 0,
                Usage = viewGroup.GetBufferUsage(),
                HeapType = heapProperties.Type.ToHeapType()
            });
        }
        else
        {
            return ResourceDesc.Texture(new TextureDesc
            {
                Width = (uint)desc.Width,
                Height = desc.Height,
                Slice = desc.DepthOrArraySize,
                Format = desc.Format.ToTextureFormat(),
                Dimension = desc.Dimension.ToTextureDimension(),
                MipLevels = desc.MipLevels,
                Usage = viewGroup.GetTextureUsage(),
            });
        }
    }


    public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CREATE(
        D3D12_FILL_MODE fillMode,
        D3D12_CULL_MODE cullMode,
        bool frontCounterClockwise = false,
        int depthBias = D3D12_DEFAULT_DEPTH_BIAS,
        float depthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
        float slopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
        bool depthClipEnable = true,
        bool multisampleEnable = true,
        bool antialiasedLineEnable = false,
        uint forcedSampleCount = 0,
        D3D12_CONSERVATIVE_RASTERIZATION_MODE conservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF)
    {
        return new D3D12_RASTERIZER_DESC
        {
            FillMode = fillMode,
            CullMode = cullMode,
            FrontCounterClockwise = frontCounterClockwise ? TRUE : FALSE,
            DepthBias = depthBias,
            DepthBiasClamp = depthBiasClamp,
            SlopeScaledDepthBias = slopeScaledDepthBias,
            DepthClipEnable = depthClipEnable ? TRUE : FALSE,
            MultisampleEnable = multisampleEnable ? TRUE : FALSE,
            AntialiasedLineEnable = antialiasedLineEnable ? TRUE : FALSE,
            ForcedSampleCount = forcedSampleCount,
            ConservativeRaster = conservativeRaster
        };
    }

    public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_NONE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE);
    public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_CLOCKWISE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_FRONT);
    public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_BACK);
    public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_WIREFRAME => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_WIREFRAME, D3D12_CULL_MODE_NONE);


    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_CREATE(D3D12_BLEND srcBlend, D3D12_BLEND destBlend)
    {
        var blendDesc = new D3D12_BLEND_DESC
        {
            AlphaToCoverageEnable = false,
            IndependentBlendEnable = false
        };

        for (var i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
        {
            blendDesc.RenderTarget[i].BlendEnable = srcBlend != D3D12_BLEND_ONE || destBlend != D3D12_BLEND_ZERO;
            blendDesc.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_NOOP;
            blendDesc.RenderTarget[i].SrcBlend = srcBlend;
            blendDesc.RenderTarget[i].DestBlend = destBlend;
            blendDesc.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
            blendDesc.RenderTarget[i].SrcBlendAlpha = srcBlend;
            blendDesc.RenderTarget[i].DestBlendAlpha = destBlend;
            blendDesc.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
            blendDesc.RenderTarget[i].RenderTargetWriteMask = (byte)D3D12_COLOR_WRITE_ENABLE_ALL;
        }

        return blendDesc;
    }

    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_OPAQUE => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_ONE, D3D12_BLEND_ZERO);
    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_ALPHA_BLEND => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_ADDITIVE => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_ONE);
    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_MULTIPLY => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_DEST_COLOR, D3D12_BLEND_ZERO);
    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_PREMULTIPLIED => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA);
    public static D3D12_BLEND_DESC D3D12_BLEND_DESC_NON_PREMULTIPLIED => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);


    public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_CREATE(
        bool depthEnable,
        bool depthWriteEnable,
        D3D12_COMPARISON_FUNC depthFunc,
        bool stencilEnable = false,
        byte stencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK,
        byte stencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK,
        D3D12_STENCIL_OP frontStencilFailOp = D3D12_STENCIL_OP_KEEP,
        D3D12_STENCIL_OP frontStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
        D3D12_STENCIL_OP frontStencilPassOp = D3D12_STENCIL_OP_KEEP,
        D3D12_COMPARISON_FUNC frontStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS,
        D3D12_STENCIL_OP backStencilFailOp = D3D12_STENCIL_OP_KEEP,
        D3D12_STENCIL_OP backStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
        D3D12_STENCIL_OP backStencilPassOp = D3D12_STENCIL_OP_KEEP,
        D3D12_COMPARISON_FUNC backStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS)
    {
        return new D3D12_DEPTH_STENCIL_DESC
        {
            DepthEnable = depthEnable,
            DepthWriteMask = depthWriteEnable ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO,
            DepthFunc = depthFunc,
            StencilEnable = stencilEnable,
            StencilReadMask = stencilReadMask,
            StencilWriteMask = stencilWriteMask,
            FrontFace = D3D12_DEPTH_STENCILOP_DESC_CREATE(frontStencilFailOp, frontStencilDepthFailOp, frontStencilPassOp, frontStencilFunc),
            BackFace = D3D12_DEPTH_STENCILOP_DESC_CREATE(backStencilFailOp, backStencilDepthFailOp, backStencilPassOp, backStencilFunc)
        };
    }

    public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_NONE => D3D12_DEPTH_STENCIL_DESC_CREATE(false, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
    public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_READ => D3D12_DEPTH_STENCIL_DESC_CREATE(true, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
    public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_REVERSE_Z => D3D12_DEPTH_STENCIL_DESC_CREATE(true, true, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
    public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_READ_REVERSE_Z => D3D12_DEPTH_STENCIL_DESC_CREATE(true, false, D3D12_COMPARISON_FUNC_GREATER_EQUAL);


    public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_CREATE(
        D3D12_STENCIL_OP stencilFailOp,
        D3D12_STENCIL_OP stencilDepthFailOp,
        D3D12_STENCIL_OP stencilPassOp,
        D3D12_COMPARISON_FUNC stencilFunc)
    {
        return new D3D12_DEPTH_STENCILOP_DESC
        {
            StencilFailOp = stencilFailOp,
            StencilDepthFailOp = stencilDepthFailOp,
            StencilPassOp = stencilPassOp,
            StencilFunc = stencilFunc
        };
    }

    public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_DEFAULT => D3D12_DEPTH_STENCILOP_DESC_CREATE(D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS);


    public static D3D12_SHADER_RESOURCE_VIEW_DESC CreateTextureSrvDesc(ID3D12Resource* pResource, uint mipLevels, uint arraySize, bool isCubeMap, TextureFormat originalFormat)
    {
        var resourceDesc = pResource->GetDesc();
        var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
        {
            Format = resourceDesc.Format,
            Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
        };

        if (originalFormat == TextureFormat.D32_Float)
        {
            srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
        }
        else if (originalFormat == TextureFormat.D24_UNorm_S8_UInt)
        {
            srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
        }

        switch (resourceDesc.Dimension)
        {
            case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
                if (resourceDesc.DepthOrArraySize > 1)
                {
                    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
                    srvDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_SRV
                    {
                        MipLevels = mipLevels,
                        ArraySize = arraySize,
                    };
                }
                else
                {
                    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
                    srvDesc.Texture1D = new D3D12_TEX1D_SRV
                    {
                        MipLevels = mipLevels,
                    };
                }
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
                if (resourceDesc.DepthOrArraySize > 1)
                {
                    if (isCubeMap)
                    {
                        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
                        srvDesc.TextureCubeArray = new D3D12_TEXCUBE_ARRAY_SRV
                        {
                            MipLevels = mipLevels,
                            NumCubes = arraySize / 6,
                        };
                    }
                    else
                    {
                        srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
                        srvDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_SRV
                        {
                            MipLevels = mipLevels,
                            ArraySize = arraySize,
                        };
                    }
                }
                else
                {
                    if (isCubeMap)
                    {
                        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
                        srvDesc.TextureCube = new D3D12_TEXCUBE_SRV
                        {
                            MipLevels = mipLevels,
                        };
                    }
                    else
                    {
                        srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
                        srvDesc.Texture2D = new D3D12_TEX2D_SRV
                        {
                            MipLevels = mipLevels,
                        };
                    }
                }
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
                srvDesc.Texture3D = new D3D12_TEX3D_SRV
                {
                    MipLevels = mipLevels,
                };
                break;
            default:
                throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
        }

        return srvDesc;
    }

    public static D3D12_SHADER_RESOURCE_VIEW_DESC CreateBufferSrvDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
    {
        var resourceDesc = pResource->GetDesc();
        var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
        {
            ViewDimension = D3D12_SRV_DIMENSION_BUFFER,
            Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
        };

        if (isRaw)
        {
            srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
            srvDesc.Buffer.FirstElement = 0;
            srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
            srvDesc.Buffer.StructureByteStride = 0;
            srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
        }
        else // Assumes Structured
        {
            srvDesc.Format = resourceDesc.Format;
            srvDesc.Buffer.FirstElement = 0;
            srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
            srvDesc.Buffer.StructureByteStride = stride;
            srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
        }

        return srvDesc;
    }

    public static D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
    {
        var resourceDesc = pResource->GetDesc();
        var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC();

        switch (resourceDesc.Dimension)
        {
            case D3D12_RESOURCE_DIMENSION_BUFFER:
                rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_BUFFER;
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
                rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE1DARRAY : D3D12_RTV_DIMENSION_TEXTURE1D;
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
                if (resourceDesc.SampleDesc.Count > 1)
                {
                    rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DMS;
                }
                else
                {
                    rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DARRAY : D3D12_RTV_DIMENSION_TEXTURE2D;
                }
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
                rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
                break;

            default:
                throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
        }

        rtvDesc.Format = resourceDesc.Format;

        var isArray =
            rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DARRAY ||
            rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;

        var arraySize = 1u;
        if (isArray)
        {
            arraySize = resourceDesc.ArraySize() - firstArraySlice;
        }

        switch (rtvDesc.ViewDimension)
        {
            case D3D12_RTV_DIMENSION_BUFFER:
                rtvDesc.Buffer.FirstElement = firstArraySlice;
                rtvDesc.Buffer.NumElements = arraySize;
                break;

            case D3D12_RTV_DIMENSION_TEXTURE1D:
                rtvDesc.Texture1D.MipSlice = mipSlice;
                break;

            case D3D12_RTV_DIMENSION_TEXTURE1DARRAY:
                rtvDesc.Texture1DArray.MipSlice = mipSlice;
                rtvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
                rtvDesc.Texture1DArray.ArraySize = arraySize;
                break;

            case D3D12_RTV_DIMENSION_TEXTURE2D:
                rtvDesc.Texture2D.MipSlice = mipSlice;
                rtvDesc.Texture2D.PlaneSlice = planeSlice;
                break;

            case D3D12_RTV_DIMENSION_TEXTURE2DARRAY:
                rtvDesc.Texture2DArray.MipSlice = mipSlice;
                rtvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
                rtvDesc.Texture2DArray.ArraySize = arraySize;
                rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
                break;

            case D3D12_RTV_DIMENSION_TEXTURE2DMS:
                break;

            case D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY:
                rtvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
                rtvDesc.Texture2DMSArray.ArraySize = arraySize;
                break;

            case D3D12_RTV_DIMENSION_TEXTURE3D:
                rtvDesc.Texture3D.MipSlice = mipSlice;
                rtvDesc.Texture3D.FirstWSlice = firstArraySlice;
                rtvDesc.Texture3D.WSize = arraySize;
                break;

            default:
                throw new ArgumentException($"Unsupported RTV dimension: {rtvDesc.ViewDimension}");
        }

        return rtvDesc;
    }

    public static D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE, TextureFormat originalFormat = TextureFormat.Unknown)
    {
        var resourceDesc = pResource->GetDesc();
        var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC
        {
            Flags = flags,
        };

        switch (resourceDesc.Dimension)
        {
            case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
                dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE1DARRAY : D3D12_DSV_DIMENSION_TEXTURE1D;
                break;
            case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
                if (resourceDesc.SampleDesc.Count > 1)
                {
                    dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DMS;
                }
                else
                {
                    dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DARRAY : D3D12_DSV_DIMENSION_TEXTURE2D;
                }
                break;
        }

        dsvDesc.Format = originalFormat == TextureFormat.Unknown ? resourceDesc.Format : originalFormat.ToDXGIFormat();

        var isArray =
            dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DARRAY ||
            dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;

        var arraySize = 1u;
        if (isArray)
        {
            arraySize = resourceDesc.ArraySize() - firstArraySlice;
        }

        switch (dsvDesc.ViewDimension)
        {
            case D3D12_DSV_DIMENSION_TEXTURE1D:
                dsvDesc.Texture1D.MipSlice = mipSlice;
                break;
            case D3D12_DSV_DIMENSION_TEXTURE1DARRAY:
                dsvDesc.Texture1DArray.MipSlice = mipSlice;
                dsvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
                dsvDesc.Texture1DArray.ArraySize = arraySize;
                break;
            case D3D12_DSV_DIMENSION_TEXTURE2D:
                dsvDesc.Texture2D.MipSlice = mipSlice;
                break;
            case D3D12_DSV_DIMENSION_TEXTURE2DARRAY:
                dsvDesc.Texture2DArray.MipSlice = mipSlice;
                dsvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
                dsvDesc.Texture2DArray.ArraySize = arraySize;
                break;
            case D3D12_DSV_DIMENSION_TEXTURE2DMS:
                break;
            case D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY:
                dsvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
                dsvDesc.Texture2DMSArray.ArraySize = arraySize;
                break;
            default:
                break;
        }

        return dsvDesc;
    }

    public static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateTextureUavDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
    {
        var resourceDesc = pResource->GetDesc();
        var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
        {
            Format = resourceDesc.Format
        };

        switch (resourceDesc.Dimension)
        {
            case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
                if (resourceDesc.DepthOrArraySize > 1)
                {
                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY;
                    uavDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_UAV
                    {
                        MipSlice = mipSlice,
                        FirstArraySlice = firstArraySlice,
                        ArraySize = resourceDesc.ArraySize() - firstArraySlice
                    };
                }
                else
                {
                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
                    uavDesc.Texture1D = new D3D12_TEX1D_UAV
                    {
                        MipSlice = mipSlice
                    };
                }
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
                if (resourceDesc.DepthOrArraySize > 1)
                {
                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
                    uavDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_UAV
                    {
                        MipSlice = mipSlice,
                        FirstArraySlice = firstArraySlice,
                        ArraySize = resourceDesc.ArraySize() - firstArraySlice,
                        PlaneSlice = planeSlice
                    };
                }
                else
                {
                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
                    uavDesc.Texture2D = new D3D12_TEX2D_UAV
                    {
                        MipSlice = mipSlice,
                        PlaneSlice = planeSlice
                    };
                }
                break;

            case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
                uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
                uavDesc.Texture3D = new D3D12_TEX3D_UAV
                {
                    MipSlice = mipSlice,
                    FirstWSlice = firstArraySlice,
                    WSize = resourceDesc.Depth() - firstArraySlice
                };
                break;

            case D3D12_RESOURCE_DIMENSION_BUFFER:
                uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
                uavDesc.Buffer = new D3D12_BUFFER_UAV
                {
                    FirstElement = 0,
                    NumElements = (uint)(resourceDesc.Width / 4), // Assuming R32_TYPELESS RAW
                    StructureByteStride = 0,
                    Flags = D3D12_BUFFER_UAV_FLAG_RAW
                };
                break;

            default:
                throw new ArgumentException($"Unsupported texture dimension for UAV: {resourceDesc.Dimension}");
        }

        return uavDesc;
    }

    public static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateBufferUavDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
    {
        var resourceDesc = pResource->GetDesc();
        var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
        {
            ViewDimension = D3D12_UAV_DIMENSION_BUFFER,
        };

        if (isRaw)
        {
            uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
            uavDesc.Buffer.FirstElement = 0;
            uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
            uavDesc.Buffer.StructureByteStride = 0;
            uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
        }
        else // Assumes Structured
        {
            uavDesc.Format = resourceDesc.Format;
            uavDesc.Buffer.FirstElement = 0;
            uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
            uavDesc.Buffer.StructureByteStride = stride;
            uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
        }

        return uavDesc;
    }

    public static ResourceViewGroup CreateResourceDescriptor(D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, in ResourceDesc desc, ID3D12Resource* pResource, ResourceViewGroup originalGroup = default)
    {
        var resourceDescriptor = new ResourceViewGroup();
        var resourceDesc = pResource->GetDesc();

        if (desc.Type == ResourceType.Texture)
        {
            ref readonly var textureDesc = ref desc.TextureDescriptor;

            if (textureDesc.Usage.HasFlag(TextureUsage.ShaderResource))
            {
                resourceDescriptor.srv = originalGroup.srv.IsValid ? originalGroup.srv : descriptorAllocator.AllocateCbvSrvUav();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);

                var isCubeMap = textureDesc.Dimension == TextureDimension.TextureCube || textureDesc.Dimension == TextureDimension.TextureCubeArray;
                var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap, textureDesc.Format);

                device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
                descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
            }

            if (textureDesc.Usage.HasFlag(TextureUsage.RenderTarget))
            {
                resourceDescriptor.rtv = originalGroup.rtv.IsValid ? originalGroup.rtv : descriptorAllocator.AllocateRTV();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
                var rtvDesc = CreateRtvDesc(pResource);

                device.NativeObject.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle);
            }

            if (textureDesc.Usage.HasFlag(TextureUsage.DepthStencil))
            {
                resourceDescriptor.dsv = originalGroup.dsv.IsValid ? originalGroup.dsv : descriptorAllocator.AllocateDSV();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
                var dsvDesc = CreateDsvDesc(pResource, 0, 0, D3D12_DSV_FLAG_NONE, textureDesc.Format);

                device.NativeObject.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle);
            }

            if (textureDesc.Usage.HasFlag(TextureUsage.UnorderedAccess))
            {
                resourceDescriptor.uav = originalGroup.uav.IsValid ? originalGroup.uav : descriptorAllocator.AllocateCbvSrvUav();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
                var uavDesc = CreateTextureUavDesc(pResource);

                device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
                descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
            }
        }
        else
        {
            ref readonly var bufferDesc = ref desc.BufferDescriptor;
            var isRaw = bufferDesc.Usage.HasFlag(BufferUsage.Raw);

            if (bufferDesc.Usage.HasFlag(BufferUsage.Constant))
            {
                resourceDescriptor.cbv = originalGroup.cbv.IsValid ? originalGroup.cbv : descriptorAllocator.AllocateCbvSrvUav();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv);
                var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
                {
                    BufferLocation = pResource->GetGPUVirtualAddress(),
                    SizeInBytes = (uint)resourceDesc.Width,
                };

                device.NativeObject.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
                descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv);
            }

            if (bufferDesc.Usage.HasFlag(BufferUsage.ShaderResource))
            {
                resourceDescriptor.srv = originalGroup.srv.IsValid ? originalGroup.srv : descriptorAllocator.AllocateCbvSrvUav();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
                var srvDesc = CreateBufferSrvDesc(pResource, bufferDesc.Stride, isRaw);

                device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
                descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
            }

            if (bufferDesc.Usage.HasFlag(BufferUsage.UnorderedAccess))
            {
                resourceDescriptor.uav = originalGroup.uav.IsValid ? originalGroup.uav : descriptorAllocator.AllocateCbvSrvUav();
                var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
                var uavDesc = CreateBufferUavDesc(pResource, bufferDesc.Stride, isRaw);

                device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
                descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
            }
        }

        return resourceDescriptor;
    }
}
