using System;
using System.Runtime.InteropServices;
using MoonWorks.Math;
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;
}
///
/// 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();
}
Rect renderArea = new Rect((int) colorAttachmentInfos[0].Texture.Width, (int) colorAttachmentInfos[0].Texture.Height);
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 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();
Rect renderArea = new Rect((int) colorAttachmentInfos[0].Texture.Width, (int) colorAttachmentInfos[0].Texture.Height);
fixed (Refresh.ColorAttachmentInfo* pColorAttachmentInfos = refreshColorAttachmentInfos)
{
Refresh.Refresh_BeginRenderPass(
Device.Handle,
Handle,
renderArea.ToRefresh(),
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
);
}
///
/// 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].Texture.Handle;
samplerPtrs[i] = textureSamplerBindings[i].Sampler.Handle;
}
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)
{
texturePtrs[i] = textureSamplerBindings[i].Texture.Handle;
samplerPtrs[i] = textureSamplerBindings[i].Sampler.Handle;
}
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 * Marshal.SizeOf())
);
}
}
///
/// 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 * Marshal.SizeOf())
);
}
}
///
/// 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 * Marshal.SizeOf())
);
}
}
///
/// Clears the render targets on the current framebuffer to a single color or depth/stencil value.
/// NOTE: It is recommended that you clear when beginning render passes unless you have a good reason to clear mid-pass.
///
/// The area of the framebuffer to clear.
/// Whether to clear colors, depth, or stencil value, or multiple.
/// The depth/stencil clear values. Will be ignored if color is not provided in ClearOptions.
/// The color clear values. Must provide one per render target. Can be omitted if depth/stencil is not cleared.
public unsafe void Clear(
in Rect clearRect,
ClearOptionsFlags clearOptions,
in DepthStencilValue depthStencilClearValue,
params Vector4[] clearColors
)
{
Refresh.Vec4* colors = stackalloc Refresh.Vec4[clearColors.Length];
for (var i = 0; i < clearColors.Length; i++)
{
colors[i] = new Refresh.Vec4
{
x = clearColors[i].X,
y = clearColors[i].Y,
z = clearColors[i].Z,
w = clearColors[i].W
};
}
Refresh.Refresh_Clear(
Device.Handle,
Handle,
clearRect.ToRefresh(),
(Refresh.ClearOptionsFlags) clearOptions,
(IntPtr) colors,
(uint) clearColors.Length,
depthStencilClearValue.ToRefresh()
);
}
///
/// 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.
///
public Texture? AcquireSwapchainTexture(
Window window
)
{
var texturePtr = Refresh.Refresh_AcquireSwapchainTexture(
Device.Handle,
Handle,
window.Handle
);
if (texturePtr == IntPtr.Zero)
{
return null;
}
return new Texture(
Device,
texturePtr,
Device.GetSwapchainFormat(window),
window.Width,
window.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 = Marshal.SizeOf();
fixed (T* ptr = &data[0])
{
var dataPtr = ptr + (startElement * elementSize);
Refresh.Refresh_SetBufferData(
Device.Handle,
Handle,
buffer.Handle,
bufferOffsetInBytes,
(IntPtr) dataPtr,
(uint) (numElements * elementSize)
);
}
}
///
/// 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 = Marshal.SizeOf();
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);
}
///
/// 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
);
}
}
}