496 lines
14 KiB
C#
496 lines
14 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using RefreshCS;
|
|
|
|
namespace Nerfed.Runtime.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; private set; }
|
|
|
|
#if DEBUG
|
|
internal uint colorAttachmentCount;
|
|
internal SampleCount colorAttachmentSampleCount;
|
|
internal TextureFormat colorFormatOne;
|
|
internal TextureFormat colorFormatTwo;
|
|
internal TextureFormat colorFormatThree;
|
|
internal TextureFormat colorFormatFour;
|
|
internal bool hasDepthStencilAttachment;
|
|
internal SampleCount depthStencilAttachmentSampleCount;
|
|
internal TextureFormat depthStencilFormat;
|
|
|
|
GraphicsPipeline currentGraphicsPipeline;
|
|
#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
|
|
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
|
|
|
|
Refresh.Refresh_BindGraphicsPipeline(
|
|
Handle,
|
|
graphicsPipeline.Handle
|
|
);
|
|
|
|
#if DEBUG
|
|
currentGraphicsPipeline = graphicsPipeline;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the viewport.
|
|
/// </summary>
|
|
public void SetViewport(in Viewport viewport)
|
|
{
|
|
Refresh.Refresh_SetViewport(
|
|
Handle,
|
|
viewport.ToRefresh()
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the scissor area.
|
|
/// </summary>
|
|
public void SetScissor(in Rect scissor)
|
|
{
|
|
#if DEBUG
|
|
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
|
|
|
|
Refresh.Refresh_SetScissor(
|
|
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 BindVertexBuffer(
|
|
in BufferBinding bufferBinding,
|
|
uint firstBinding = 0
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
#endif
|
|
|
|
Refresh.BufferBinding refreshBufferBinding = bufferBinding.ToRefresh();
|
|
|
|
Refresh.Refresh_BindVertexBuffers(
|
|
Handle,
|
|
firstBinding,
|
|
&refreshBufferBinding,
|
|
1
|
|
);
|
|
}
|
|
|
|
/// <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
|
|
|
|
Refresh.Refresh_BindIndexBuffer(
|
|
Handle,
|
|
bufferBinding.ToRefresh(),
|
|
(Refresh.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
|
|
|
|
Refresh.TextureSamplerBinding refreshTextureSamplerBinding = textureSamplerBinding.ToRefresh();
|
|
|
|
Refresh.Refresh_BindVertexSamplers(
|
|
Handle,
|
|
slot,
|
|
&refreshTextureSamplerBinding,
|
|
1
|
|
);
|
|
}
|
|
|
|
public unsafe void BindVertexStorageTexture(
|
|
in TextureSlice textureSlice,
|
|
uint slot = 0
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
AssertTextureNonNull(textureSlice.Texture);
|
|
AssertTextureHasGraphicsStorageFlag(textureSlice.Texture);
|
|
#endif
|
|
|
|
Refresh.TextureSlice refreshTextureSlice = textureSlice.ToRefresh();
|
|
|
|
Refresh.Refresh_BindVertexStorageTextures(
|
|
Handle,
|
|
slot,
|
|
&refreshTextureSlice,
|
|
1
|
|
);
|
|
}
|
|
|
|
public unsafe void BindVertexStorageBuffer(
|
|
Buffer buffer,
|
|
uint slot = 0
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
AssertBufferNonNull(buffer);
|
|
AssertBufferHasGraphicsStorageFlag(buffer);
|
|
#endif
|
|
|
|
IntPtr bufferHandle = buffer.Handle;
|
|
|
|
Refresh.Refresh_BindVertexStorageBuffers(
|
|
Handle,
|
|
slot,
|
|
&bufferHandle,
|
|
1
|
|
);
|
|
}
|
|
|
|
public unsafe void BindFragmentSampler(
|
|
in TextureSamplerBinding textureSamplerBinding,
|
|
uint slot = 0
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
AssertTextureSamplerBindingNonNull(textureSamplerBinding);
|
|
AssertTextureHasSamplerFlag(textureSamplerBinding.Texture);
|
|
#endif
|
|
|
|
Refresh.TextureSamplerBinding refreshTextureSamplerBinding = textureSamplerBinding.ToRefresh();
|
|
|
|
Refresh.Refresh_BindFragmentSamplers(
|
|
Handle,
|
|
slot,
|
|
&refreshTextureSamplerBinding,
|
|
1
|
|
);
|
|
}
|
|
|
|
public unsafe void BindFragmentStorageTexture(
|
|
in TextureSlice textureSlice,
|
|
uint slot = 0
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
AssertTextureNonNull(textureSlice.Texture);
|
|
AssertTextureHasGraphicsStorageFlag(textureSlice.Texture);
|
|
#endif
|
|
|
|
Refresh.TextureSlice refreshTextureSlice = textureSlice.ToRefresh();
|
|
|
|
Refresh.Refresh_BindFragmentStorageTextures(
|
|
Handle,
|
|
slot,
|
|
&refreshTextureSlice,
|
|
1
|
|
);
|
|
}
|
|
|
|
public unsafe void BindFragmentStorageBuffer(
|
|
Buffer buffer,
|
|
uint slot = 0
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
AssertBufferNonNull(buffer);
|
|
AssertBufferHasGraphicsStorageFlag(buffer);
|
|
#endif
|
|
|
|
IntPtr bufferHandle = buffer.Handle;
|
|
|
|
Refresh.Refresh_BindFragmentStorageBuffers(
|
|
Handle,
|
|
slot,
|
|
&bufferHandle,
|
|
1
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws using a vertex buffer and an index buffer, and an optional instance count.
|
|
/// </summary>
|
|
/// <param name="baseVertex">The starting index offset for the vertex buffer.</param>
|
|
/// <param name="startIndex">The starting index offset for the index buffer.</param>
|
|
/// <param name="primitiveCount">The number of primitives to draw.</param>
|
|
/// <param name="instanceCount">The number of instances to draw.</param>
|
|
public void DrawIndexedPrimitives(
|
|
uint baseVertex,
|
|
uint startIndex,
|
|
uint primitiveCount,
|
|
uint instanceCount = 1
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
#endif
|
|
|
|
Refresh.Refresh_DrawIndexedPrimitives(
|
|
Handle,
|
|
baseVertex,
|
|
startIndex,
|
|
primitiveCount,
|
|
instanceCount
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws using a vertex buffer and an index buffer.
|
|
/// </summary>
|
|
/// <param name="baseVertex">The starting index offset for the vertex buffer.</param>
|
|
/// <param name="startIndex">The starting index offset for the index buffer.</param>
|
|
/// <param name="primitiveCount">The number of primitives to draw.</param>
|
|
public void DrawPrimitives(
|
|
uint vertexStart,
|
|
uint primitiveCount
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
#endif
|
|
|
|
Refresh.Refresh_DrawPrimitives(
|
|
Handle,
|
|
vertexStart,
|
|
primitiveCount
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to DrawPrimitives, but parameters are set from a buffer.
|
|
/// The buffer must have the Indirect usage flag set.
|
|
/// </summary>
|
|
/// <param name="buffer">The draw parameters buffer.</param>
|
|
/// <param name="offsetInBytes">The offset to start reading from the draw parameters buffer.</param>
|
|
/// <param name="drawCount">The number of draw parameter sets that should be read from the buffer.</param>
|
|
/// <param name="stride">The byte stride between sets of draw parameters.</param>
|
|
public void DrawPrimitivesIndirect(
|
|
Buffer buffer,
|
|
uint offsetInBytes,
|
|
uint drawCount,
|
|
uint stride
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
#endif
|
|
|
|
Refresh.Refresh_DrawPrimitivesIndirect(
|
|
Handle,
|
|
buffer.Handle,
|
|
offsetInBytes,
|
|
drawCount,
|
|
stride
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Similar to DrawIndexedPrimitives, but parameters are set from a buffer.
|
|
/// The buffer must have the Indirect usage flag set.
|
|
/// </summary>
|
|
/// <param name="buffer">The draw parameters buffer.</param>
|
|
/// <param name="offsetInBytes">The offset to start reading from the draw parameters buffer.</param>
|
|
/// <param name="drawCount">The number of draw parameter sets that should be read from the buffer.</param>
|
|
/// <param name="stride">The byte stride between sets of draw parameters.</param>
|
|
public void DrawIndexedPrimitivesIndirect(
|
|
Buffer buffer,
|
|
uint offsetInBytes,
|
|
uint drawCount,
|
|
uint stride
|
|
)
|
|
{
|
|
#if DEBUG
|
|
AssertGraphicsPipelineBound();
|
|
#endif
|
|
|
|
Refresh.Refresh_DrawIndexedPrimitivesIndirect(
|
|
Handle,
|
|
buffer.Handle,
|
|
offsetInBytes,
|
|
drawCount,
|
|
stride
|
|
);
|
|
}
|
|
|
|
#if DEBUG
|
|
private void AssertRenderPassPipelineFormatMatch(GraphicsPipeline graphicsPipeline)
|
|
{
|
|
for (int 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;
|
|
}
|
|
|
|
TextureFormat 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)
|
|
{
|
|
TextureFormat 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!");
|
|
}
|
|
}
|
|
|
|
private void AssertBufferNonNull(Buffer buffer)
|
|
{
|
|
if (buffer == null || buffer.Handle == IntPtr.Zero)
|
|
{
|
|
throw new System.NullReferenceException("Buffer must not be null!");
|
|
}
|
|
}
|
|
|
|
private void AssertBufferHasGraphicsStorageFlag(Buffer buffer)
|
|
{
|
|
if ((buffer.UsageFlags & BufferUsageFlags.GraphicsStorage) == 0)
|
|
{
|
|
throw new System.ArgumentException("The bound Buffer's UsageFlags must include BufferUsageFlag.GraphicsStorage!");
|
|
}
|
|
}
|
|
#endif
|
|
}
|