MoonWorks/src/Graphics/ResourceUploader.cs

382 lines
11 KiB
C#
Raw Normal View History

2024-02-23 08:06:04 +00:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// A convenience structure for creating resources and uploading their data.
2024-02-23 08:06:04 +00:00
///
/// Note that Upload or UploadAndWait must be called after the Create methods for the data to actually be uploaded.
///
/// Note that this structure does not magically keep memory usage down -
/// you may want to stagger uploads over multiple submissions to minimize memory usage.
2024-02-23 08:06:04 +00:00
/// </summary>
public unsafe class ResourceUploader : GraphicsResource
2024-02-23 08:06:04 +00:00
{
2024-03-08 23:29:44 +00:00
TransferBuffer BufferTransferBuffer;
TransferBuffer TextureTransferBuffer;
2024-02-23 08:06:04 +00:00
2024-03-08 23:29:44 +00:00
byte* bufferData;
uint bufferDataOffset = 0;
uint bufferDataSize = 1024;
byte* textureData;
uint textureDataOffset = 0;
uint textureDataSize = 1024;
2024-02-23 08:06:04 +00:00
2024-03-01 23:03:14 +00:00
List<(GpuBuffer, BufferCopy, WriteOptions)> BufferUploads = new List<(GpuBuffer, BufferCopy, WriteOptions)>();
List<(TextureRegion, uint, WriteOptions)> TextureUploads = new List<(TextureRegion, uint, WriteOptions)>();
2024-02-23 08:06:04 +00:00
public ResourceUploader(GraphicsDevice device) : base(device)
2024-02-23 08:06:04 +00:00
{
2024-03-08 23:29:44 +00:00
bufferData = (byte*) NativeMemory.Alloc(bufferDataSize);
textureData = (byte*) NativeMemory.Alloc(textureDataSize);
2024-02-23 08:06:04 +00:00
}
2024-02-24 00:00:29 +00:00
// Buffers
2024-02-23 08:06:04 +00:00
/// <summary>
/// Creates a GpuBuffer with data to be uploaded.
/// </summary>
public GpuBuffer CreateBuffer<T>(Span<T> data, BufferUsageFlags usageFlags) where T : unmanaged
{
var lengthInBytes = (uint) (Marshal.SizeOf<T>() * data.Length);
var gpuBuffer = new GpuBuffer(Device, usageFlags, lengthInBytes);
2024-03-01 23:03:14 +00:00
SetBufferData(gpuBuffer, 0, data, WriteOptions.SafeOverwrite);
2024-02-24 00:00:29 +00:00
return gpuBuffer;
}
/// <summary>
/// Prepares upload of data into a GpuBuffer.
/// </summary>
2024-03-01 23:03:14 +00:00
public void SetBufferData<T>(GpuBuffer buffer, uint bufferOffsetInElements, Span<T> data, WriteOptions option) where T : unmanaged
2024-02-24 00:00:29 +00:00
{
2024-02-27 08:42:53 +00:00
uint elementSize = (uint) Marshal.SizeOf<T>();
uint offsetInBytes = elementSize * bufferOffsetInElements;
uint lengthInBytes = (uint) (elementSize * data.Length);
2024-02-24 00:00:29 +00:00
uint resourceOffset;
2024-02-23 08:06:04 +00:00
fixed (void* spanPtr = data)
{
2024-03-08 23:29:44 +00:00
resourceOffset = CopyBufferData(spanPtr, lengthInBytes);
2024-02-23 08:06:04 +00:00
}
2024-02-27 08:42:53 +00:00
var bufferCopyParams = new BufferCopy(resourceOffset, offsetInBytes, lengthInBytes);
2024-02-29 04:07:19 +00:00
BufferUploads.Add((buffer, bufferCopyParams, option));
2024-02-23 08:06:04 +00:00
}
2024-02-24 00:00:29 +00:00
// Textures
2024-03-01 23:03:14 +00:00
public Texture CreateTexture2D<T>(Span<T> pixelData, uint width, uint height) where T : unmanaged
2024-02-27 08:42:53 +00:00
{
var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
2024-03-01 23:03:14 +00:00
SetTextureData(texture, pixelData, WriteOptions.SafeOverwrite);
2024-02-27 08:42:53 +00:00
return texture;
}
2024-02-23 08:06:04 +00:00
/// <summary>
/// Creates a 2D Texture from compressed image data to be uploaded.
/// </summary>
2024-02-23 22:53:35 +00:00
public Texture CreateTexture2DFromCompressed(Span<byte> compressedImageData)
2024-02-23 08:06:04 +00:00
{
ImageUtils.ImageInfoFromBytes(compressedImageData, out var width, out var height, out var _);
2024-02-23 08:06:04 +00:00
var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
SetTextureDataFromCompressed(texture, compressedImageData);
2024-02-23 08:06:04 +00:00
return texture;
}
/// <summary>
/// Creates a 2D Texture from a compressed image stream to be uploaded.
/// </summary>
2024-02-23 22:53:35 +00:00
public Texture CreateTexture2DFromCompressed(Stream compressedImageStream)
2024-02-23 08:06:04 +00:00
{
var length = compressedImageStream.Length;
2024-02-23 08:06:04 +00:00
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
compressedImageStream.ReadExactly(span);
2024-02-23 08:06:04 +00:00
2024-02-23 22:53:35 +00:00
var texture = CreateTexture2DFromCompressed(span);
2024-02-23 08:06:04 +00:00
NativeMemory.Free(buffer);
return texture;
}
/// <summary>
/// Creates a 2D Texture from a compressed image file to be uploaded.
/// </summary>
2024-02-23 22:53:35 +00:00
public Texture CreateTexture2DFromCompressed(string compressedImageFilePath)
2024-02-23 08:06:04 +00:00
{
var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
2024-02-23 22:53:35 +00:00
return CreateTexture2DFromCompressed(fileStream);
2024-02-23 08:06:04 +00:00
}
/// <summary>
/// Creates a texture from a DDS stream.
/// </summary>
public Texture CreateTextureFromDDS(Stream stream)
{
using var reader = new BinaryReader(stream);
Texture texture;
int faces;
ImageUtils.ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
if (isCube)
{
texture = Texture.CreateTextureCube(Device, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 6;
}
else
{
texture = Texture.CreateTexture2D(Device, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 1;
}
for (int face = 0; face < faces; face += 1)
{
for (int level = 0; level < levels; level += 1)
{
var levelWidth = width >> level;
var levelHeight = height >> level;
var levelSize = ImageUtils.CalculateDDSLevelSize(levelWidth, levelHeight, format);
var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
var byteSpan = new Span<byte>(byteBuffer, levelSize);
stream.ReadExactly(byteSpan);
2024-03-01 07:53:11 +00:00
var textureRegion = new TextureRegion
{
2024-03-01 07:53:11 +00:00
TextureSlice = new TextureSlice
{
Texture = texture,
Layer = (uint) face,
MipLevel = (uint) level
},
X = 0,
Y = 0,
Z = 0,
Width = (uint) levelWidth,
Height = (uint) levelHeight,
Depth = 1
};
2024-03-01 23:03:14 +00:00
SetTextureData(textureRegion, byteSpan, WriteOptions.SafeOverwrite);
NativeMemory.Free(byteBuffer);
}
}
return texture;
}
/// <summary>
/// Creates a texture from a DDS file.
/// </summary>
public Texture CreateTextureFromDDS(string path)
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
return CreateTextureFromDDS(stream);
}
2024-03-01 07:53:11 +00:00
public void SetTextureDataFromCompressed(TextureRegion textureRegion, Span<byte> compressedImageData)
{
var pixelData = ImageUtils.GetPixelDataFromBytes(compressedImageData, out var _, out var _, out var sizeInBytes);
2024-02-23 22:53:35 +00:00
var pixelSpan = new Span<byte>((void*) pixelData, (int) sizeInBytes);
2024-03-01 23:03:14 +00:00
SetTextureData(textureRegion, pixelSpan, WriteOptions.SafeOverwrite);
2024-02-23 22:53:35 +00:00
ImageUtils.FreePixelData(pixelData);
}
2024-03-01 07:53:11 +00:00
public void SetTextureDataFromCompressed(TextureRegion textureRegion, Stream compressedImageStream)
{
var length = compressedImageStream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
compressedImageStream.ReadExactly(span);
2024-03-01 07:53:11 +00:00
SetTextureDataFromCompressed(textureRegion, span);
NativeMemory.Free(buffer);
}
2024-03-01 07:53:11 +00:00
public void SetTextureDataFromCompressed(TextureRegion textureRegion, string compressedImageFilePath)
{
var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
2024-03-01 07:53:11 +00:00
SetTextureDataFromCompressed(textureRegion, fileStream);
}
2024-02-23 22:53:35 +00:00
/// <summary>
/// Prepares upload of pixel data into a TextureSlice.
/// </summary>
2024-03-01 23:03:14 +00:00
public void SetTextureData<T>(TextureRegion textureRegion, Span<T> data, WriteOptions option) where T : unmanaged
2024-02-23 22:53:35 +00:00
{
var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * data.Length);
uint resourceOffset;
fixed (T* dataPtr = data)
{
2024-03-08 23:29:44 +00:00
resourceOffset = CopyTextureData(dataPtr, dataLengthInBytes, Texture.TexelSize(textureRegion.TextureSlice.Texture.Format));
2024-02-23 22:53:35 +00:00
}
2024-03-01 07:53:11 +00:00
TextureUploads.Add((textureRegion, resourceOffset, option));
2024-02-23 22:53:35 +00:00
}
2024-02-24 00:00:29 +00:00
// Upload
2024-02-23 08:06:04 +00:00
/// <summary>
/// Uploads all the data corresponding to the created resources.
/// </summary>
public void Upload()
{
CopyToTransferBuffer();
var commandBuffer = Device.AcquireCommandBuffer();
RecordUploadCommands(commandBuffer);
Device.Submit(commandBuffer);
}
/// <summary>
/// Uploads and then blocks until the upload is finished.
/// This is useful for keeping memory usage down during threaded upload.
/// </summary>
public void UploadAndWait()
{
CopyToTransferBuffer();
var commandBuffer = Device.AcquireCommandBuffer();
RecordUploadCommands(commandBuffer);
var fence = Device.SubmitAndAcquireFence(commandBuffer);
Device.WaitForFences(fence);
Device.ReleaseFence(fence);
}
2024-02-24 00:00:29 +00:00
// Helper methods
private void CopyToTransferBuffer()
2024-02-23 08:06:04 +00:00
{
2024-03-08 23:29:44 +00:00
if (BufferUploads.Count > 0)
2024-02-23 08:06:04 +00:00
{
2024-03-08 23:29:44 +00:00
if (BufferTransferBuffer == null || BufferTransferBuffer.Size < bufferDataSize)
{
BufferTransferBuffer?.Dispose();
BufferTransferBuffer = new TransferBuffer(Device, TransferUsage.Buffer, bufferDataSize);
}
var dataSpan = new Span<byte>(bufferData, (int) bufferDataSize);
BufferTransferBuffer.SetData(dataSpan, TransferOptions.Cycle);
2024-02-23 08:06:04 +00:00
}
2024-03-08 23:29:44 +00:00
if (TextureUploads.Count > 0)
{
if (TextureTransferBuffer == null || TextureTransferBuffer.Size < textureDataSize)
{
TextureTransferBuffer?.Dispose();
TextureTransferBuffer = new TransferBuffer(Device, TransferUsage.Texture, textureDataSize);
}
var dataSpan = new Span<byte>(textureData, (int) textureDataSize);
TextureTransferBuffer.SetData(dataSpan, TransferOptions.Cycle);
}
}
2024-02-23 08:06:04 +00:00
private void RecordUploadCommands(CommandBuffer commandBuffer)
{
2024-02-23 08:06:04 +00:00
commandBuffer.BeginCopyPass();
2024-02-29 04:07:19 +00:00
foreach (var (gpuBuffer, bufferCopyParams, option) in BufferUploads)
2024-02-23 08:06:04 +00:00
{
commandBuffer.UploadToBuffer(
2024-03-08 23:29:44 +00:00
BufferTransferBuffer,
2024-02-23 08:06:04 +00:00
gpuBuffer,
2024-02-29 04:07:19 +00:00
bufferCopyParams,
option
2024-02-23 08:06:04 +00:00
);
}
2024-03-01 07:53:11 +00:00
foreach (var (textureRegion, offset, option) in TextureUploads)
2024-02-23 08:06:04 +00:00
{
commandBuffer.UploadToTexture(
2024-03-08 23:29:44 +00:00
TextureTransferBuffer,
2024-03-01 07:53:11 +00:00
textureRegion,
2024-02-23 08:06:04 +00:00
new BufferImageCopy(
offset,
0,
0
2024-02-29 04:07:19 +00:00
),
option
2024-02-23 08:06:04 +00:00
);
}
commandBuffer.EndCopyPass();
BufferUploads.Clear();
TextureUploads.Clear();
2024-03-08 23:29:44 +00:00
bufferDataOffset = 0;
2024-02-23 08:06:04 +00:00
}
2024-03-08 23:29:44 +00:00
private uint CopyBufferData(void* ptr, uint lengthInBytes)
2024-02-23 08:06:04 +00:00
{
2024-03-08 23:29:44 +00:00
if (bufferDataOffset + lengthInBytes >= bufferDataSize)
2024-02-23 08:06:04 +00:00
{
2024-03-08 23:29:44 +00:00
bufferDataSize = bufferDataOffset + lengthInBytes;
bufferData = (byte*) NativeMemory.Realloc(bufferData, bufferDataSize);
2024-02-23 08:06:04 +00:00
}
2024-03-08 23:29:44 +00:00
var resourceOffset = bufferDataOffset;
2024-03-08 23:29:44 +00:00
NativeMemory.Copy(ptr, bufferData + bufferDataOffset, lengthInBytes);
bufferDataOffset += lengthInBytes;
return resourceOffset;
}
2024-03-08 23:29:44 +00:00
private uint CopyTextureData(void* ptr, uint lengthInBytes, uint alignment)
{
2024-03-08 23:29:44 +00:00
textureDataOffset = RoundToAlignment(textureDataOffset, alignment);
if (textureDataOffset + lengthInBytes >= textureDataSize)
{
textureDataSize = textureDataOffset + lengthInBytes;
textureData = (byte*) NativeMemory.Realloc(textureData, textureDataSize);
}
var resourceOffset = textureDataOffset;
NativeMemory.Copy(ptr, textureData + textureDataOffset, lengthInBytes);
textureDataOffset += lengthInBytes;
return resourceOffset;
}
private uint RoundToAlignment(uint value, uint alignment)
{
return alignment * ((value + alignment - 1) / alignment);
2024-02-23 08:06:04 +00:00
}
2024-02-24 00:00:29 +00:00
// Dispose
/// <summary>
/// It is valid to immediately call Dispose after calling Upload.
/// </summary>
2024-02-23 08:06:04 +00:00
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
2024-03-08 23:29:44 +00:00
BufferTransferBuffer?.Dispose();
TextureTransferBuffer?.Dispose();
2024-02-23 08:06:04 +00:00
}
2024-03-08 23:29:44 +00:00
NativeMemory.Free(bufferData);
2024-02-23 08:06:04 +00:00
}
base.Dispose(disposing);
}
}
}