using Ghost.Graphics.D3D12; 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; /// /// 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 /// 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 cpuData, BindlessDescriptor bindlessDescriptor) : base(width, height, format, in handle, bindlessDescriptor) { Pitch = width * BytesPerPixel; uint alignment; if (BytesPerPixel > 4) { alignment = (uint)MemoryUtilities.AlignOf(); } else { alignment = (uint)MemoryUtilities.AlignOf(); } _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 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); } /// /// Sets the entire texture data on the CPU side. /// /// The texture data to set public void SetData(ReadOnlySpan 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); } /// /// Sets a single pixel value using generic color types. /// /// The color type (e.g., uint for RGBA32) /// X coordinate of the pixel /// Y coordinate of the pixel /// The color value public void SetPixel(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(&color, sizeof(T)); SetPixel(x, y, colorSpan); } /// /// Sets a single pixel value on the CPU side. /// /// X coordinate of the pixel /// Y coordinate of the pixel /// The color data for the pixel public void SetPixel(uint x, uint y, ReadOnlySpan 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); } /// /// Gets a single pixel value as a generic color type. /// /// The color type (e.g., uint for RGBA32) /// X coordinate of the pixel /// Y coordinate of the pixel /// The pixel color value public T GetPixel(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; } } /// /// Gets a single pixel value from the CPU side data. /// /// X coordinate of the pixel /// Y coordinate of the pixel /// The pixel color data public ReadOnlySpan 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); } /// /// Uploads the CPU-side texture data to the GPU resource. /// 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(); } }