Setup entry point + integrated moonworks stuff

This commit is contained in:
2024-07-05 14:32:58 +02:00
parent e7a4a862be
commit 8334a24fd1
116 changed files with 16988 additions and 3 deletions

View File

@ -0,0 +1,88 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// A data container that can be efficiently used by the GPU.
/// </summary>
public class Buffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseBuffer;
public BufferUsageFlags UsageFlags { get; }
/// <summary>
/// Size in bytes.
/// </summary>
public uint Size { get; }
private string name;
public string Name
{
get => name;
set
{
if (Device.DebugMode)
{
Refresh.Refresh_SetBufferName(
Device.Handle,
Handle,
value
);
}
name = value;
}
}
/// <summary>
/// Creates a buffer of appropriate size given a type and element count.
/// </summary>
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
/// <param name="device">The GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
/// <returns></returns>
public unsafe static Buffer Create<T>(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint elementCount
) where T : unmanaged
{
return new Buffer(
device,
usageFlags,
(uint) Marshal.SizeOf<T>() * elementCount
);
}
/// <summary>
/// Creates a buffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
public Buffer(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateBuffer(
device.Handle,
(Refresh.BufferUsageFlags) usageFlags,
sizeInBytes
);
UsageFlags = usageFlags;
Size = sizeInBytes;
name = "";
}
public static implicit operator BufferBinding(Buffer b)
{
return new BufferBinding(b, 0);
}
}

View File

@ -0,0 +1,91 @@
using RefreshCS;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Compute pipelines perform arbitrary parallel processing on input data.
/// </summary>
public class ComputePipeline : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseComputePipeline;
public uint ReadOnlyStorageTextureCount { get; }
public uint ReadOnlyStorageBufferCount { get; }
public uint ReadWriteStorageTextureCount { get; }
public uint ReadWriteStorageBufferCount { get; }
public uint UniformBufferCount { get; }
public ComputePipeline(
GraphicsDevice device,
string filePath,
string entryPointName,
in ComputePipelineCreateInfo computePipelineCreateInfo
) : base(device)
{
using FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Handle = CreateFromStream(device, stream, entryPointName, computePipelineCreateInfo);
ReadOnlyStorageTextureCount = computePipelineCreateInfo.ReadOnlyStorageTextureCount;
ReadOnlyStorageBufferCount = computePipelineCreateInfo.ReadOnlyStorageBufferCount;
ReadWriteStorageTextureCount = computePipelineCreateInfo.ReadWriteStorageTextureCount;
ReadWriteStorageBufferCount = computePipelineCreateInfo.ReadWriteStorageBufferCount;
UniformBufferCount = computePipelineCreateInfo.UniformBufferCount;
}
public ComputePipeline(
GraphicsDevice device,
Stream stream,
string entryPointName,
in ComputePipelineCreateInfo computePipelineCreateInfo
) : base(device)
{
Handle = CreateFromStream(device, stream, entryPointName, computePipelineCreateInfo);
ReadOnlyStorageTextureCount = computePipelineCreateInfo.ReadOnlyStorageTextureCount;
ReadOnlyStorageBufferCount = computePipelineCreateInfo.ReadOnlyStorageBufferCount;
ReadWriteStorageTextureCount = computePipelineCreateInfo.ReadWriteStorageTextureCount;
ReadWriteStorageBufferCount = computePipelineCreateInfo.ReadWriteStorageBufferCount;
UniformBufferCount = computePipelineCreateInfo.UniformBufferCount;
}
private static unsafe nint CreateFromStream(
GraphicsDevice device,
Stream stream,
string entryPointName,
in ComputePipelineCreateInfo computePipelineCreateInfo
) {
byte* bytecodeBuffer = (byte*) NativeMemory.Alloc((nuint) stream.Length);
Span<byte> bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
stream.ReadExactly(bytecodeSpan);
Refresh.ComputePipelineCreateInfo refreshPipelineCreateInfo;
refreshPipelineCreateInfo.Code = bytecodeBuffer;
refreshPipelineCreateInfo.CodeSize = (nuint) stream.Length;
refreshPipelineCreateInfo.EntryPointName = entryPointName;
refreshPipelineCreateInfo.Format = (Refresh.ShaderFormat) computePipelineCreateInfo.ShaderFormat;
refreshPipelineCreateInfo.ReadOnlyStorageTextureCount = computePipelineCreateInfo.ReadOnlyStorageTextureCount;
refreshPipelineCreateInfo.ReadOnlyStorageBufferCount = computePipelineCreateInfo.ReadOnlyStorageBufferCount;
refreshPipelineCreateInfo.ReadWriteStorageTextureCount = computePipelineCreateInfo.ReadWriteStorageTextureCount;
refreshPipelineCreateInfo.ReadWriteStorageBufferCount = computePipelineCreateInfo.ReadWriteStorageBufferCount;
refreshPipelineCreateInfo.UniformBufferCount = computePipelineCreateInfo.UniformBufferCount;
refreshPipelineCreateInfo.ThreadCountX = computePipelineCreateInfo.ThreadCountX;
refreshPipelineCreateInfo.ThreadCountY = computePipelineCreateInfo.ThreadCountY;
refreshPipelineCreateInfo.ThreadCountZ = computePipelineCreateInfo.ThreadCountZ;
IntPtr computePipelineHandle = Refresh.Refresh_CreateComputePipeline(
device.Handle,
refreshPipelineCreateInfo
);
if (computePipelineHandle == nint.Zero)
{
throw new Exception("Could not create compute pipeline!");
}
NativeMemory.Free(bytecodeBuffer);
return computePipelineHandle;
}
}

