using System; using System.Runtime.InteropServices; using RefreshCS; namespace MoonWorks.Graphics; /// /// 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. /// 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; } /// /// Binds a graphics pipeline so that rendering work may be performed. /// /// The graphics pipeline to bind. 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 } /// /// Sets the viewport. /// public void SetViewport(in Viewport viewport) { Refresh.Refresh_SetViewport( Handle, viewport.ToRefresh() ); } /// /// Sets the scissor area. /// 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() ); } /// /// Binds vertex buffers to be used by subsequent draw calls. /// /// Buffer to bind and associated offset. /// The index of the first vertex input binding whose state is updated by the command. public unsafe void BindVertexBuffer( in BufferBinding bufferBinding, uint firstBinding = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); #endif var refreshBufferBinding = bufferBinding.ToRefresh(); Refresh.Refresh_BindVertexBuffers( Handle, firstBinding, &refreshBufferBinding, 1 ); } /// /// Binds an index buffer to be used by subsequent draw calls. /// /// The index buffer to bind. /// The size in bytes of the index buffer elements. /// The offset index for the buffer. public void BindIndexBuffer( BufferBinding bufferBinding, IndexElementSize indexElementSize ) { #if DEBUG AssertGraphicsPipelineBound(); #endif Refresh.Refresh_BindIndexBuffer( Handle, bufferBinding.ToRefresh(), (Refresh.IndexElementSize) indexElementSize ); } /// /// Binds samplers to be used by the vertex shader. /// /// The texture-sampler to bind. public unsafe void BindVertexSampler( in TextureSamplerBinding textureSamplerBinding, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertTextureSamplerBindingNonNull(textureSamplerBinding); AssertTextureHasSamplerFlag(textureSamplerBinding.Texture); #endif var 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 var refreshTextureSlice = textureSlice.ToRefresh(); Refresh.Refresh_BindVertexStorageTextures( Handle, slot, &refreshTextureSlice, 1 ); } public unsafe void BindVertexStorageBuffer( GpuBuffer buffer, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertBufferNonNull(buffer); AssertBufferHasGraphicsStorageFlag(buffer); #endif var 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 var 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 var refreshTextureSlice = textureSlice.ToRefresh(); Refresh.Refresh_BindFragmentStorageTextures( Handle, slot, &refreshTextureSlice, 1 ); } public unsafe void BindFragmentStorageBuffer( GpuBuffer buffer, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertBufferNonNull(buffer); AssertBufferHasGraphicsStorageFlag(buffer); #endif var bufferHandle = buffer.Handle; Refresh.Refresh_BindFragmentStorageBuffers( Handle, slot, &bufferHandle, 1 ); } public unsafe void PushVertexUniformData( void* uniformsPtr, uint size, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); if (slot >= currentGraphicsPipeline.VertexShaderResourceInfo.UniformBufferCount) { throw new System.ArgumentException($"Slot {slot} given, but {currentGraphicsPipeline.VertexShaderResourceInfo.UniformBufferCount} uniform buffers are used on the shader!"); } #endif Refresh.Refresh_PushVertexUniformData( Handle, slot, (nint) uniformsPtr, size ); } public unsafe void PushVertexUniformData( in T uniforms, uint slot = 0 ) where T : unmanaged { fixed (T* uniformsPtr = &uniforms) { PushVertexUniformData(uniformsPtr, (uint) Marshal.SizeOf(), slot); } } public unsafe void PushFragmentUniformData( void* uniformsPtr, uint size, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); if (slot >= currentGraphicsPipeline.FragmentShaderResourceInfo.UniformBufferCount) { throw new System.ArgumentException($"Slot {slot} given, but {currentGraphicsPipeline.FragmentShaderResourceInfo.UniformBufferCount} uniform buffers are used on the shader!"); } #endif Refresh.Refresh_PushFragmentUniformData( Handle, slot, (nint) uniformsPtr, size ); } public unsafe void PushFragmentUniformData( in T uniforms, uint slot = 0 ) where T : unmanaged { fixed (T* uniformsPtr = &uniforms) { PushFragmentUniformData(uniformsPtr, (uint) Marshal.SizeOf(), slot); } } /// /// Draws using a vertex buffer and an index buffer, and an optional instance count. /// /// The starting index offset for the vertex buffer. /// The starting index offset for the index buffer. /// The number of primitives to draw. /// The number of instances to draw. public void DrawIndexedPrimitives( uint baseVertex, uint startIndex, uint primitiveCount, uint instanceCount = 1 ) { #if DEBUG AssertGraphicsPipelineBound(); #endif Refresh.Refresh_DrawIndexedPrimitives( Handle, baseVertex, startIndex, primitiveCount, instanceCount ); } /// /// Draws using a vertex buffer and an index buffer. /// /// The starting index offset for the vertex buffer. /// The starting index offset for the index buffer. /// The number of primitives to draw. public void DrawPrimitives( uint vertexStart, uint primitiveCount ) { #if DEBUG AssertGraphicsPipelineBound(); #endif Refresh.Refresh_DrawPrimitives( Handle, vertexStart, primitiveCount ); } /// /// Similar to DrawPrimitives, but parameters are set from a buffer. /// The buffer must have the Indirect usage flag set. /// /// The draw parameters buffer. /// The offset to start reading from the draw parameters buffer. /// The number of draw parameter sets that should be read from the buffer. /// The byte stride between sets of draw parameters. public void DrawPrimitivesIndirect( GpuBuffer buffer, uint offsetInBytes, uint drawCount, uint stride ) { #if DEBUG AssertGraphicsPipelineBound(); #endif Refresh.Refresh_DrawPrimitivesIndirect( Handle, buffer.Handle, offsetInBytes, drawCount, stride ); } /// /// Similar to DrawIndexedPrimitives, but parameters are set from a buffer. /// The buffer must have the Indirect usage flag set. /// /// The draw parameters buffer. /// The offset to start reading from the draw parameters buffer. /// The number of draw parameter sets that should be read from the buffer. /// The byte stride between sets of draw parameters. public void DrawIndexedPrimitivesIndirect( GpuBuffer 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 (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!"); } } 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!"); } } #endif }