2024-06-04 17:20:07 +00:00
using System ;
using System.Runtime.InteropServices ;
2024-06-05 19:34:24 +00:00
using RefreshCS ;
2024-06-04 17:20:07 +00:00
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
{
2024-06-05 00:24:25 +00:00
public nint Handle { get ; private set ; }
2024-06-04 17:20:07 +00:00
#if DEBUG
2024-06-04 23:04:19 +00:00
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 ;
2024-06-04 17:20:07 +00:00
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
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindGraphicsPipeline (
2024-06-04 17:20:07 +00:00
Handle ,
graphicsPipeline . Handle
) ;
#if DEBUG
currentGraphicsPipeline = graphicsPipeline ;
# endif
}
/// <summary>
/// Sets the viewport.
/// </summary>
public void SetViewport ( in Viewport viewport )
{
2024-06-05 19:34:24 +00:00
Refresh . Refresh_SetViewport (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-05 19:34:24 +00:00
viewport . ToRefresh ( )
2024-06-04 17:20:07 +00:00
) ;
}
/// <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
2024-06-05 19:34:24 +00:00
Refresh . Refresh_SetScissor (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-05 19:34:24 +00:00
scissor . ToRefresh ( )
2024-06-04 17:20:07 +00:00
) ;
}
/// <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>
2024-06-04 23:04:19 +00:00
public unsafe void BindVertexBuffer (
2024-06-04 17:20:07 +00:00
in BufferBinding bufferBinding ,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
# endif
2024-06-05 19:37:02 +00:00
var refreshBufferBinding = bufferBinding . ToRefresh ( ) ;
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindVertexBuffers (
2024-06-04 17:20:07 +00:00
Handle ,
firstBinding ,
2024-06-05 19:37:02 +00:00
& refreshBufferBinding ,
2024-06-04 17:20:07 +00:00
1
) ;
}
/// <summary>
2024-06-04 23:04:19 +00:00
/// Binds an index buffer to be used by subsequent draw calls.
2024-06-04 17:20:07 +00:00
/// </summary>
2024-06-04 23:04:19 +00:00
/// <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
)
{
2024-06-04 17:20:07 +00:00
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
# endif
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindIndexBuffer (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-05 19:34:24 +00:00
bufferBinding . ToRefresh ( ) ,
( Refresh . IndexElementSize ) indexElementSize
2024-06-04 17:20:07 +00:00
) ;
}
/// <summary>
2024-06-04 23:04:19 +00:00
/// Binds samplers to be used by the vertex shader.
2024-06-04 17:20:07 +00:00
/// </summary>
2024-06-04 23:04:19 +00:00
/// <param name="textureSamplerBindings">The texture-sampler to bind.</param>
public unsafe void BindVertexSampler (
in TextureSamplerBinding textureSamplerBinding ,
uint slot = 0
2024-06-04 17:20:07 +00:00
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
2024-06-04 23:04:19 +00:00
AssertTextureSamplerBindingNonNull ( textureSamplerBinding ) ;
AssertTextureHasSamplerFlag ( textureSamplerBinding . Texture ) ;
2024-06-04 17:20:07 +00:00
# endif
2024-06-05 19:37:02 +00:00
var refreshTextureSamplerBinding = textureSamplerBinding . ToRefresh ( ) ;
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindVertexSamplers (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
slot ,
2024-06-05 19:37:02 +00:00
& refreshTextureSamplerBinding ,
2024-06-04 23:04:19 +00:00
1
2024-06-04 17:20:07 +00:00
) ;
}
2024-06-04 23:04:19 +00:00
public unsafe void BindVertexStorageTexture (
in TextureSlice textureSlice ,
uint slot = 0
2024-06-04 17:20:07 +00:00
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
2024-06-04 23:04:19 +00:00
AssertTextureNonNull ( textureSlice . Texture ) ;
AssertTextureHasGraphicsStorageFlag ( textureSlice . Texture ) ;
2024-06-04 17:20:07 +00:00
# endif
2024-06-05 19:37:02 +00:00
var refreshTextureSlice = textureSlice . ToRefresh ( ) ;
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindVertexStorageTextures (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
slot ,
2024-06-05 19:37:02 +00:00
& refreshTextureSlice ,
2024-06-04 23:04:19 +00:00
1
2024-06-04 17:20:07 +00:00
) ;
}
2024-06-04 23:04:19 +00:00
public unsafe void BindVertexStorageBuffer (
GpuBuffer buffer ,
uint slot = 0
2024-06-04 17:20:07 +00:00
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
2024-06-04 23:04:19 +00:00
AssertBufferNonNull ( buffer ) ;
AssertBufferHasGraphicsStorageFlag ( buffer ) ;
2024-06-04 17:20:07 +00:00
# endif
2024-06-04 23:04:19 +00:00
var bufferHandle = buffer . Handle ;
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindVertexStorageBuffers (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
slot ,
& bufferHandle ,
1
2024-06-04 17:20:07 +00:00
) ;
2024-06-04 23:04:19 +00:00
}
2024-06-05 21:19:17 +00:00
public unsafe void BindFragmentSampler (
2024-06-04 23:04:19 +00:00
in TextureSamplerBinding textureSamplerBinding ,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
AssertTextureSamplerBindingNonNull ( textureSamplerBinding ) ;
AssertTextureHasSamplerFlag ( textureSamplerBinding . Texture ) ;
# endif
2024-06-05 19:37:02 +00:00
var refreshTextureSamplerBinding = textureSamplerBinding . ToRefresh ( ) ;
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindFragmentSamplers (
2024-06-04 23:04:19 +00:00
Handle ,
slot ,
2024-06-05 19:37:02 +00:00
& refreshTextureSamplerBinding ,
2024-06-04 23:04:19 +00:00
1
) ;
2024-06-04 17:20:07 +00:00
}
2024-06-04 23:04:19 +00:00
public unsafe void BindFragmentStorageTexture (
in TextureSlice textureSlice ,
uint slot = 0
) {
2024-06-04 17:20:07 +00:00
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
2024-06-04 23:04:19 +00:00
AssertTextureNonNull ( textureSlice . Texture ) ;
AssertTextureHasGraphicsStorageFlag ( textureSlice . Texture ) ;
2024-06-04 17:20:07 +00:00
# endif
2024-06-05 19:37:02 +00:00
var refreshTextureSlice = textureSlice . ToRefresh ( ) ;
2024-06-04 23:04:19 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindFragmentStorageTextures (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
slot ,
2024-06-05 19:37:02 +00:00
& refreshTextureSlice ,
2024-06-04 23:04:19 +00:00
1
2024-06-04 17:20:07 +00:00
) ;
}
2024-06-04 23:04:19 +00:00
public unsafe void BindFragmentStorageBuffer (
GpuBuffer buffer ,
2024-06-04 17:20:07 +00:00
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
2024-06-04 23:04:19 +00:00
AssertBufferNonNull ( buffer ) ;
AssertBufferHasGraphicsStorageFlag ( buffer ) ;
2024-06-04 17:20:07 +00:00
# endif
2024-06-04 23:04:19 +00:00
var bufferHandle = buffer . Handle ;
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_BindFragmentStorageBuffers (
2024-06-04 17:20:07 +00:00
Handle ,
slot ,
2024-06-04 23:04:19 +00:00
& bufferHandle ,
2024-06-04 17:20:07 +00:00
1
) ;
}
2024-06-04 23:04:19 +00:00
public unsafe void PushVertexUniformData (
void * uniformsPtr ,
uint size ,
uint slot = 0
2024-06-04 17:20:07 +00:00
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
2024-06-04 23:04:19 +00:00
if ( slot > = currentGraphicsPipeline . VertexShaderResourceInfo . UniformBufferCount )
2024-06-04 17:20:07 +00:00
{
2024-06-04 23:04:19 +00:00
throw new System . ArgumentException ( $"Slot {slot} given, but {currentGraphicsPipeline.VertexShaderResourceInfo.UniformBufferCount} uniform buffers are used on the shader!" ) ;
2024-06-04 17:20:07 +00:00
}
# endif
2024-06-05 19:34:24 +00:00
Refresh . Refresh_PushVertexUniformData (
2024-06-04 23:04:19 +00:00
Handle ,
slot ,
( nint ) uniformsPtr ,
size
) ;
}
public unsafe void PushVertexUniformData < T > (
2024-06-05 00:24:25 +00:00
in T uniforms ,
uint slot = 0
2024-06-04 23:04:19 +00:00
) where T : unmanaged
{
fixed ( T * uniformsPtr = & uniforms )
{
2024-06-05 00:24:25 +00:00
PushVertexUniformData ( uniformsPtr , ( uint ) Marshal . SizeOf < T > ( ) , slot ) ;
2024-06-04 23:04:19 +00:00
}
}
2024-06-04 17:20:07 +00:00
2024-06-04 23:04:19 +00:00
public unsafe void PushFragmentUniformData (
void * uniformsPtr ,
uint size ,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
if ( slot > = currentGraphicsPipeline . FragmentShaderResourceInfo . UniformBufferCount )
2024-06-04 17:20:07 +00:00
{
2024-06-04 23:04:19 +00:00
throw new System . ArgumentException ( $"Slot {slot} given, but {currentGraphicsPipeline.FragmentShaderResourceInfo.UniformBufferCount} uniform buffers are used on the shader!" ) ;
2024-06-04 17:20:07 +00:00
}
2024-06-04 23:04:19 +00:00
# endif
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_PushFragmentUniformData (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
slot ,
( nint ) uniformsPtr ,
size
2024-06-04 17:20:07 +00:00
) ;
2024-06-04 23:04:19 +00:00
}
2024-06-04 17:20:07 +00:00
2024-06-04 23:04:19 +00:00
public unsafe void PushFragmentUniformData < T > (
2024-06-05 00:24:25 +00:00
in T uniforms ,
uint slot = 0
2024-06-04 23:04:19 +00:00
) where T : unmanaged
{
fixed ( T * uniformsPtr = & uniforms )
{
2024-06-05 00:24:25 +00:00
PushFragmentUniformData ( uniformsPtr , ( uint ) Marshal . SizeOf < T > ( ) , slot ) ;
2024-06-04 23:04:19 +00:00
}
2024-06-04 17:20:07 +00:00
}
2024-06-04 23:04:19 +00:00
/// <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
2024-06-04 17:20:07 +00:00
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
# endif
2024-06-05 19:34:24 +00:00
Refresh . Refresh_DrawIndexedPrimitives (
2024-06-04 23:04:19 +00:00
Handle ,
baseVertex ,
startIndex ,
primitiveCount ,
instanceCount
) ;
}
2024-06-04 17:20:07 +00:00
2024-06-04 23:04:19 +00:00
/// <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
2024-06-05 19:34:24 +00:00
Refresh . Refresh_DrawPrimitives (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
vertexStart ,
primitiveCount
2024-06-04 17:20:07 +00:00
) ;
}
2024-06-04 23:04:19 +00:00
/// <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 (
GpuBuffer buffer ,
uint offsetInBytes ,
uint drawCount ,
uint stride
2024-06-04 17:20:07 +00:00
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
# endif
2024-06-05 19:34:24 +00:00
Refresh . Refresh_DrawPrimitivesIndirect (
2024-06-04 23:04:19 +00:00
Handle ,
buffer . Handle ,
offsetInBytes ,
drawCount ,
stride
) ;
}
2024-06-04 17:20:07 +00:00
2024-06-04 23:04:19 +00:00
/// <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 (
GpuBuffer buffer ,
uint offsetInBytes ,
uint drawCount ,
uint stride
) {
#if DEBUG
AssertGraphicsPipelineBound ( ) ;
# endif
2024-06-04 17:20:07 +00:00
2024-06-05 19:34:24 +00:00
Refresh . Refresh_DrawIndexedPrimitivesIndirect (
2024-06-04 17:20:07 +00:00
Handle ,
2024-06-04 23:04:19 +00:00
buffer . Handle ,
offsetInBytes ,
drawCount ,
stride
2024-06-04 17:20:07 +00:00
) ;
}
#if DEBUG
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!" ) ;
}
}
2024-06-04 23:04:19 +00:00
private void AssertBufferNonNull ( GpuBuffer buffer )
{
if ( buffer = = null | | buffer . Handle = = IntPtr . Zero )
{
throw new System . NullReferenceException ( "Buffer must not be null!" ) ;
}
}
private void AssertBufferHasGraphicsStorageFlag ( GpuBuffer buffer )
{
if ( ( buffer . UsageFlags & BufferUsageFlags . GraphicsStorage ) = = 0 )
{
throw new System . ArgumentException ( "The bound Buffer's UsageFlags must include BufferUsageFlag.GraphicsStorage!" ) ;
}
}
2024-06-04 17:20:07 +00:00
# endif
}