View File

@ -0,0 +1,25 @@
using System;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Fences allow you to track the status of a submitted command buffer. <br/>
/// You should only acquire a Fence if you will need to track the command buffer. <br/>
/// You should make sure to call GraphicsDevice.ReleaseFence when done with a Fence to avoid memory growth. <br/>
/// The Fence object itself is basically just a wrapper for the Refresh_Fence. <br/>
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
/// </summary>
public class Fence : RefreshResource
{
protected override Action<nint, nint> ReleaseFunction => Refresh.Refresh_ReleaseFence;
internal Fence(GraphicsDevice device) : base(device)
{
}
internal void SetHandle(nint handle)
{
Handle = handle;
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
/// These pipelines are bound before draw calls are issued.
/// </summary>
public class GraphicsPipeline : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseGraphicsPipeline;
public Shader VertexShader;
public Shader FragmentShader;
public SampleCount SampleCount { get; }
#if DEBUG
internal GraphicsPipelineAttachmentInfo AttachmentInfo { get; }
#endif
public unsafe GraphicsPipeline(
GraphicsDevice device,
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
) : base(device)
{
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
Refresh.VertexAttribute* vertexAttributes = (Refresh.VertexAttribute*) NativeMemory.Alloc(
(nuint) (graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length * Marshal.SizeOf<Refresh.VertexAttribute>())
);
for (int i = 0; i < graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length; i += 1)
{
vertexAttributes[i] = graphicsPipelineCreateInfo.VertexInputState.VertexAttributes[i].ToRefresh();
}
Refresh.VertexBinding* vertexBindings = (Refresh.VertexBinding*) NativeMemory.Alloc(
(nuint) (graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length * Marshal.SizeOf<Refresh.VertexBinding>())
);
for (int i = 0; i < graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length; i += 1)
{
vertexBindings[i] = graphicsPipelineCreateInfo.VertexInputState.VertexBindings[i].ToRefresh();
}
Refresh.ColorAttachmentDescription* colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length
];
for (int i = 0; i < graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
{
colorAttachmentDescriptions[i].Format = (Refresh.TextureFormat) graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[i].Format;
colorAttachmentDescriptions[i].BlendState = graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
}
refreshGraphicsPipelineCreateInfo.VertexShader = graphicsPipelineCreateInfo.VertexShader.Handle;
refreshGraphicsPipelineCreateInfo.FragmentShader = graphicsPipelineCreateInfo.FragmentShader.Handle;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexAttributes = vertexAttributes;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexAttributeCount = (uint) graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexBindings = vertexBindings;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexBindingCount = (uint) graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length;
refreshGraphicsPipelineCreateInfo.PrimitiveType = (Refresh.PrimitiveType) graphicsPipelineCreateInfo.PrimitiveType;
refreshGraphicsPipelineCreateInfo.RasterizerState = graphicsPipelineCreateInfo.RasterizerState.ToRefresh();
refreshGraphicsPipelineCreateInfo.MultisampleState = graphicsPipelineCreateInfo.MultisampleState.ToRefresh();
refreshGraphicsPipelineCreateInfo.DepthStencilState = graphicsPipelineCreateInfo.DepthStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentCount = (uint) graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length;
refreshGraphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions = colorAttachmentDescriptions;
refreshGraphicsPipelineCreateInfo.AttachmentInfo.DepthStencilFormat = (Refresh.TextureFormat) graphicsPipelineCreateInfo.AttachmentInfo.DepthStencilFormat;
refreshGraphicsPipelineCreateInfo.AttachmentInfo.HasDepthStencilAttachment = Conversions.BoolToInt(graphicsPipelineCreateInfo.AttachmentInfo.HasDepthStencilAttachment);
refreshGraphicsPipelineCreateInfo.BlendConstants[0] = graphicsPipelineCreateInfo.BlendConstants.R;
refreshGraphicsPipelineCreateInfo.BlendConstants[1] = graphicsPipelineCreateInfo.BlendConstants.G;
refreshGraphicsPipelineCreateInfo.BlendConstants[2] = graphicsPipelineCreateInfo.BlendConstants.B;
refreshGraphicsPipelineCreateInfo.BlendConstants[3] = graphicsPipelineCreateInfo.BlendConstants.A;
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
if (Handle == IntPtr.Zero)
{
throw new Exception("Could not create graphics pipeline!");
}
NativeMemory.Free(vertexAttributes);
NativeMemory.Free(vertexBindings);
VertexShader = graphicsPipelineCreateInfo.VertexShader;
FragmentShader = graphicsPipelineCreateInfo.FragmentShader;
SampleCount = graphicsPipelineCreateInfo.MultisampleState.MultisampleCount;
#if DEBUG
AttachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
#endif
}
}

View File

@ -0,0 +1,23 @@
using System;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Specifies how a texture will be sampled in a shader.
/// </summary>
public class Sampler : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseSampler;
public Sampler(
GraphicsDevice device,
in SamplerCreateInfo samplerCreateInfo
) : base(device)
{
Handle = Refresh.Refresh_CreateSampler(
device.Handle,
samplerCreateInfo.ToRefresh()
);
}
}

