starting graphics layer restructuring

sdl2_gpu
cosmonaut 2024-06-04 10:20:07 -07:00
parent cc7cae9d6b
commit 8b35b6b0b7
10 changed files with 2378 additions and 2366 deletions

6
.gitmodules vendored
View File

@ -4,12 +4,12 @@
[submodule "lib/FAudio"] [submodule "lib/FAudio"]
path = lib/FAudio path = lib/FAudio
url = https://github.com/FNA-XNA/FAudio.git url = https://github.com/FNA-XNA/FAudio.git
[submodule "lib/RefreshCS"]
path = lib/RefreshCS
url = https://gitea.moonside.games/MoonsideGames/RefreshCS.git
[submodule "lib/WellspringCS"] [submodule "lib/WellspringCS"]
path = lib/WellspringCS path = lib/WellspringCS
url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
[submodule "lib/dav1dfile"] [submodule "lib/dav1dfile"]
path = lib/dav1dfile path = lib/dav1dfile
url = https://github.com/MoonsideGames/dav1dfile.git url = https://github.com/MoonsideGames/dav1dfile.git
[submodule "SDL2_gpuCS"]
path = lib/SDL2_gpuCS
url = https://github.com/MoonsideGames/SDL2_gpuCS.git

View File

@ -11,7 +11,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="lib\FAudio\csharp\FAudio.cs" /> <Compile Include="lib\FAudio\csharp\FAudio.cs" />
<Compile Include="lib\RefreshCS\src\Refresh.cs" /> <Compile Include="lib\SDL2_gpuCS\SDL2_gpuCS.cs" />
<Compile Include="lib\SDL2-CS\src\SDL2.cs" /> <Compile Include="lib\SDL2-CS\src\SDL2.cs" />
<Compile Include="lib\WellspringCS\WellspringCS.cs" /> <Compile Include="lib\WellspringCS\WellspringCS.cs" />
<Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" /> <Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" />

@ -1 +0,0 @@
Subproject commit b92ecb60e3f60c3cce694477cbde381449759c58

1
lib/SDL2_gpuCS Submodule

@ -0,0 +1 @@
Subproject commit b3cf918f900a24d131b4eb130a87dd12b4e4ca8c

View File

@ -1,21 +1,19 @@
using RefreshCS; using SDL2_gpuCS;
namespace MoonWorks.Graphics;
namespace MoonWorks.Graphics /// <summary>
{ /// A buffer-offset pair to be used when binding buffers.
/// <summary> /// </summary>
/// A buffer-offset pair to be used when binding vertex or index buffers. public readonly record struct BufferBinding(
/// </summary> GpuBuffer Buffer,
public readonly record struct BufferBinding( uint Offset
GpuBuffer Buffer, ) {
uint Offset public SDL_Gpu.BufferBinding ToRefresh()
) { {
public Refresh.BufferBinding ToRefresh() return new SDL_Gpu.BufferBinding
{ {
return new Refresh.BufferBinding Buffer = Buffer.Handle,
{ Offset = Offset
gpuBuffer = Buffer.Handle, };
offset = Offset
};
}
} }
} }

View File

@ -1,35 +1,34 @@
using RefreshCS; using SDL2_gpuCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics;
{
/// <summary> /// <summary>
/// Binding specification to be used when binding buffers for compute shaders. /// Binding specification to be used when binding buffers for compute shaders.
/// </summary> /// </summary>
/// <param name="GpuBuffer">The GpuBuffer to bind.</param> /// <param name="GpuBuffer">The GpuBuffer to bind.</param>
/// <param name="WriteOption"> /// <param name="WriteOption">
/// Specifies data dependency behavior when this buffer is written to in the shader. <br/> /// Specifies data dependency behavior when this buffer is written to in the shader. <br/>
/// ///
/// Cycle: /// Cycle:
/// If this buffer has been used in commands that have not finished, /// If this buffer has been used in commands that have not finished,
/// the implementation may choose to prevent a dependency on those commands /// the implementation may choose to prevent a dependency on those commands
/// at the cost of increased memory usage. /// at the cost of increased memory usage.
/// You may NOT assume that any of the previous data is retained. /// You may NOT assume that any of the previous data is retained.
/// This may prevent stalls when frequently updating a resource. <br /> /// This may prevent stalls when frequently updating a resource. <br />
/// ///
/// SafeOverwrite: /// SafeOverwrite:
/// Overwrites the data safely using a GPU memory barrier. /// Overwrites the data safely using a GPU memory barrier.
/// </param> /// </param>
public readonly record struct ComputeBufferBinding( public readonly record struct ComputeBufferBinding(
GpuBuffer GpuBuffer, GpuBuffer GpuBuffer,
WriteOptions WriteOption WriteOptions WriteOption
) { ) {
public Refresh.ComputeBufferBinding ToRefresh() public SDL_Gpu.BufferBinding ToRefresh()
{
return new Refresh.ComputeBufferBinding
{ {
return new Refresh.ComputeBufferBinding gpuBuffer = GpuBuffer.Handle,
{ writeOption = (Refresh.WriteOptions) WriteOption
gpuBuffer = GpuBuffer.Handle, };
writeOption = (Refresh.WriteOptions) WriteOption
};
}
} }
} }

