using System; using RefreshCS; namespace MoonWorks.Graphics { /// /// Command buffers are used to apply render state and issue draw calls. /// NOTE: it is not recommended to hold references to command buffers long term. /// public struct CommandBuffer { public GraphicsDevice Device { get; } public IntPtr Handle { get; internal set; } // called from RefreshDevice internal CommandBuffer(GraphicsDevice device, IntPtr handle) { Device = device; Handle = handle; } // FIXME: we can probably use the NativeMemory functions to not have to generate arrays here /// /// Begins a render pass. /// All render state, resource binding, and draw commands must be made within a render pass. /// It is an error to call this after calling BeginRenderPass but before calling EndRenderPass. /// /// The color attachments to use in the render pass. public unsafe void BeginRenderPass( params ColorAttachmentInfo[] colorAttachmentInfos ) { #if DEBUG if (colorAttachmentInfos.Length == 0) { Logger.LogError("Render pass must contain at least one attachment!"); return; } if (colorAttachmentInfos.Length > 4) { Logger.LogError("Render pass cannot have more than 4 color attachments!"); return; } #endif var refreshColorAttachmentInfos = new Refresh.ColorAttachmentInfo[colorAttachmentInfos.Length]; for (var i = 0; i < colorAttachmentInfos.Length; i += 1) { refreshColorAttachmentInfos[i] = colorAttachmentInfos[i].ToRefresh(); } fixed (Refresh.ColorAttachmentInfo* pColorAttachmentInfos = refreshColorAttachmentInfos) { Refresh.Refresh_BeginRenderPass( Device.Handle, Handle, IntPtr.Zero, (IntPtr) pColorAttachmentInfos, (uint) colorAttachmentInfos.Length, IntPtr.Zero ); } } /// /// Begins a render pass. /// All render state, resource binding, and draw commands must be made within a render pass. /// It is an error to call this after calling BeginRenderPass but before calling EndRenderPass. /// /// The depth stencil attachment to use in the render pass. /// The color attachments to use in the render pass. public unsafe void BeginRenderPass( DepthStencilAttachmentInfo depthStencilAttachmentInfo, params ColorAttachmentInfo[] colorAttachmentInfos ) { #if DEBUG if (colorAttachmentInfos.Length == 0) { Logger.LogError("Render pass must contain at least one attachment!"); return; } if (colorAttachmentInfos.Length > 4) { Logger.LogError("Render pass cannot have more than 4 color attachments!"); return; } #endif var refreshColorAttachmentInfos = new Refresh.ColorAttachmentInfo[colorAttachmentInfos.Length]; for (var i = 0; i < colorAttachmentInfos.Length; i += 1) { refreshColorAttachmentInfos[i] = colorAttachmentInfos[i].ToRefresh(); } var refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh(); fixed (Refresh.ColorAttachmentInfo* pColorAttachmentInfos = refreshColorAttachmentInfos) { Refresh.Refresh_BeginRenderPass( Device.Handle, Handle, IntPtr.Zero, pColorAttachmentInfos, (uint) colorAttachmentInfos.Length, &refreshDepthStencilAttachmentInfo ); } } /// /// Begins a render pass. /// All render state, resource binding, and draw commands must be made within a render pass. /// It is an error to call this after calling BeginRenderPass but before calling EndRenderPass. /// /// The rectangle that should be drawn to on the attachments. /// The color attachments to use in the render pass. public unsafe void BeginRenderPass( in Rect renderArea, params ColorAttachmentInfo[] colorAttachmentInfos ) { #if DEBUG if (colorAttachmentInfos.Length == 0) { Logger.LogError("Render pass must contain at least one attachment!"); return; } if (colorAttachmentInfos.Length > 4) { Logger.LogError("Render pass cannot have more than 4 color attachments!"); return; } #endif var refreshColorAttachmentInfos = new Refresh.ColorAttachmentInfo[colorAttachmentInfos.Length]; for (var i = 0; i < colorAttachmentInfos.Length; i += 1) { refreshColorAttachmentInfos[i] = colorAttachmentInfos[i].ToRefresh(); } fixed (Refresh.ColorAttachmentInfo* pColorAttachmentInfos = refreshColorAttachmentInfos) { Refresh.Refresh_BeginRenderPass( Device.Handle, Handle, renderArea.ToRefresh(), (IntPtr) pColorAttachmentInfos, (uint) colorAttachmentInfos.Length, IntPtr.Zero ); } } /// /// Begins a render pass. /// All render state, resource binding, and draw commands must be made within a render pass. /// It is an error to call this after calling BeginRenderPass but before calling EndRenderPass. /// /// The rectangle that should be drawn to on the attachments. /// The depth stencil attachment to use in the render pass. /// The color attachments to use in the render pass. public unsafe void BeginRenderPass( in Rect renderArea, DepthStencilAttachmentInfo depthStencilAttachmentInfo, params ColorAttachmentInfo[] colorAttachmentInfos ) { #if DEBUG if (colorAttachmentInfos.Length == 0) { Logger.LogError("Render pass must contain at least one attachment!"); return; } if (colorAttachmentInfos.Length > 4) { Logger.LogError("Render pass cannot have more than 4 color attachments!"); return; } #endif var refreshColorAttachmentInfos = new Refresh.ColorAttachmentInfo[colorAttachmentInfos.Length]; for (var i = 0; i < colorAttachmentInfos.Length; i += 1) { refreshColorAttachmentInfos[i] = colorAttachmentInfos[i].ToRefresh(); } var refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh(); fixed (Refresh.ColorAttachmentInfo* pColorAttachmentInfos = refreshColorAttachmentInfos) { Refresh.Refresh_BeginRenderPass( Device.Handle, Handle, renderArea.ToRefresh(), pColorAttachmentInfos, (uint) colorAttachmentInfos.Length, &refreshDepthStencilAttachmentInfo ); } } /// /// Binds a compute pipeline so that compute work may be dispatched. /// /// The compute pipeline to bind. public void BindComputePipeline( ComputePipeline computePipeline ) { Refresh.Refresh_BindComputePipeline( Device.Handle, Handle, computePipeline.Handle ); } /// /// Binds buffers to be used in the compute shader. /// /// A set of buffers to bind. public unsafe void BindComputeBuffers( params Buffer[] buffers ) { var bufferPtrs = stackalloc IntPtr[buffers.Length]; for (var i = 0; i < buffers.Length; i += 1) { bufferPtrs[i] = buffers[i].Handle; } Refresh.Refresh_BindComputeBuffers( Device.Handle, Handle, (IntPtr) bufferPtrs ); } /// /// Binds textures to be used in the compute shader. /// /// A set of textures to bind. public unsafe void BindComputeTextures( params Texture[] textures ) { var texturePtrs = stackalloc IntPtr[textures.Length]; for (var i = 0; i < textures.Length; i += 1) { texturePtrs[i] = textures[i].Handle; } Refresh.Refresh_BindComputeTextures( Device.Handle, Handle, (IntPtr) texturePtrs ); } /// /// Dispatches compute work. /// /// /// /// /// public void DispatchCompute( uint groupCountX, uint groupCountY, uint groupCountZ, uint computeParamOffset ) { Refresh.Refresh_DispatchCompute( Device.Handle, Handle, groupCountX, groupCountY, groupCountZ, computeParamOffset ); } /// /// Binds a graphics pipeline so that rendering work may be performed. /// /// The graphics pipeline to bind. public void BindGraphicsPipeline( GraphicsPipeline graphicsPipeline ) { Refresh.Refresh_BindGraphicsPipeline( Device.Handle, Handle, graphicsPipeline.Handle ); } /// /// Sets the viewport. Only valid during a render pass. /// public void SetViewport(Viewport viewport) { Refresh.Refresh_SetViewport( Device.Handle, Handle, viewport.ToRefresh() ); } /// /// Sets the scissor area. Only valid during a render pass. /// public void SetScissor(Rect scissor) { Refresh.Refresh_SetScissor( Device.Handle, Handle, scissor.ToRefresh() ); } /// /// Binds vertex buffers to be used by subsequent draw calls. /// /// The index of the first buffer to bind. /// Buffers to bind and their associated offsets. public unsafe void BindVertexBuffers( uint firstBinding, params BufferBinding[] bufferBindings ) { var bufferPtrs = stackalloc IntPtr[bufferBindings.Length]; var offsets = stackalloc ulong[bufferBindings.Length]; for (var i = 0; i < bufferBindings.Length; i += 1) { bufferPtrs[i] = bufferBindings[i].Buffer.Handle; offsets[i] = bufferBindings[i].Offset; } Refresh.Refresh_BindVertexBuffers( Device.Handle, Handle, firstBinding, (uint) bufferBindings.Length, (IntPtr) bufferPtrs, (IntPtr) offsets ); } /// /// Binds vertex buffers to be used by subsequent draw calls. /// /// The buffers to bind. public unsafe void BindVertexBuffers( params Buffer[] buffers ) { var bufferPtrs = stackalloc IntPtr[buffers.Length]; var offsets = stackalloc ulong[buffers.Length]; for (var i = 0; i < buffers.Length; i += 1) { bufferPtrs[i] = buffers[i].Handle; offsets[i] = 0; } Refresh.Refresh_BindVertexBuffers( Device.Handle, Handle, 0, (uint) buffers.Length, (IntPtr) bufferPtrs, (IntPtr) offsets ); } /// /// 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( Buffer indexBuffer, IndexElementSize indexElementSize, uint offset = 0 ) { Refresh.Refresh_BindIndexBuffer( Device.Handle, Handle, indexBuffer.Handle, offset, (Refresh.IndexElementSize) indexElementSize ); } /// /// Binds samplers to be used by the vertex shader. /// /// An array of texture-sampler pairs to bind. /// The number of texture-sampler pairs from the array to bind. public unsafe void BindVertexSamplers( TextureSamplerBinding[] textureSamplerBindings, int length ) { var texturePtrs = stackalloc IntPtr[textureSamplerBindings.Length]; var samplerPtrs = stackalloc IntPtr[textureSamplerBindings.Length]; for (var i = 0; i < length; i += 1) { texturePtrs[i] = textureSamplerBindings[i].TextureHandle; samplerPtrs[i] = textureSamplerBindings[i].SamplerHandle; } Refresh.Refresh_BindVertexSamplers( Device.Handle, Handle, (IntPtr) texturePtrs, (IntPtr) samplerPtrs ); } /// /// Binds samplers to be used by the vertex shader. /// /// The texture-sampler pairs to bind. public unsafe void BindVertexSamplers( params TextureSamplerBinding[] textureSamplerBindings ) { BindVertexSamplers(textureSamplerBindings, textureSamplerBindings.Length); } /// /// Binds samplers to be used by the fragment shader. /// /// An array of texture-sampler pairs to bind. /// The number of texture-sampler pairs from the given array to bind. public unsafe void BindFragmentSamplers( TextureSamplerBinding[] textureSamplerBindings, int length ) { var texturePtrs = stackalloc IntPtr[textureSamplerBindings.Length]; var samplerPtrs = stackalloc IntPtr[textureSamplerBindings.Length]; for (var i = 0; i < length; i += 1) { #if DEBUG if (textureSamplerBindings[i].TextureHandle == IntPtr.Zero) { throw new NullReferenceException("Texture binding must not be null!"); } if (textureSamplerBindings[i].TextureHandle == IntPtr.Zero) { throw new NullReferenceException("Sampler binding must not be null!"); } #endif texturePtrs[i] = textureSamplerBindings[i].TextureHandle; samplerPtrs[i] = textureSamplerBindings[i].SamplerHandle; } Refresh.Refresh_BindFragmentSamplers( Device.Handle, Handle, (IntPtr) texturePtrs, (IntPtr) samplerPtrs ); } /// /// Binds samplers to be used by the fragment shader. /// /// An array of texture-sampler pairs to bind. public unsafe void BindFragmentSamplers( params TextureSamplerBinding[] textureSamplerBindings ) { BindFragmentSamplers(textureSamplerBindings, textureSamplerBindings.Length); } /// /// Pushes vertex shader uniforms to the device. /// /// A starting offset value to be used with draw calls. public unsafe uint PushVertexShaderUniforms( params T[] uniforms ) where T : unmanaged { fixed (T* ptr = &uniforms[0]) { return Refresh.Refresh_PushVertexShaderUniforms( Device.Handle, Handle, (IntPtr) ptr, (uint) (uniforms.Length * sizeof(T)) ); } } /// /// Pushes fragment shader uniforms to the device. /// /// A starting offset to be used with draw calls. public unsafe uint PushFragmentShaderUniforms( params T[] uniforms ) where T : unmanaged { fixed (T* ptr = &uniforms[0]) { return Refresh.Refresh_PushFragmentShaderUniforms( Device.Handle, Handle, (IntPtr) ptr, (uint) (uniforms.Length * sizeof(T)) ); } } /// /// Pushes compute shader uniforms to the device. /// /// A starting offset to be used with dispatch calls. public unsafe uint PushComputeShaderUniforms( params T[] uniforms ) where T : unmanaged { fixed (T* ptr = &uniforms[0]) { return Refresh.Refresh_PushComputeShaderUniforms( Device.Handle, Handle, (IntPtr) ptr, (uint) (uniforms.Length * sizeof(T)) ); } } /// /// Draws using instanced rendering. /// It is an error to call this method unless two vertex buffers have been bound. /// /// 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. /// An offset value obtained from PushVertexShaderUniforms. If no uniforms are required then use 0. /// An offset value obtained from PushFragmentShaderUniforms. If no uniforms are required the use 0. public void DrawInstancedPrimitives( uint baseVertex, uint startIndex, uint primitiveCount, uint instanceCount, uint vertexParamOffset, uint fragmentParamOffset ) { Refresh.Refresh_DrawInstancedPrimitives( Device.Handle, Handle, baseVertex, startIndex, primitiveCount, instanceCount, vertexParamOffset, fragmentParamOffset ); } /// /// 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. /// An offset value obtained from PushVertexShaderUniforms. If no uniforms are required then use 0. /// An offset value obtained from PushFragmentShaderUniforms. If no uniforms are required the use 0. public void DrawIndexedPrimitives( uint baseVertex, uint startIndex, uint primitiveCount, uint vertexParamOffset, uint fragmentParamOffset ) { Refresh.Refresh_DrawIndexedPrimitives( Device.Handle, Handle, baseVertex, startIndex, primitiveCount, vertexParamOffset, fragmentParamOffset ); } /// /// Draws using a vertex buffer. /// /// /// /// /// public void DrawPrimitives( uint vertexStart, uint primitiveCount, uint vertexParamOffset, uint fragmentParamOffset ) { Refresh.Refresh_DrawPrimitives( Device.Handle, Handle, vertexStart, primitiveCount, vertexParamOffset, fragmentParamOffset ); } /// /// Ends the current render pass. /// This must be called before beginning another render pass or submitting the command buffer. /// public void EndRenderPass() { Refresh.Refresh_EndRenderPass( Device.Handle, Handle ); } /// /// Acquires a swapchain texture. /// This texture will be presented to the given window when the command buffer is submitted. /// Can return null if the swapchain is unavailable. The user should ALWAYS handle the case where this occurs. /// If null is returned, presentation will not occur. /// It is an error to acquire two swapchain textures from the same window in one command buffer. /// public Texture? AcquireSwapchainTexture( Window window ) { var texturePtr = Refresh.Refresh_AcquireSwapchainTexture( Device.Handle, Handle, window.Handle, out var width, out var height ); if (texturePtr == IntPtr.Zero) { return null; } return new Texture( Device, texturePtr, Device.GetSwapchainFormat(window), width, height ); } /// /// Copies array data into a buffer. /// /// The buffer to copy to. /// The array to copy from. /// Specifies where in the buffer to start copying. /// Specifies whether the buffer should be copied in immediate or deferred mode. When in doubt, use deferred. public unsafe void SetBufferData( Buffer buffer, T[] data, uint bufferOffsetInBytes = 0 ) where T : unmanaged { SetBufferData( buffer, data, bufferOffsetInBytes, 0, (uint) data.Length ); } /// /// Copies arbitrary data into a buffer. /// /// The buffer to copy into. /// Pointer to the data to copy into the buffer. /// Specifies where in the buffer to copy data. /// The length of data that should be copied. /// Specifies whether the buffer should be copied in immediate or deferred mode. When in doubt, use deferred. public void SetBufferData( Buffer buffer, IntPtr dataPtr, uint bufferOffsetInBytes, uint dataLengthInBytes ) { Refresh.Refresh_SetBufferData( Device.Handle, Handle, buffer.Handle, bufferOffsetInBytes, dataPtr, dataLengthInBytes ); } /// /// Copies array data into a buffer. /// /// The buffer to copy to. /// The array to copy from. /// Specifies where in the buffer to start copying. /// The index of the first element to copy from the array. /// How many elements to copy. /// Specifies whether the buffer should be copied in immediate or deferred mode. When in doubt, use deferred. public unsafe void SetBufferData( Buffer buffer, T[] data, uint bufferOffsetInBytes, uint startElement, uint numElements ) where T : unmanaged { var elementSize = sizeof(T); fixed (T* ptr = &data[0]) { var dataPtr = ptr + (startElement * elementSize); Refresh.Refresh_SetBufferData( Device.Handle, Handle, buffer.Handle, bufferOffsetInBytes, (IntPtr) dataPtr, (uint) (numElements * elementSize) ); } } public unsafe void SetBufferData( Buffer buffer, IntPtr dataPtr, uint bufferOffsetInElements, uint numElements ) where T : unmanaged { Refresh.Refresh_SetBufferData( Device.Handle, Handle, buffer.Handle, (uint) sizeof(T) * bufferOffsetInElements, dataPtr, (uint) sizeof(T) * numElements ); } /// /// Asynchronously copies data into a texture. /// /// An array of data to copy into the texture. public unsafe void SetTextureData(Texture texture, T[] data) where T : unmanaged { SetTextureData(new TextureSlice(texture), data); } /// /// Asynchronously copies data into a texture slice. /// /// The texture slice to copy into. /// An array of data to copy into the texture. public unsafe void SetTextureData(in TextureSlice textureSlice, T[] data) where T : unmanaged { var size = sizeof(T); fixed (T* ptr = &data[0]) { Refresh.Refresh_SetTextureData( Device.Handle, Handle, textureSlice.ToRefreshTextureSlice(), (IntPtr) ptr, (uint) (data.Length * size) ); } } /// /// Asynchronously copies data into a texture slice. /// /// The texture slice to copy into. /// A pointer to an array of data to copy from. /// The amount of data to copy from the array. public void SetTextureData(in TextureSlice textureSlice, IntPtr dataPtr, uint dataLengthInBytes) { Refresh.Refresh_SetTextureData( Device.Handle, Handle, textureSlice.ToRefreshTextureSlice(), dataPtr, dataLengthInBytes ); } /// /// Asynchronously copies data into a texture. /// /// A pointer to an array of data to copy from. /// The amount of data to copy from the array. public void SetTextureData(Texture texture, IntPtr dataPtr, uint dataLengthInBytes) { SetTextureData(new TextureSlice(texture), dataPtr, dataLengthInBytes); } /// /// Asynchronously copies YUV data into three textures. Use with compressed video. /// public void SetTextureDataYUV(Texture yTexture, Texture uTexture, Texture vTexture, IntPtr dataPtr, uint dataLengthInBytes) { Refresh.Refresh_SetTextureDataYUV( Device.Handle, Handle, yTexture.Handle, uTexture.Handle, vTexture.Handle, yTexture.Width, yTexture.Height, uTexture.Width, uTexture.Height, dataPtr, dataLengthInBytes ); } /// /// Performs an asynchronous texture-to-texture copy on the GPU. /// /// The texture slice to copy from. /// The texture slice to copy to. /// The filter to use if the sizes of the texture slices differ. public void CopyTextureToTexture( in TextureSlice sourceTextureSlice, in TextureSlice destinationTextureSlice, Filter filter ) { var sourceRefreshTextureSlice = sourceTextureSlice.ToRefreshTextureSlice(); var destRefreshTextureSlice = destinationTextureSlice.ToRefreshTextureSlice(); Refresh.Refresh_CopyTextureToTexture( Device.Handle, Handle, sourceRefreshTextureSlice, destRefreshTextureSlice, (Refresh.Filter) filter ); } /// /// Performs an asynchronous texture-to-buffer copy. /// Note that the buffer is not guaranteed to be filled until you call GraphicsDevice.Wait() /// /// /// public void CopyTextureToBuffer( in TextureSlice textureSlice, Buffer buffer ) { var refreshTextureSlice = textureSlice.ToRefreshTextureSlice(); Refresh.Refresh_CopyTextureToBuffer( Device.Handle, Handle, refreshTextureSlice, buffer.Handle ); } } }