View File

@ -0,0 +1,91 @@
using RefreshCS;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Shaders are used to create graphics pipelines.
/// Graphics pipelines take a vertex shader and a fragment shader.
/// </summary>
public class Shader : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseShader;
public uint SamplerCount { get; }
public uint StorageTextureCount { get; }
public uint StorageBufferCount { get; }
public uint UniformBufferCount { get; }
public unsafe Shader(
GraphicsDevice device,
string filePath,
string entryPointName,
in ShaderCreateInfo shaderCreateInfo
) : base(device)
{
using FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Handle = CreateFromStream(
device,
stream,
entryPointName,
shaderCreateInfo
);
SamplerCount = shaderCreateInfo.SamplerCount;
StorageTextureCount = shaderCreateInfo.StorageTextureCount;
StorageBufferCount = shaderCreateInfo.StorageBufferCount;
UniformBufferCount = shaderCreateInfo.UniformBufferCount;
}
public unsafe Shader(
GraphicsDevice device,
Stream stream,
string entryPointName,
in ShaderCreateInfo shaderCreateInfo
) : base(device)
{
Handle = CreateFromStream(
device,
stream,
entryPointName,
shaderCreateInfo
);
SamplerCount = shaderCreateInfo.SamplerCount;
StorageTextureCount = shaderCreateInfo.StorageTextureCount;
StorageBufferCount = shaderCreateInfo.StorageBufferCount;
UniformBufferCount = shaderCreateInfo.UniformBufferCount;
}
private static unsafe IntPtr CreateFromStream(
GraphicsDevice device,
Stream stream,
string entryPointName,
in ShaderCreateInfo shaderCreateInfo
) {
void* bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
Span<byte> bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
stream.ReadExactly(bytecodeSpan);
Refresh.ShaderCreateInfo refreshShaderCreateInfo;
refreshShaderCreateInfo.CodeSize = (nuint) stream.Length;
refreshShaderCreateInfo.Code = (byte*) bytecodeBuffer;
refreshShaderCreateInfo.EntryPointName = entryPointName;
refreshShaderCreateInfo.Stage = (Refresh.ShaderStage) shaderCreateInfo.ShaderStage;
refreshShaderCreateInfo.Format = (Refresh.ShaderFormat) shaderCreateInfo.ShaderFormat;
refreshShaderCreateInfo.SamplerCount = shaderCreateInfo.SamplerCount;
refreshShaderCreateInfo.StorageTextureCount = shaderCreateInfo.StorageTextureCount;
refreshShaderCreateInfo.StorageBufferCount = shaderCreateInfo.StorageBufferCount;
refreshShaderCreateInfo.UniformBufferCount = shaderCreateInfo.UniformBufferCount;
IntPtr shaderModule = Refresh.Refresh_CreateShader(
device.Handle,
refreshShaderCreateInfo
);
NativeMemory.Free(bytecodeBuffer);
return shaderModule;
}
}