View File

@ -1,4 +1,4 @@
using RefreshCS; using SDL2_gpuCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
{ {
@ -9,12 +9,12 @@ namespace MoonWorks.Graphics
Texture Texture, Texture Texture,
Sampler Sampler Sampler Sampler
) { ) {
public Refresh.TextureSamplerBinding ToRefresh() public SDL_Gpu.TextureSamplerBinding ToSDL()
{ {
return new Refresh.TextureSamplerBinding return new SDL_Gpu.TextureSamplerBinding
{ {
texture = Texture.Handle, Texture = Texture.Handle,
sampler = Sampler.Handle Sampler = Sampler.Handle
}; };
} }
} }

File diff suppressed because it is too large Load Diff

485
src/Graphics/RenderPass.cs Normal file
View File

@ -0,0 +1,485 @@
using System;
using System.Runtime.InteropServices;
using SDL2_gpuCS;
namespace MoonWorks.Graphics;
/// <summary>
/// Render passes are begun in command buffers and are used to apply render state and issue draw calls.
/// Render passes are pooled and should not be referenced after calling EndRenderPass.
/// </summary>
public class RenderPass
{
public nint Handle { get; internal set; }
#if DEBUG
internal bool active;
GraphicsPipeline currentGraphicsPipeline;
uint colorAttachmentCount;
SampleCount colorAttachmentSampleCount;
TextureFormat colorFormatOne;
TextureFormat colorFormatTwo;
TextureFormat colorFormatThree;
TextureFormat colorFormatFour;
bool hasDepthStencilAttachment;
SampleCount depthStencilAttachmentSampleCount;
TextureFormat depthStencilFormat;
#endif
internal void SetHandle(nint handle)
{
Handle = handle;
}
/// <summary>
/// Binds a graphics pipeline so that rendering work may be performed.
/// </summary>
/// <param name="graphicsPipeline">The graphics pipeline to bind.</param>
public void BindGraphicsPipeline(
GraphicsPipeline graphicsPipeline
) {
#if DEBUG
AssertRenderPassActive();
AssertRenderPassPipelineFormatMatch(graphicsPipeline);
if (colorAttachmentCount > 0)
{
if (graphicsPipeline.SampleCount != colorAttachmentSampleCount)
{
throw new System.InvalidOperationException($"Sample count mismatch! Graphics pipeline sample count: {graphicsPipeline.SampleCount}, Color attachment sample count: {colorAttachmentSampleCount}");
}
}
if (hasDepthStencilAttachment)
{
if (graphicsPipeline.SampleCount != depthStencilAttachmentSampleCount)
{
throw new System.InvalidOperationException($"Sample count mismatch! Graphics pipeline sample count: {graphicsPipeline.SampleCount}, Depth stencil attachment sample count: {depthStencilAttachmentSampleCount}");
}
}
#endif
SDL_Gpu.SDL_GpuBindGraphicsPipeline(
Handle,
graphicsPipeline.Handle
);
#if DEBUG
currentGraphicsPipeline = graphicsPipeline;
#endif
}
/// <summary>
/// Sets the viewport.
/// </summary>
public void SetViewport(in Viewport viewport)
{
#if DEBUG
AssertRenderPassActive();
#endif
SDL_Gpu.SDL_GpuSetViewport(
Handle,
viewport.ToRefresh()
);
}
/// <summary>
/// Sets the scissor area.
/// </summary>
public void SetScissor(in Rect scissor)
{
#if DEBUG
AssertRenderPassActive();
if (scissor.X < 0 || scissor.Y < 0 || scissor.W <= 0 || scissor.H <= 0)
{
throw new System.ArgumentOutOfRangeException("Scissor position cannot be negative and dimensions must be positive!");
}
#endif
SDL_Gpu.SDL_GpuSetScissor(
Handle,
scissor.ToRefresh()
);
}
/// <summary>
/// Binds vertex buffers to be used by subsequent draw calls.
/// </summary>
/// <param name="bufferBinding">Buffer to bind and associated offset.</param>
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
public unsafe void BindVertexBuffers(
in BufferBinding bufferBinding,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
var bindingArray = stackalloc SDL_Gpu.BufferBinding[1];
bindingArray[0] = bufferBinding.ToRefresh();
SDL_Gpu.SDL_GpuBindVertexBuffers(
Handle,
firstBinding,
bindingArray,
1
);
}
/// <summary>
/// Binds vertex buffers to be used by subsequent draw calls.
/// </summary>
/// <param name="bufferBindingOne">Buffer to bind and associated offset.</param>
/// <param name="bufferBindingTwo">Buffer to bind and associated offset.</param>
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
public unsafe void BindVertexBuffers(
in BufferBinding bufferBindingOne,
in BufferBinding bufferBindingTwo,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
var bindingArray = stackalloc SDL_Gpu.BufferBinding[2];
bindingArray[0] = bufferBindingOne.ToRefresh();
bindingArray[1] = bufferBindingTwo.ToRefresh();
SDL_Gpu.SDL_GpuBindVertexBuffers(
Handle,
firstBinding,
bindingArray,
2
);
}
/// <summary>
/// Binds vertex buffers to be used by subsequent draw calls.
/// </summary>
/// <param name="bufferBindingOne">Buffer to bind and associated offset.</param>
/// <param name="bufferBindingTwo">Buffer to bind and associated offset.</param>
/// <param name="bufferBindingThree">Buffer to bind and associated offset.</param>
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
public unsafe void BindVertexBuffers(
in BufferBinding bufferBindingOne,
in BufferBinding bufferBindingTwo,
in BufferBinding bufferBindingThree,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
var bindingArray = stackalloc SDL_Gpu.BufferBinding[3];
bindingArray[0] = bufferBindingOne.ToRefresh();
bindingArray[1] = bufferBindingTwo.ToRefresh();
bindingArray[2] = bufferBindingThree.ToRefresh();
SDL_Gpu.SDL_GpuBindVertexBuffers(
Handle,
firstBinding,
bindingArray,
3
);
}
/// <summary>
/// Binds vertex buffers to be used by subsequent draw calls.
/// </summary>
/// <param name="bufferBindingOne">Buffer to bind and associated offset.</param>
/// <param name="bufferBindingTwo">Buffer to bind and associated offset.</param>
/// <param name="bufferBindingThree">Buffer to bind and associated offset.</param>
/// <param name="bufferBindingFour">Buffer to bind and associated offset.</param>
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
public unsafe void BindVertexBuffers(
in BufferBinding bufferBindingOne,
in BufferBinding bufferBindingTwo,
in BufferBinding bufferBindingThree,
in BufferBinding bufferBindingFour,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
var bindingArray = stackalloc SDL_Gpu.BufferBinding[4];
bindingArray[0] = bufferBindingOne.ToRefresh();
bindingArray[1] = bufferBindingTwo.ToRefresh();
bindingArray[2] = bufferBindingThree.ToRefresh();
bindingArray[3] = bufferBindingFour.ToRefresh();
SDL_Gpu.SDL_GpuBindVertexBuffers(
Handle,
firstBinding,
bindingArray,
4
);
}
/// <summary>
/// Binds vertex buffers to be used by subsequent draw calls.
/// </summary>
/// <param name="bufferBindings">Spawn of buffers to bind and their associated offsets.</param>
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
public unsafe void BindVertexBuffers(
in Span<BufferBinding> bufferBindings,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
SDL_Gpu.BufferBinding* bufferBindingsArray = (SDL_Gpu.BufferBinding*) NativeMemory.Alloc((nuint) (Marshal.SizeOf<SDL_Gpu.BufferBinding>() * bufferBindings.Length));
for (var i = 0; i < bufferBindings.Length; i += 1)
{
bufferBindingsArray[i] = bufferBindings[i].ToRefresh();
}
SDL_Gpu.SDL_GpuBindVertexBuffers(
Handle,
firstBinding,
bufferBindingsArray,
(uint) bufferBindings.Length
);
NativeMemory.Free(bufferBindingsArray);
}
/// <summary>
/// Binds an index buffer to be used by subsequent draw calls.
/// </summary>
/// <param name="indexBuffer">The index buffer to bind.</param>
/// <param name="indexElementSize">The size in bytes of the index buffer elements.</param>
/// <param name="offset">The offset index for the buffer.</param>
public void BindIndexBuffer(
BufferBinding bufferBinding,
IndexElementSize indexElementSize
)
{
#if DEBUG
AssertGraphicsPipelineBound();
#endif
SDL_Gpu.SDL_GpuBindIndexBuffer(
Handle,
bufferBinding.ToRefresh(),
(SDL_Gpu.IndexElementSize) indexElementSize
);
}
/// <summary>
/// Binds samplers to be used by the vertex shader.
/// </summary>
/// <param name="textureSamplerBindings">The texture-sampler to bind.</param>
public unsafe void BindVertexSampler(
in TextureSamplerBinding textureSamplerBinding,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertTextureSamplerBindingNonNull(textureSamplerBinding);
AssertTextureHasSamplerFlag(textureSamplerBinding.Texture);
#endif
var bindingArray = stackalloc SDL_Gpu.TextureSamplerBinding[1];
bindingArray[0] = textureSamplerBinding.ToSDL();
SDL_Gpu.SDL_GpuBindVertexSamplers(
Handle,
slot,
bindingArray,
1
);
}
public unsafe void BindVertexSamplers(
in Span<TextureSamplerBinding> textureSamplerBindings,
uint firstSlot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
for (var i = 0; i < textureSamplerBindings.Length; i += 1)
{
AssertTextureSamplerBindingNonNull(textureSamplerBindings[i]);
AssertTextureHasSamplerFlag(textureSamplerBindings[i].Texture);
}
#endif
SDL_Gpu.TextureSamplerBinding* samplerBindingsArray =
(SDL_Gpu.TextureSamplerBinding*) NativeMemory.Alloc(
(nuint) (Marshal.SizeOf<SDL_Gpu.TextureSamplerBinding>() * textureSamplerBindings.Length)
);
for (var i = 0; i < textureSamplerBindings.Length; i += 1)
{
samplerBindingsArray[i] = textureSamplerBindings[i].ToSDL();
}
SDL_Gpu.SDL_GpuBindVertexSamplers(
Handle,
firstSlot,
samplerBindingsArray,
(uint) textureSamplerBindings.Length
);
NativeMemory.Free(samplerBindingsArray);
}
public unsafe void BindVertexStorageTexture(
in TextureSlice storageTextureSlice,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertTextureNonNull(storageTextureSlice.Texture);
AssertTextureHasGraphicsStorageFlag(storageTextureSlice.Texture);
#endif
var sliceArray = stackalloc SDL_Gpu.TextureSlice[1];
sliceArray[0] = storageTextureSlice.ToSDL();
SDL_Gpu.SDL_GpuBindVertexStorageTextures(
Handle,
slot,
sliceArray,
1
);
}
public unsafe void BindVertexStorageTextures(
in Span<TextureSlice> storageTextureSlices,
uint firstSlot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
for (var i = 0; i < storageTextureSlices.Length; i += 1)
{
AssertTextureNonNull(storageTextureSlices[i].Texture);
AssertTextureHasGraphicsStorageFlag(storageTextureSlices[i].Texture);
}
#endif
SDL_Gpu.TextureSlice* sliceArray =
(SDL_Gpu.TextureSlice*) NativeMemory.Alloc(
(nuint) (Marshal.SizeOf<SDL_Gpu.TextureSlice>() * storageTextureSlices.Length)
);
for (var i = 0; i < storageTextureSlices.Length; i += 1)
{
sliceArray[i] = storageTextureSlices[i].ToSDL();
}
SDL_Gpu.SDL_GpuBindVertexStorageTextures(
Handle,
firstSlot,
sliceArray,
(uint) storageTextureSlices.Length
);
NativeMemory.Free(sliceArray);
}
#if DEBUG
private void AssertRenderPassActive(string message = "Render pass is not active!")
{
if (!active)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertRenderPassPipelineFormatMatch(GraphicsPipeline graphicsPipeline)
{
for (var i = 0; i < graphicsPipeline.AttachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
{
TextureFormat format;
if (i == 0)
{
format = colorFormatOne;
}
else if (i == 1)
{
format = colorFormatTwo;
}
else if (i == 2)
{
format = colorFormatThree;
}
else
{
format = colorFormatFour;
}
var pipelineFormat = graphicsPipeline.AttachmentInfo.ColorAttachmentDescriptions[i].Format;
if (pipelineFormat != format)
{
throw new System.InvalidOperationException($"Color texture format mismatch! Pipeline expects {pipelineFormat}, render pass attachment is {format}");
}
}
if (graphicsPipeline.AttachmentInfo.HasDepthStencilAttachment)
{
var pipelineDepthFormat = graphicsPipeline.AttachmentInfo.DepthStencilFormat;
if (!hasDepthStencilAttachment)
{
throw new System.InvalidOperationException("Pipeline expects depth attachment!");
}
if (pipelineDepthFormat != depthStencilFormat)
{
throw new System.InvalidOperationException($"Depth texture format mismatch! Pipeline expects {pipelineDepthFormat}, render pass attachment is {depthStencilFormat}");
}
}
}
private void AssertGraphicsPipelineBound(string message = "No graphics pipeline is bound!")
{
if (currentGraphicsPipeline == null)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertTextureNonNull(in TextureSlice textureSlice)
{
if (textureSlice.Texture == null)
{
throw new NullReferenceException("Texture must not be null!");
}
}
private void AssertTextureSamplerBindingNonNull(in TextureSamplerBinding binding)
{
if (binding.Texture == null || binding.Texture.Handle == IntPtr.Zero)
{
throw new NullReferenceException("Texture binding must not be null!");
}
if (binding.Sampler == null || binding.Sampler.Handle == IntPtr.Zero)
{
throw new NullReferenceException("Sampler binding must not be null!");
}
}
private void AssertTextureHasSamplerFlag(Texture texture)
{
if ((texture.UsageFlags & TextureUsageFlags.Sampler) == 0)
{
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.Sampler!");
}
}
private void AssertTextureHasGraphicsStorageFlag(Texture texture)
{
if ((texture.UsageFlags & TextureUsageFlags.GraphicsStorage) == 0)
{
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.GraphicsStorage!");
}
}
#endif
}

View File

@ -1,33 +1,31 @@
using RefreshCS; using SDL2_gpuCS;
namespace MoonWorks.Graphics;
namespace MoonWorks.Graphics /// <summary>
/// A texture slice specifies a subresource of a texture.
/// </summary>
public struct TextureSlice
{ {
/// <summary> public Texture Texture;
/// A texture slice specifies a subresource of a texture. public uint MipLevel;
/// </summary> public uint Layer;
public struct TextureSlice
public uint Size => (Texture.Width * Texture.Height * Texture.Depth * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)) >> (int) MipLevel;
public TextureSlice(Texture texture)
{ {
public Texture Texture; Texture = texture;
public uint MipLevel; MipLevel = 0;
public uint Layer; Layer = 0;
}
public uint Size => (Texture.Width * Texture.Height * Texture.Depth * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)) >> (int) MipLevel; public SDL_Gpu.TextureSlice ToSDL()
{
public TextureSlice(Texture texture) return new SDL_Gpu.TextureSlice
{ {
Texture = texture; Texture = Texture.Handle,
MipLevel = 0; MipLevel = MipLevel,
Layer = 0; Layer = Layer
} };
public Refresh.TextureSlice ToRefreshTextureSlice()
{
return new Refresh.TextureSlice
{
texture = Texture.Handle,
mipLevel = MipLevel,
layer = Layer
};
}
} }
} }