using System; using System.Runtime.InteropServices; using SDL2_gpuCS; 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; internal set; } #if DEBUG internal bool active; 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 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 } /// /// Sets the viewport. /// public void SetViewport(in Viewport viewport) { #if DEBUG AssertRenderPassActive(); #endif SDL_Gpu.SDL_GpuSetViewport( Handle, viewport.ToSDL() ); } /// /// Sets the scissor area. /// 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.ToSDL() ); } /// /// 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 sdlBufferBinding = bufferBinding.ToSDL(); SDL_Gpu.SDL_GpuBindVertexBuffers( Handle, firstBinding, &sdlBufferBinding, 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 SDL_Gpu.SDL_GpuBindIndexBuffer( Handle, bufferBinding.ToSDL(), (SDL_Gpu.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 sdlTextureSamplerBinding = textureSamplerBinding.ToSDL(); SDL_Gpu.SDL_GpuBindVertexSamplers( Handle, slot, &sdlTextureSamplerBinding, 1 ); } public unsafe void BindVertexStorageTexture( in TextureSlice textureSlice, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertTextureNonNull(textureSlice.Texture); AssertTextureHasGraphicsStorageFlag(textureSlice.Texture); #endif var sdlTextureSlice = textureSlice.ToSDL(); SDL_Gpu.SDL_GpuBindVertexStorageTextures( Handle, slot, &sdlTextureSlice, 1 ); } public unsafe void BindVertexStorageBuffer( GpuBuffer buffer, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertBufferNonNull(buffer); AssertBufferHasGraphicsStorageFlag(buffer); #endif var bufferHandle = buffer.Handle; SDL_Gpu.SDL_GpuBindVertexStorageBuffers( Handle, slot, &bufferHandle, 1 ); } public unsafe void BindFragmentSamplers( in TextureSamplerBinding textureSamplerBinding, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertTextureSamplerBindingNonNull(textureSamplerBinding); AssertTextureHasSamplerFlag(textureSamplerBinding.Texture); #endif var sdlTextureSamplerBinding = textureSamplerBinding.ToSDL(); SDL_Gpu.SDL_GpuBindFragmentSamplers( Handle, slot, &sdlTextureSamplerBinding, 1 ); } public unsafe void BindFragmentStorageTexture( in TextureSlice textureSlice, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertTextureNonNull(textureSlice.Texture); AssertTextureHasGraphicsStorageFlag(textureSlice.Texture); #endif var sdlTextureSlice = textureSlice.ToSDL(); SDL_Gpu.SDL_GpuBindFragmentStorageTextures( Handle, slot, &sdlTextureSlice, 1 ); } public unsafe void BindFragmentStorageBuffer( GpuBuffer buffer, uint slot = 0 ) { #if DEBUG AssertGraphicsPipelineBound(); AssertBufferNonNull(buffer); AssertBufferHasGraphicsStorageFlag(buffer); #endif var bufferHandle = buffer.Handle; SDL_Gpu.SDL_GpuBindFragmentStorageBuffers( 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 SDL_Gpu.SDL_GpuPushVertexUniformData( Handle, slot, (nint) uniformsPtr, size ); } public unsafe void PushVertexUniformData( in T uniforms ) where T : unmanaged { fixed (T* uniformsPtr = &uniforms) { PushVertexUniformData(uniformsPtr, (uint) Marshal.SizeOf()); } } 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 SDL_Gpu.SDL_GpuPushFragmentUniformData( Handle, slot, (nint) uniformsPtr, size ); } public unsafe void PushFragmentUniformData( in T uniforms ) where T : unmanaged { fixed (T* uniformsPtr = &uniforms) { PushFragmentUniformData(uniformsPtr, (uint) Marshal.SizeOf()); } } /// /// 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 SDL_Gpu.SDL_GpuDrawIndexedPrimitives( 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 SDL_Gpu.SDL_GpuDrawPrimitives( 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 SDL_Gpu.SDL_GpuDrawPrimitivesIndirect( 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 SDL_Gpu.SDL_GpuDrawIndexedPrimitivesIndirect( Handle, buffer.Handle, offsetInBytes, drawCount, stride ); } #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!"); } } 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 }