View File

@ -0,0 +1,326 @@
using System;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// A multi-dimensional data container that can be efficiently used by the GPU.
/// </summary>
public class Texture : RefreshResource
{
public uint Width { get; internal set; }
public uint Height { get; internal set; }
public uint Depth { get; }
public TextureFormat Format { get; internal set; }
public bool IsCube { get; }
public uint LayerCount { get; }
public uint LevelCount { get; }
public SampleCount SampleCount { get; }
public TextureUsageFlags UsageFlags { get; }
public uint Size { get; }
private string name;
public string Name
{
get => name;
set
{
if (Device.DebugMode)
{
Refresh.Refresh_SetTextureName(
Device.Handle,
Handle,
value
);
}
name = value;
}
}
// FIXME: this allocates a delegate instance
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTexture;
/// <summary>
/// Creates a 2D texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="format">The format of the texture.</param>
/// <param name="usageFlags">Specifies how the texture will be used.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture2D(
GraphicsDevice device,
uint width,
uint height,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1,
SampleCount sampleCount = SampleCount.One
) {
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = 1,
IsCube = false,
LayerCount = 1,
LevelCount = levelCount,
SampleCount = sampleCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a 2D texture array.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="layerCount">The layer count of the texture.</param>
/// <param name="format">The format of the texture.</param>
/// <param name="usageFlags">Specifies how the texture will be used.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture2DArray(
GraphicsDevice device,
uint width,
uint height,
uint layerCount,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1
) {
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = 1,
IsCube = false,
LayerCount = layerCount,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a 3D texture.
/// Note that the width, height and depth all form one slice and cannot be subdivided in a texture slice.
/// </summary>
public static Texture CreateTexture3D(
GraphicsDevice device,
uint width,
uint height,
uint depth,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1
) {
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = depth,
IsCube = false,
LayerCount = 1,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a cube texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="size">The length of one side of the cube.</param>
/// <param name="format">The format of the texture.</param>
/// <param name="usageFlags">Specifies how the texture will be used.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTextureCube(
GraphicsDevice device,
uint size,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1
) {
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
{
Width = size,
Height = size,
Depth = 1,
IsCube = true,
LayerCount = 6,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a new texture using a TextureCreateInfo struct.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="textureCreateInfo">The parameters to use when creating the texture.</param>
public Texture(
GraphicsDevice device,
in TextureCreateInfo textureCreateInfo
) : base(device)
{
Handle = Refresh.Refresh_CreateTexture(
device.Handle,
textureCreateInfo.ToRefresh()
);
Format = textureCreateInfo.Format;
Width = textureCreateInfo.Width;
Height = textureCreateInfo.Height;
Depth = textureCreateInfo.Depth;
IsCube = textureCreateInfo.IsCube;
LayerCount = textureCreateInfo.LayerCount;
LevelCount = textureCreateInfo.LevelCount;
SampleCount = textureCreateInfo.SampleCount;
UsageFlags = textureCreateInfo.UsageFlags;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
name = "";
}
// Used by Window. Swapchain texture handles are managed by the driver backend.
internal Texture(
GraphicsDevice device,
TextureFormat format
) : base(device)
{
Handle = IntPtr.Zero;
Format = format;
Width = 0;
Height = 0;
Depth = 1;
IsCube = false;
LevelCount = 1;
SampleCount = SampleCount.One;
UsageFlags = TextureUsageFlags.ColorTarget;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
}
public static uint BytesPerPixel(TextureFormat format)
{
switch (format)
{
case TextureFormat.R8:
case TextureFormat.R8_UINT:
return 1;
case TextureFormat.R5G6B5:
case TextureFormat.B4G4R4A4:
case TextureFormat.A1R5G5B5:
case TextureFormat.R16_SFLOAT:
case TextureFormat.R8G8_SNORM:
case TextureFormat.R8G8_UINT:
case TextureFormat.R16_UINT:
case TextureFormat.D16_UNORM:
return 2;
case TextureFormat.R8G8B8A8:
case TextureFormat.B8G8R8A8:
case TextureFormat.R32_SFLOAT:
case TextureFormat.R16G16:
case TextureFormat.R16G16_SFLOAT:
case TextureFormat.R8G8B8A8_SNORM:
case TextureFormat.A2R10G10B10:
case TextureFormat.R8G8B8A8_UINT:
case TextureFormat.R16G16_UINT:
case TextureFormat.D24_UNORM_S8_UINT:
case TextureFormat.D32_SFLOAT:
return 4;
case TextureFormat.D32_SFLOAT_S8_UINT:
return 5;
case TextureFormat.R16G16B16A16_SFLOAT:
case TextureFormat.R16G16B16A16:
case TextureFormat.R32G32_SFLOAT:
case TextureFormat.R16G16B16A16_UINT:
case TextureFormat.BC1:
return 8;
case TextureFormat.R32G32B32A32_SFLOAT:
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
default:
Log.Error("Texture format not recognized!");
return 0;
}
}
public static uint TexelSize(TextureFormat format)
{
switch (format)
{
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
case TextureFormat.BC1:
return 8;
default:
return 1;
}
}
public static uint BlockSizeSquared(TextureFormat format)
{
switch (format)
{
case TextureFormat.BC1:
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
case TextureFormat.R8G8B8A8:
case TextureFormat.B8G8R8A8:
case TextureFormat.R5G6B5:
case TextureFormat.A1R5G5B5:
case TextureFormat.B4G4R4A4:
case TextureFormat.A2R10G10B10:
case TextureFormat.R16G16:
case TextureFormat.R16G16B16A16:
case TextureFormat.R8:
case TextureFormat.R8G8_SNORM:
case TextureFormat.R8G8B8A8_SNORM:
case TextureFormat.R16_SFLOAT:
case TextureFormat.R16G16_SFLOAT:
case TextureFormat.R16G16B16A16_SFLOAT:
case TextureFormat.R32_SFLOAT:
case TextureFormat.R32G32_SFLOAT:
case TextureFormat.R32G32B32A32_SFLOAT:
case TextureFormat.R8_UINT:
case TextureFormat.R8G8_UINT:
case TextureFormat.R8G8B8A8_UINT:
case TextureFormat.R16_UINT:
case TextureFormat.R16G16_UINT:
case TextureFormat.R16G16B16A16_UINT:
case TextureFormat.D16_UNORM:
case TextureFormat.D24_UNORM:
case TextureFormat.D24_UNORM_S8_UINT:
case TextureFormat.D32_SFLOAT:
case TextureFormat.D32_SFLOAT_S8_UINT:
return 1;
default:
Log.Error("Texture format not recognized!");
return 0;
}
}
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
public static implicit operator TextureRegion(Texture t) => new TextureRegion(t);
}

View File

@ -0,0 +1,209 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// A data container that can efficiently transfer data to and from the GPU.
/// </summary>
public unsafe class TransferBuffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTransferBuffer;
/// <summary>
/// Size in bytes.
/// </summary>
public uint Size { get; }
#if DEBUG
public bool Mapped { get; private set; }
#endif
/// <summary>
/// Creates a buffer of requested size given a type and element count.
/// </summary>
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
/// <param name="device">The GraphicsDevice.</param>
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
/// <returns></returns>
public unsafe static TransferBuffer Create<T>(
GraphicsDevice device,
TransferBufferUsage usage,
uint elementCount
) where T : unmanaged
{
return new TransferBuffer(
device,
usage,
(uint) Marshal.SizeOf<T>() * elementCount
);
}
/// <summary>
/// Creates a TransferBuffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="usage">Whether this will be used to upload buffers or textures.</param>
/// <param name="sizeInBytes">The length of the buffer. Cannot be resized.</param>
public TransferBuffer(
GraphicsDevice device,
TransferBufferUsage usage,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateTransferBuffer(
device.Handle,
(Refresh.TransferBufferUsage) usage,
sizeInBytes
);
Size = sizeInBytes;
}
/// <summary>
/// Immediately copies data from a Span to the TransferBuffer.
/// Returns the length of the copy in bytes.
///
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
/// that command will still use the correct data at the cost of increased memory usage.
///
/// If cycle is set to false, the data will be overwritten immediately,
/// which could cause a data race.
/// </summary>
public unsafe uint SetData<T>(
Span<T> source,
uint bufferOffsetInBytes,
bool cycle
) where T : unmanaged
{
int elementSize = Marshal.SizeOf<T>();
uint dataLengthInBytes = (uint) (elementSize * source.Length);
#if DEBUG
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
AssertNotMapped();
#endif
fixed (T* dataPtr = source)
{
Refresh.Refresh_SetTransferData(
Device.Handle,
(nint) dataPtr,
new Refresh.TransferBufferRegion
{
TransferBuffer = Handle,
Offset = bufferOffsetInBytes,
Size = dataLengthInBytes
},
Conversions.BoolToInt(cycle)
);
}
return dataLengthInBytes;
}
/// <summary>
/// Immediately copies data from a Span to the TransferBuffer.
/// Returns the length of the copy in bytes.
///
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
/// that command will still use the corret data at the cost of increased memory usage.
///
/// If cycle is set to false, the data will be overwritten immediately,
/// which could cause a data race.
/// </summary>
public unsafe uint SetData<T>(
Span<T> source,
bool cycle
) where T : unmanaged
{
return SetData(source, 0, cycle);
}
/// <summary>
/// Immediately copies data from the TransferBuffer into a Span.
/// </summary>
public unsafe void GetData<T>(
Span<T> destination,
uint bufferOffsetInBytes = 0
) where T : unmanaged
{
int elementSize = Marshal.SizeOf<T>();
uint dataLengthInBytes = (uint) (elementSize * destination.Length);
#if DEBUG
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
AssertNotMapped();
#endif
fixed (T* dataPtr = destination)
{
Refresh.Refresh_GetTransferData(
Device.Handle,
new Refresh.TransferBufferRegion
{
TransferBuffer = Handle,
Offset = bufferOffsetInBytes,
Size = dataLengthInBytes
},
(nint) dataPtr
);
}
}
/// <summary>
/// Maps the transfer buffer into application address space.
/// You must call Unmap before encoding transfer commands.
/// </summary>
public unsafe void Map(bool cycle, out byte* data)
{
#if DEBUG
AssertNotMapped();
#endif
Refresh.Refresh_MapTransferBuffer(
Device.Handle,
Handle,
Conversions.BoolToInt(cycle),
out data
);
#if DEBUG
Mapped = true;
#endif
}
/// <summary>
/// Unmaps the transfer buffer.
/// The pointer given by Map is no longer valid.
/// </summary>
public void Unmap()
{
Refresh.Refresh_UnmapTransferBuffer(
Device.Handle,
Handle
);
#if DEBUG
Mapped = false;
#endif
}
#if DEBUG
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
{
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
{
throw new InvalidOperationException($"Data overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
}
}
private void AssertNotMapped()
{
if (Mapped)
{
throw new InvalidOperationException("Transfer buffer must not be mapped!");
}
}
#endif
}